Merge remote-tracking branch 'upstream/master' into lateral-tests

pull/25759/head
Shane Smiskol 3 years ago
commit 133cda20d8
  1. 1
      .github/ISSUE_TEMPLATE/bug_report.yml
  2. 5
      .github/ISSUE_TEMPLATE/config.yml
  3. 8
      .github/workflows/prebuilt.yaml
  4. 123
      .github/workflows/selfdrive_tests.yaml
  5. 8
      .github/workflows/setup/action.yaml
  6. 50
      .github/workflows/tools_tests.yaml
  7. 2
      .gitignore
  8. 28
      .pre-commit-config.yaml
  9. 6
      Dockerfile.openpilot_base
  10. 54
      Jenkinsfile
  11. 94
      Pipfile
  12. 2855
      Pipfile.lock
  13. 37
      RELEASES.md
  14. 19
      SConstruct
  15. 2
      body
  16. 2
      cereal
  17. 39
      common/gpio.cc
  18. 12
      common/gpio.h
  19. 2
      common/modeldata.h
  20. 43
      common/params.cc
  21. 6
      common/params.h
  22. 11
      common/params_pyx.pyx
  23. 2
      common/realtime.py
  24. 8
      common/tests/test_params.py
  25. 17
      common/tests/test_util.cc
  26. 25
      common/util.cc
  27. 2
      common/util.h
  28. 2
      common/version.h
  29. 311
      docs/CARS.md
  30. 7
      docs/docker/Dockerfile
  31. 2
      laika_repo
  32. 2
      launch_env.sh
  33. 14
      mypy.ini
  34. 2
      opendbc
  35. 2
      panda
  36. 7989
      poetry.lock
  37. 178
      pyproject.toml
  38. 13
      release/build_devel.sh
  39. 11
      release/files_common
  40. 1
      scripts/launch_corolla.sh
  41. 16
      scripts/switch_to_master.sh
  42. BIN
      selfdrive/assets/fonts/JetBrainsMono-Medium.ttf
  43. BIN
      selfdrive/assets/images/button_flag.png
  44. 4
      selfdrive/assets/img_couch.svg
  45. 10
      selfdrive/assets/img_experimental.svg
  46. 4
      selfdrive/assets/img_experimental_grey.svg
  47. 4
      selfdrive/assets/img_experimental_white.svg
  48. 2
      selfdrive/athena/tests/helpers.py
  49. 68
      selfdrive/athena/tests/test_athenad.py
  50. 4
      selfdrive/boardd/SConscript
  51. 97
      selfdrive/boardd/boardd.cc
  52. 297
      selfdrive/boardd/panda.cc
  53. 48
      selfdrive/boardd/panda.h
  54. 232
      selfdrive/boardd/panda_comms.cc
  55. 80
      selfdrive/boardd/panda_comms.h
  56. 287
      selfdrive/boardd/spi.cc
  57. 23
      selfdrive/car/__init__.py
  58. 13
      selfdrive/car/body/values.py
  59. 17
      selfdrive/car/car_helpers.py
  60. 27
      selfdrive/car/chrysler/values.py
  61. 8
      selfdrive/car/docs.py
  62. 28
      selfdrive/car/docs_definitions.py
  63. 7
      selfdrive/car/ecu_addrs.py
  64. 50
      selfdrive/car/ford/carcontroller.py
  65. 8
      selfdrive/car/ford/carstate.py
  66. 29
      selfdrive/car/ford/fordcan.py
  67. 9
      selfdrive/car/ford/interface.py
  68. 47
      selfdrive/car/ford/values.py
  69. 4
      selfdrive/car/fw_query_definitions.py
  70. 38
      selfdrive/car/fw_versions.py
  71. 70
      selfdrive/car/gm/carcontroller.py
  72. 64
      selfdrive/car/gm/carstate.py
  73. 59
      selfdrive/car/gm/gmcan.py
  74. 92
      selfdrive/car/gm/interface.py
  75. 79
      selfdrive/car/gm/values.py
  76. 14
      selfdrive/car/honda/carcontroller.py
  77. 14
      selfdrive/car/honda/carstate.py
  78. 18
      selfdrive/car/honda/interface.py
  79. 80
      selfdrive/car/honda/values.py
  80. 133
      selfdrive/car/hyundai/carcontroller.py
  81. 169
      selfdrive/car/hyundai/carstate.py
  82. 24
      selfdrive/car/hyundai/hyundaican.py
  83. 128
      selfdrive/car/hyundai/hyundaicanfd.py
  84. 219
      selfdrive/car/hyundai/interface.py
  85. 29
      selfdrive/car/hyundai/tests/test_hyundai.py
  86. 336
      selfdrive/car/hyundai/values.py
  87. 53
      selfdrive/car/interfaces.py
  88. 68
      selfdrive/car/isotp_parallel_query.py
  89. 4
      selfdrive/car/mazda/interface.py
  90. 8
      selfdrive/car/mazda/values.py
  91. 24
      selfdrive/car/mock/interface.py
  92. 2
      selfdrive/car/nissan/carstate.py
  93. 8
      selfdrive/car/subaru/carstate.py
  94. 19
      selfdrive/car/subaru/values.py
  95. 50
      selfdrive/car/tesla/values.py
  96. 27
      selfdrive/car/tests/routes.py
  97. 21
      selfdrive/car/tests/test_car_interfaces.py
  98. 18
      selfdrive/car/tests/test_docs.py
  99. 26
      selfdrive/car/tests/test_fw_fingerprint.py
  100. 25
      selfdrive/car/tests/test_models.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -8,6 +8,7 @@ body:
value: > value: >
Before creating a **bug report**, please check the following: Before creating a **bug report**, please check the following:
* If the issue likely only affects your car model or make, go back and open a **car bug report** instead. * If the issue likely only affects your car model or make, go back and open a **car bug report** instead.
* If the issue is related to the driving or driver monitoring models, you should open a [discussion](https://github.com/commaai/openpilot/discussions/categories/model-feedback) instead.
* Ensure you're running the latest openpilot release. * Ensure you're running the latest openpilot release.
* Ensure you're using officially supported hardware. Issues running on PCs have a different issue template. * Ensure you're using officially supported hardware. Issues running on PCs have a different issue template.
* Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue. * Ensure there isn't an existing issue for your bug. If there is, leave a comment on the existing issue.

@ -1,8 +1,11 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Report model bugs
url: https://github.com/commaai/openpilot/discussions/categories/model-feedback
about: Provide feedback for the driving or driver monitoring models
- name: Discussions - name: Discussions
url: https://github.com/commaai/openpilot/discussions url: https://github.com/commaai/openpilot/discussions
about: For questions and discussion about openpilot about: For questions and general discussion about openpilot
- name: Community Wiki - name: Community Wiki
url: https://github.com/commaai/openpilot/wiki url: https://github.com/commaai/openpilot/wiki
about: Check out our community wiki about: Check out our community wiki

@ -2,7 +2,6 @@ name: prebuilt
on: on:
schedule: schedule:
- cron: '0 * * * *' - cron: '0 * * * *'
workflow_dispatch: workflow_dispatch:
env: env:
@ -11,9 +10,7 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
jobs: jobs:
build_prebuilt: build_prebuilt:
@ -37,8 +34,7 @@ jobs:
- name: Build Docker image - name: Build Docker image
run: | run: |
eval "$BUILD" eval "$BUILD"
docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot .
docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f Dockerfile.openpilot .
- name: Push to container registry - name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN

@ -1,10 +1,15 @@
name: selfdrive name: selfdrive
on: on:
push: push:
branches-ignore: branches-ignore:
- 'testing-closet*' - 'testing-closet*'
pull_request: pull_request:
concurrency:
group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
@ -13,15 +18,12 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: | BUILD_CL: |
docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
UNIT_TEST: coverage run --append -m unittest discover UNIT_TEST: coverage run --append -m unittest discover
@ -36,13 +38,10 @@ jobs:
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
fetch-depth: 0
submodules: true submodules: true
- name: Build devel - name: Build devel
run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh run: TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup
with:
save-cache: true
- name: Check submodules - name: Check submodules
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: release/check-submodules.sh run: release/check-submodules.sh
@ -62,23 +61,25 @@ jobs:
cp .pylintrc $STRIPPED_DIR cp .pylintrc $STRIPPED_DIR
cp mypy.ini $STRIPPED_DIR cp mypy.ini $STRIPPED_DIR
cd $STRIPPED_DIR cd $STRIPPED_DIR
${{ env.RUN }} "pre-commit run --all" ${{ env.RUN }} "SKIP=test_translations pre-commit run --all"
build_all: build_all:
name: build all name: build all
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup
with:
save-cache: true
- name: Build openpilot with all flags - name: Build openpilot with all flags
run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh" run: ${{ env.RUN }} "scons -j$(nproc) --extras && release/check-dirty.sh"
- name: Cleanup scons cache - name: Cleanup scons cache
run: | run: |
${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \ ${{ env.RUN }} "rm -rf /tmp/scons_cache/* && \
scons -j$(nproc) --extras --cache-populate" scons -j$(nproc) --cache-populate"
#build_mac: #build_mac:
# name: build macos # name: build macos
@ -104,7 +105,7 @@ jobs:
# /usr/local/Cellar # /usr/local/Cellar
# ~/github_brew_cache_entries.txt # ~/github_brew_cache_entries.txt
# /tmp/scons_cache # /tmp/scons_cache
# key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'Pipfile*') }} # key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'poetry.lock') }}
# restore-keys: macos- # restore-keys: macos-
# - name: Brew link restored dependencies # - name: Brew link restored dependencies
# run: | # run: |
@ -120,11 +121,11 @@ jobs:
# - name: Build openpilot # - name: Build openpilot
# run: | # run: |
# source tools/openpilot_env.sh # source tools/openpilot_env.sh
# pipenv run selfdrive/manager/build.py # poetry run selfdrive/manager/build.py
# #
# # cleanup scons cache # # cleanup scons cache
# rm -rf /tmp/scons_cache/ # rm -rf /tmp/scons_cache/
# pipenv run scons -j$(nproc) --cache-populate # poetry run scons -j$(nproc) --cache-populate
# - name: Remove pre-existing Homebrew packages for caching # - name: Remove pre-existing Homebrew packages for caching
# if: steps.dependency-cache.outputs.cache-hit != 'true' # if: steps.dependency-cache.outputs.cache-hit != 'true'
# run: | # run: |
@ -140,7 +141,7 @@ jobs:
docker_push: docker_push:
name: docker push name: docker push
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 22
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast
steps: steps:
@ -149,12 +150,14 @@ jobs:
submodules: true submodules: true
- name: Build Docker image - name: Build Docker image
run: eval "$BUILD" run: eval "$BUILD"
timeout-minutes: 13
- name: Push to container registry - name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest
- name: Build CL Docker image - name: Build CL Docker image
run: eval "$BUILD_CL" run: eval "$BUILD_CL"
timeout-minutes: 4
- name: Push to container registry - name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
@ -163,7 +166,7 @@ jobs:
static_analysis: static_analysis:
name: static analysis name: static analysis
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -171,21 +174,23 @@ jobs:
- name: Build Docker image - name: Build Docker image
run: eval "$BUILD" run: eval "$BUILD"
- name: pre-commit - name: pre-commit
timeout-minutes: 5
run: ${{ env.RUN }} "pre-commit run --all" run: ${{ env.RUN }} "pre-commit run --all"
valgrind: valgrind:
name: valgrind name: valgrind
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run valgrind - name: Run valgrind
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && \ ${{ env.RUN }} "python selfdrive/test/test_valgrind_replay.py"
python selfdrive/test/test_valgrind_replay.py"
- name: Print logs - name: Print logs
if: always() if: always()
run: cat selfdrive/test/valgrind_logs.txt run: cat selfdrive/test/valgrind_logs.txt
@ -193,16 +198,18 @@ jobs:
unit_tests: unit_tests:
name: unit tests name: unit tests
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 30
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Run unit tests - name: Run unit tests
timeout-minutes: 15
run: | run: |
${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \
scons -j$(nproc) && \
$UNIT_TEST common && \ $UNIT_TEST common && \
$UNIT_TEST opendbc/can && \ $UNIT_TEST opendbc/can && \
$UNIT_TEST selfdrive/boardd && \ $UNIT_TEST selfdrive/boardd && \
@ -215,7 +222,6 @@ jobs:
$UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/athena && \
$UNIT_TEST selfdrive/thermald && \ $UNIT_TEST selfdrive/thermald && \
$UNIT_TEST system/hardware/tici && \ $UNIT_TEST system/hardware/tici && \
$UNIT_TEST selfdrive/modeld && \
$UNIT_TEST tools/lib/tests && \ $UNIT_TEST tools/lib/tests && \
./selfdrive/ui/tests/create_test_translations.sh && \ ./selfdrive/ui/tests/create_test_translations.sh && \
QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \
@ -226,6 +232,7 @@ jobs:
./selfdrive/loggerd/tests/test_logger &&\ ./selfdrive/loggerd/tests/test_logger &&\
./system/proclogd/tests/test_proclog && \ ./system/proclogd/tests/test_proclog && \
./tools/replay/tests/test_replay && \ ./tools/replay/tests/test_replay && \
./tools/cabana/tests/test_cabana && \
./system/camerad/test/ae_gray_test && \ ./system/camerad/test/ae_gray_test && \
coverage xml" coverage xml"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
@ -234,7 +241,7 @@ jobs:
process_replay: process_replay:
name: process replay name: process replay
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 25
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -246,10 +253,12 @@ jobs:
with: with:
path: /tmp/comma_download_cache path: /tmp/comma_download_cache
key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }} key: proc-replay-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/ref_commit') }}
- name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Run replay - name: Run replay
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && \ ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
coverage xml" coverage xml"
- name: Print diff - name: Print diff
if: always() if: always()
@ -263,15 +272,14 @@ jobs:
- name: Upload reference logs - name: Upload reference logs
if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && \ ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
model_replay_onnx: test_modeld:
name: model replay onnx name: model tests
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -280,29 +288,41 @@ jobs:
- name: Build Docker image - name: Build Docker image
# Sim docker is needed to get the OpenCL drivers # Sim docker is needed to get the OpenCL drivers
run: eval "$BUILD_CL" run: eval "$BUILD_CL"
- name: Run replay - name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Run model replay with ONNX
timeout-minutes: 2
run: |
${{ env.RUN_CL }} "ONNXCPU=1 CI=1 coverage run selfdrive/test/process_replay/model_replay.py && \
coverage xml"
- name: Run unit tests
timeout-minutes: 5
run: | run: |
${{ env.RUN_CL }} "scons -j$(nproc) && \ ${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \
ONNXCPU=1 CI=1 coverage run \
selfdrive/test/process_replay/model_replay.py -j$(nproc) && \
coverage xml" coverage xml"
- name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2
test_longitudinal: test_longitudinal:
name: longitudinal name: longitudinal
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
submodules: true submodules: true
- uses: ./.github/workflows/setup - uses: ./.github/workflows/setup
- name: Build openpilot
run: |
${{ env.RUN }} "scons -j$(nproc)"
- name: Test longitudinal - name: Test longitudinal
run: | run: |
${{ env.RUN }} "mkdir -p selfdrive/test/out && \ ${{ env.RUN }} "mkdir -p selfdrive/test/out && \
scons -j$(nproc) && \
cd selfdrive/test/longitudinal_maneuvers && \ cd selfdrive/test/longitudinal_maneuvers && \
coverage run ./test_longitudinal.py && \ coverage run ./test_longitudinal.py && \
coverage xml" coverage xml"
timeout-minutes: 2
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
- uses: actions/upload-artifact@v2 - uses: actions/upload-artifact@v2
@ -315,7 +335,7 @@ jobs:
test_cars: test_cars:
name: cars name: cars
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
@ -331,10 +351,12 @@ jobs:
with: with:
path: /tmp/comma_download_cache path: /tmp/comma_download_cache
key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }} key: car_models-${{ hashFiles('selfdrive/car/tests/test_models.py', 'selfdrive/car/tests/routes.py') }}-${{ matrix.job }}
- name: Build openpilot
run: ${{ env.RUN }} "scons -j$(nproc)"
- name: Test car models - name: Test car models
timeout-minutes: 12
run: | run: |
${{ env.RUN }} "scons -j$(nproc) && \ ${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \
coverage run -m pytest selfdrive/car/tests/test_models.py && \
coverage xml && \ coverage xml && \
chmod -R 777 /tmp/comma_download_cache" chmod -R 777 /tmp/comma_download_cache"
env: env:
@ -343,29 +365,10 @@ jobs:
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
docs:
name: build docs
runs-on: ubuntu-20.04
timeout-minutes: 50
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Build docker container
run: |
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker pull $DOCKER_REGISTRY/openpilot-docs:latest || true
DOCKER_BUILDKIT=1 docker build --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile .
- name: Push docker container
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs:latest
car_docs_diff: car_docs_diff:
name: comment on PR with car docs diff name: PR comments
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 20
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

@ -15,9 +15,9 @@ runs:
# build cache # build cache
- id: date - id: date
shell: bash shell: bash
run: echo "::set-output name=date::$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d')" run: echo "CACHE_COMMIT_DATE=$(git log -1 --pretty='format:%cd' --date=format:'%Y-%m-%d-%H:%M')" >> $GITHUB_ENV
- shell: bash - shell: bash
run: echo "${{ steps.date.outputs.date }}" run: echo "$CACHE_COMMIT_DATE"
- shell: bash - shell: bash
run: echo "CACHE_SKIP_SAVE=true" >> $GITHUB_ENV run: echo "CACHE_SKIP_SAVE=true" >> $GITHUB_ENV
if: github.ref != 'refs/heads/master' || inputs.save-cache == 'false' if: github.ref != 'refs/heads/master' || inputs.save-cache == 'false'
@ -27,9 +27,9 @@ runs:
uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b
with: with:
path: /tmp/scons_cache path: /tmp/scons_cache
key: scons-${{ steps.date.outputs.date }}-${{ github.sha }} key: scons-${{ env.CACHE_COMMIT_DATE }}-${{ github.sha }}
restore-keys: | restore-keys: |
scons-${{ steps.date.outputs.date }}- scons-${{ env.CACHE_COMMIT_DATE }}-
scons- scons-
# build our docker image # build our docker image

@ -1,8 +1,15 @@
name: tools name: tools
on: on:
push: push:
branches-ignore:
- 'testing-closet*'
pull_request: pull_request:
concurrency:
group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }}
cancel-in-progress: true
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl CL_BASE_IMAGE: openpilot-base-cl
@ -10,21 +17,20 @@ env:
DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }}
BUILD: | BUILD: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: | BUILD_CL: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base_cl) || true DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/sh -c
docker build --cache-from $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest -t $CL_BASE_IMAGE:latest -f Dockerfile.openpilot_base_cl .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e \
GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c
jobs: jobs:
plotjuggler: plotjuggler:
name: plotjuggler name: plotjuggler
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 30 timeout-minutes: 20
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@ -32,6 +38,7 @@ jobs:
- name: Build Docker image - name: Build Docker image
run: eval "$BUILD" run: eval "$BUILD"
- name: Unit test - name: Unit test
timeout-minutes: 2
run: | run: |
${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ ${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \
apt-get update && \ apt-get update && \
@ -42,7 +49,7 @@ jobs:
simulator: simulator:
name: simulator name: simulator
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
timeout-minutes: 50 timeout-minutes: 30
env: env:
IMAGE_NAME: openpilot-sim IMAGE_NAME: openpilot-sim
if: github.repository == 'commaai/openpilot' if: github.repository == 'commaai/openpilot'
@ -56,12 +63,29 @@ jobs:
run: eval "$BUILD" run: eval "$BUILD"
- name: Build base cl image - name: Build base cl image
run: eval "$BUILD_CL" run: eval "$BUILD_CL"
- name: Pull latest simulator image
run: docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true
- name: Build simulator image - name: Build simulator image
run: docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim . run: DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim .
- name: Push to container registry - name: Push to container registry
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
docs:
name: build docs
runs-on: ubuntu-20.04
timeout-minutes: 25
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Build docker container
run: |
DOCKER_BUILDKIT=1 docker build --pull --build-arg BUILDKIT_INLINE_CACHE=1 --cache-from $DOCKER_REGISTRY/openpilot-docs:latest -t $DOCKER_REGISTRY/openpilot-docs:latest -f docs/docker/Dockerfile .
- name: Push docker container
if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot'
run: |
$DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs:latest

2
.gitignore vendored

@ -83,3 +83,5 @@ selfdrive/modeld/models/*.thneed
build/ build/
!**/.gitkeep !**/.gitkeep
poetry.toml

@ -24,18 +24,14 @@ repos:
# if you've got a short variable name that's getting flagged, add it here # if you've got a short variable name that's getting flagged, add it here
- -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup
- --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US - --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US
- repo: https://github.com/pre-commit/mirrors-mypy - repo: local
rev: v0.931
hooks: hooks:
- id: mypy - id: mypy
exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)/|(tinygrad/)|(tinygrad_repo/)' name: mypy
additional_dependencies: ['types-PyYAML', 'lxml', 'numpy', 'types-atomicwrites', 'types-pycurl', 'types-requests', 'types-certifi'] entry: mypy
args: language: system
- --warn-redundant-casts types: [python]
- --warn-return-any exclude: '^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)'
- --warn-unreachable
- --warn-unused-ignores
#- --html-report=/home/batman/openpilot
- repo: https://github.com/PyCQA/flake8 - repo: https://github.com/PyCQA/flake8
rev: 4.0.1 rev: 4.0.1
hooks: hooks:
@ -57,6 +53,7 @@ repos:
types: [python] types: [python]
exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)'
args: args:
- -j0
- -rn - -rn
- -sn - -sn
- --rcfile=.pylintrc - --rcfile=.pylintrc
@ -74,3 +71,14 @@ repos:
- --quiet - --quiet
- --force - --force
- -j8 - -j8
- repo: local
hooks:
- id: test_translations
name: test translations
entry: selfdrive/ui/tests/test_translations.py
language: script
pass_filenames: false
- repo: https://github.com/python-poetry/poetry
rev: '1.2.2'
hooks:
- id: poetry-check

@ -12,19 +12,19 @@ ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8 ENV LC_ALL en_US.UTF-8
ENV PIPENV_SYSTEM=1 ENV POETRY_VIRTUALENVS_CREATE=false
ENV PYENV_VERSION=3.8.10 ENV PYENV_VERSION=3.8.10
ENV PYENV_ROOT="/root/.pyenv" ENV PYENV_ROOT="/root/.pyenv"
ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH"
COPY Pipfile Pipfile.lock .python-version update_requirements.sh /tmp/ COPY pyproject.toml poetry.lock .python-version update_requirements.sh /tmp/
COPY tools/ubuntu_setup.sh /tmp/tools/ COPY tools/ubuntu_setup.sh /tmp/tools/
RUN cd /tmp && \ RUN cd /tmp && \
tools/ubuntu_setup.sh && \ tools/ubuntu_setup.sh && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
rm -rf /tmp/* && \ rm -rf /tmp/* && \
rm -rf /root/.cache && \ rm -rf /root/.cache && \
pip uninstall -y pipenv && \ pip uninstall -y poetry && \
# remove unused architectures from gcc for panda # remove unused architectures from gcc for panda
cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \
rm -rf arm/ && \ rm -rf arm/ && \

54
Jenkinsfile vendored

@ -59,7 +59,7 @@ pipeline {
branch 'devel-staging' branch 'devel-staging'
} }
steps { steps {
phone_steps("tici", [ phone_steps("tici-needs-can", [
["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"],
]) ])
} }
@ -111,7 +111,7 @@ pipeline {
R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}" R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}"
} }
steps { steps {
phone_steps("tici", [ phone_steps("tici-needs-can", [
["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"],
["build openpilot", "cd selfdrive/manager && ./build.py"], ["build openpilot", "cd selfdrive/manager && ./build.py"],
["check dirty", "release/check-dirty.sh"], ["check dirty", "release/check-dirty.sh"],
@ -122,25 +122,44 @@ pipeline {
} }
} }
stage('loopback-tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-loopback", [
["build openpilot", "cd selfdrive/manager && ./build.py"],
["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"],
])
}
}
stage('HW + Unit Tests') { stage('HW + Unit Tests') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps { steps {
phone_steps("tici2", [ phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
//["test power draw", "python system/hardware/tici/test_power_draw.py"], ["test power draw", "python system/hardware/tici/test_power_draw.py"],
["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"],
["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"],
["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"],
["test sensord", "python selfdrive/sensord/tests/test_sensord.py"],
["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"], ["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"],
]) ])
} }
} }
stage('camerad') { stage('camerad-ar') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-ar0321", [
["build", "cd selfdrive/manager && ./build.py"],
["test camerad", "python system/camerad/test/test_camerad.py"],
["test exposure", "python system/camerad/test/test_exposure.py"],
])
}
}
stage('camerad-ox') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps { steps {
phone_steps("tici-party", [ phone_steps("tici-ox03c10", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["test camerad", "python system/camerad/test/test_camerad.py"], ["test camerad", "python system/camerad/test/test_camerad.py"],
["test exposure", "python system/camerad/test/test_exposure.py"], ["test exposure", "python system/camerad/test/test_exposure.py"],
@ -148,17 +167,32 @@ pipeline {
} }
} }
stage('sensord') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps {
phone_steps("tici-lsmc", [
["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"],
])
phone_steps("tici-bmx-lsm", [
["build", "cd selfdrive/manager && ./build.py"],
["test sensord", "cd selfdrive/sensord/tests && python -m unittest test_sensord.py"],
])
}
}
stage('replay') { stage('replay') {
agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } }
steps { steps {
phone_steps("tici3", [ phone_steps("tici-common", [
["build", "cd selfdrive/manager && ./build.py"], ["build", "cd selfdrive/manager && ./build.py"],
["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"],
]) ])
} }
} }
}
}
} }
} }
} }

@ -1,94 +0,0 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
av = "*"
azure-storage-blob = "~=2.1"
control = "*"
coverage = "*"
dictdiffer = "*"
fastcluster = "*"
hexdump = "*"
hypothesis = "==6.46.7"
inputs = "*"
lru-dict = "*"
markdown-it-py = "*"
matplotlib = "*"
mypy = "*"
myst-parser = "*"
natsort = "*"
numpy = "*"
opencv-python-headless = "*"
parameterized = "*"
paramiko = "*"
pprofile = "*"
pre-commit = "*"
pycurl = "*"
pygame = "*"
pyprof2calltree = "*"
pytest = "*"
pytest-xdist = "*"
reverse_geocoder = "*"
scipy = "*"
sphinx = "*"
sphinx-sitemap = "*"
sphinx-rtd-theme = "*"
breathe = "*"
subprocess32 = "*"
tenacity = "*"
mpld3 = "*"
carla = {version = "==0.9.13", markers="platform_system != 'Darwin'"}
ft4222 = "*"
pandas = "*"
tabulate = "*"
[packages]
atomicwrites = "*"
casadi = {version = "*", markers="platform_system != 'Darwin'"}
cffi = "*"
crcmod = "*"
cryptography = "*"
Cython = "*"
flake8 = "*"
Flask = "*"
future-fstrings = "*" # for acados
gunicorn = "*"
hexdump = "*"
Jinja2 = "*"
json-rpc = "*"
libusb1 = "*"
nose = "*"
numpy = "*"
protobuf = "==3.20.1"
onnx = "*"
onnxruntime-gpu = {version = "*", markers="platform_system != 'Darwin'"}
pillow = "*"
psutil = "*"
pycapnp = "==1.1.0"
pycryptodome = "*"
PyJWT = "*"
pylint = "*"
pyopencl = "*"
pyserial = "*"
python-dateutil = "*"
PyYAML = "*"
pyzmq = "*"
requests = "*"
scons = "*"
sentry-sdk = "*"
setproctitle = "*"
six = "*"
smbus2 = "*"
sympy = "!=1.6.1"
timezonefinder = "*"
tqdm = "*"
urllib3 = "*"
utm = "*"
websocket_client = "*"
hatanaka = "==2.4"
PySide2 = "*"
[requires]
python_version = "3.8"

2855
Pipfile.lock generated

File diff suppressed because it is too large Load Diff

@ -1,7 +1,40 @@
Version 0.8.17 (2022-XX-XX) Version 0.9.1 (2022-12-XX)
======================== ========================
* New driving model * New driving model
* Internal feature space accuracy increased tenfold during training, this makes the model dramatically more accurate.
Version 0.9.0 (2022-11-21)
========================
* New driving model
* Internal feature space information content increased tenfold during training to ~700 bits, which makes the model dramatically more accurate
* Less reliance on previous frames makes model more reactive and snappy
* Trained in new reprojective simulator
* Trained in 36 hours from scratch, compared to one week for previous releases
* Training now simulates both lateral and longitudinal behavior, which allows openpilot to slow down for turns, stop at traffic lights, and more in experimental mode
* Experimental driving mode
* End-to-end longitudinal control
* Stops for traffic lights and stop signs
* Slows down for turns
* openpilot defaults to chill mode, enable experimental mode in settings
* Driver monitoring updates
* New bigger model with added end-to-end distracted trigger
* Reduced false positives during driver calibration
* Self-tuning torque controller: learns parameters live for each car
* Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models
* UI updates
* Matched speeds shown on car's dash
* Multi-language in navigation
* Improved update experience
* Border turns grey while overriding steering
* Bookmark events while driving; view them in comma connect
* New onroad visualization for experimental mode
* tools: new and improved cabana thanks to deanlee!
* Experimental longitudinal support for Volkswagen, CAN-FD Hyundai, and new GM models
* Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin!
* Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin!
* Kia Sportage 2023 support thanks to sunnyhaibin!
* Kia Sportage Hybrid 2023 support thanks to sunnyhaibin!
* Kia Stinger 2022 support thanks to sunnyhaibin!
Version 0.8.16 (2022-08-26) Version 0.8.16 (2022-08-26)
======================== ========================

@ -298,12 +298,15 @@ if arch == "Darwin":
qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"]
qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin"))
else: else:
qt_env['QTDIR'] = "/usr" qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip()
qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip()
qt_env['QTDIR'] = qt_install_prefix
qt_dirs = [ qt_dirs = [
f"/usr/include/{real_arch}-linux-gnu/qt5", f"{qt_install_headers}",
f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", f"{qt_install_headers}/QtGui/5.12.8/QtGui",
] ]
qt_dirs += [f"/usr/include/{real_arch}-linux-gnu/qt5/Qt{m}" for m in qt_modules] qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules]
qt_libs = [f"Qt5{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules]
if arch == "larch64": if arch == "larch64":
@ -327,6 +330,7 @@ qt_flags = [
qt_env['CXXFLAGS'] += qt_flags qt_env['CXXFLAGS'] += qt_flags
qt_env['LIBPATH'] += ['#selfdrive/ui'] qt_env['LIBPATH'] += ['#selfdrive/ui']
qt_env['LIBS'] = qt_libs qt_env['LIBS'] = qt_libs
qt_env['QT_MOCHPREFIX'] = cache_dir + '/moc_files/moc_'
if GetOption("clazy"): if GetOption("clazy"):
checks = [ checks = [
@ -431,7 +435,12 @@ SConscript(['selfdrive/sensord/SConscript'])
SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/ui/SConscript'])
SConscript(['selfdrive/navd/SConscript']) SConscript(['selfdrive/navd/SConscript'])
SConscript(['tools/replay/SConscript']) if arch in ['x86_64', 'Darwin'] or GetOption('extras'):
SConscript(['tools/replay/SConscript'])
opendbc = abspath([File('opendbc/can/libdbc.so')])
Export('opendbc')
SConscript(['tools/cabana/SConscript'])
if GetOption('test'): if GetOption('test'):
SConscript('panda/tests/safety/SConscript') SConscript('panda/tests/safety/SConscript')

@ -1 +1 @@
Subproject commit 04aeb30ce0bb14759989cd374158233877e1e151 Subproject commit dc780f858c1ef641471d09b72569e199e3e10acb

@ -1 +1 @@
Subproject commit 2335f98bbe628ec6fde92c8d929ecaf373b125af Subproject commit 3bae09cf6527674d7eda3a9956242aad94a8f3d2

@ -4,11 +4,11 @@
#include <unistd.h> #include <unistd.h>
#include <cstring> #include <cstring>
#include <linux/gpio.h>
#include <sys/ioctl.h>
#include "common/util.h" #include "common/util.h"
#include "common/swaglog.h"
// We assume that all pins have already been exported on boot,
// and that we have permission to write to them.
int gpio_init(int pin_nr, bool output) { int gpio_init(int pin_nr, bool output) {
char pin_dir_path[50]; char pin_dir_path[50];
@ -30,3 +30,36 @@ int gpio_set(int pin_nr, bool high) {
} }
return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1); return util::write_file(pin_val_path, (void*)(high ? "1" : "0"), 1);
} }
int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr) {
// Assumed that all interrupt pins are unexported and rights are given to
// read from gpiochip0.
std::string gpiochip_path = "/dev/gpiochip" + std::to_string(gpiochiop_id);
int fd = open(gpiochip_path.c_str(), O_RDONLY);
if (fd < 0) {
LOGE("Error opening gpiochip0 fd")
return -1;
}
// Setup event
struct gpioevent_request rq;
rq.lineoffset = pin_nr;
rq.handleflags = GPIOHANDLE_REQUEST_INPUT;
/* Requesting both edges as the data ready pulse from the lsm6ds sensor is
very short(75us) and is mostly detected as falling edge instead of rising.
So if it is detected as rising the following falling edge is skipped. */
rq.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES;
strncpy(rq.consumer_label, consumer_label, std::size(rq.consumer_label) - 1);
int ret = ioctl(fd, GPIO_GET_LINEEVENT_IOCTL, &rq);
if (ret == -1) {
LOGE("Unable to get line event from ioctl : %s", strerror(errno));
close(fd);
return -1;
}
close(fd);
return rq.fd;
}

@ -8,6 +8,11 @@
#define GPIO_UBLOX_PWR_EN 34 #define GPIO_UBLOX_PWR_EN 34
#define GPIO_STM_RST_N 124 #define GPIO_STM_RST_N 124
#define GPIO_STM_BOOT0 134 #define GPIO_STM_BOOT0 134
#define GPIO_BMX_ACCEL_INT 21
#define GPIO_BMX_GYRO_INT 23
#define GPIO_BMX_MAGN_INT 87
#define GPIO_LSM_INT 84
#define GPIOCHIP_INT 0
#else #else
#define GPIO_HUB_RST_N 0 #define GPIO_HUB_RST_N 0
#define GPIO_UBLOX_RST_N 0 #define GPIO_UBLOX_RST_N 0
@ -15,7 +20,14 @@
#define GPIO_UBLOX_PWR_EN 0 #define GPIO_UBLOX_PWR_EN 0
#define GPIO_STM_RST_N 0 #define GPIO_STM_RST_N 0
#define GPIO_STM_BOOT0 0 #define GPIO_STM_BOOT0 0
#define GPIO_BMX_ACCEL_INT 0
#define GPIO_BMX_GYRO_INT 0
#define GPIO_BMX_MAGN_INT 0
#define GPIO_LSM_INT 0
#define GPIOCHIP_INT 0
#endif #endif
int gpio_init(int pin_nr, bool output); int gpio_init(int pin_nr, bool output);
int gpio_set(int pin_nr, bool high); int gpio_set(int pin_nr, bool high);
int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr);

@ -4,7 +4,7 @@
#include "common/mat.h" #include "common/mat.h"
#include "system/hardware/hw.h" #include "system/hardware/hw.h"
const int TRAJECTORY_SIZE = 33; const int TRAJECTORY_SIZE = 33;
const int LAT_MPC_N = 16; const int LAT_MPC_N = 16;
const int LON_MPC_N = 32; const int LON_MPC_N = 32;
const float MIN_DRAW_DISTANCE = 10.0; const float MIN_DRAW_DISTANCE = 10.0;

@ -3,6 +3,7 @@
#include <dirent.h> #include <dirent.h>
#include <sys/file.h> #include <sys/file.h>
#include <algorithm>
#include <csignal> #include <csignal>
#include <unordered_map> #include <unordered_map>
@ -101,7 +102,8 @@ std::unordered_map<std::string, uint32_t> keys = {
{"DashcamOverride", PERSISTENT}, {"DashcamOverride", PERSISTENT},
{"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
{"DisablePowerDown", PERSISTENT}, {"DisablePowerDown", PERSISTENT},
{"EndToEndLong", PERSISTENT}, {"ExperimentalMode", PERSISTENT},
{"ExperimentalModeConfirmed", PERSISTENT},
{"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB
{"DisableUpdates", PERSISTENT}, {"DisableUpdates", PERSISTENT},
{"DisengageOnAccelerator", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT},
@ -117,6 +119,7 @@ std::unordered_map<std::string, uint32_t> keys = {
{"GithubUsername", PERSISTENT}, {"GithubUsername", PERSISTENT},
{"GitRemote", PERSISTENT}, {"GitRemote", PERSISTENT},
{"GsmApn", PERSISTENT}, {"GsmApn", PERSISTENT},
{"GsmMetered", PERSISTENT},
{"GsmRoaming", PERSISTENT}, {"GsmRoaming", PERSISTENT},
{"HardwareSerial", PERSISTENT}, {"HardwareSerial", PERSISTENT},
{"HasAcceptedTerms", PERSISTENT}, {"HasAcceptedTerms", PERSISTENT},
@ -143,6 +146,8 @@ std::unordered_map<std::string, uint32_t> keys = {
{"LastUpdateException", CLEAR_ON_MANAGER_START}, {"LastUpdateException", CLEAR_ON_MANAGER_START},
{"LastUpdateTime", PERSISTENT}, {"LastUpdateTime", PERSISTENT},
{"LiveParameters", PERSISTENT}, {"LiveParameters", PERSISTENT},
{"LiveTorqueCarParams", PERSISTENT},
{"LiveTorqueParameters", PERSISTENT | DONT_LOG},
{"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
{"NavSettingTime24h", PERSISTENT}, {"NavSettingTime24h", PERSISTENT},
{"NavSettingLeftSide", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT},
@ -154,18 +159,25 @@ std::unordered_map<std::string, uint32_t> keys = {
{"PrimeType", PERSISTENT}, {"PrimeType", PERSISTENT},
{"RecordFront", PERSISTENT}, {"RecordFront", PERSISTENT},
{"RecordFrontLock", PERSISTENT}, // for the internal fleet {"RecordFrontLock", PERSISTENT}, // for the internal fleet
{"ReleaseNotes", PERSISTENT},
{"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"ReplayControlsState", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON},
{"ShouldDoUpdate", CLEAR_ON_MANAGER_START}, {"ShouldDoUpdate", CLEAR_ON_MANAGER_START},
{"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"SnoozeUpdate", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF},
{"SshEnabled", PERSISTENT}, {"SshEnabled", PERSISTENT},
{"SubscriberInfo", PERSISTENT}, {"SubscriberInfo", PERSISTENT},
{"SwitchToBranch", CLEAR_ON_MANAGER_START},
{"TermsVersion", PERSISTENT}, {"TermsVersion", PERSISTENT},
{"Timezone", PERSISTENT}, {"Timezone", PERSISTENT},
{"TrainingVersion", PERSISTENT}, {"TrainingVersion", PERSISTENT},
{"UbloxAvailable", PERSISTENT},
{"UpdateAvailable", CLEAR_ON_MANAGER_START}, {"UpdateAvailable", CLEAR_ON_MANAGER_START},
{"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START},
{"UpdaterState", CLEAR_ON_MANAGER_START},
{"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START},
{"UpdaterTargetBranch", CLEAR_ON_MANAGER_START},
{"UpdaterAvailableBranches", CLEAR_ON_MANAGER_START},
{"UpdaterCurrentDescription", CLEAR_ON_MANAGER_START},
{"UpdaterCurrentReleaseNotes", CLEAR_ON_MANAGER_START},
{"UpdaterNewDescription", CLEAR_ON_MANAGER_START},
{"UpdaterNewReleaseNotes", CLEAR_ON_MANAGER_START},
{"Version", PERSISTENT}, {"Version", PERSISTENT},
{"VisionRadarToggle", PERSISTENT}, {"VisionRadarToggle", PERSISTENT},
{"WideCameraOnly", PERSISTENT}, {"WideCameraOnly", PERSISTENT},
@ -191,10 +203,16 @@ std::unordered_map<std::string, uint32_t> keys = {
Params::Params(const std::string &path) { Params::Params(const std::string &path) {
const char* env = std::getenv("OPENPILOT_PREFIX"); prefix = "/" + util::getenv("OPENPILOT_PREFIX", "d");
prefix = env ? "/" + std::string(env) : "/d"; params_path = ensure_params_path(prefix, path);
std::string default_param_path = ensure_params_path(prefix); }
params_path = path.empty() ? default_param_path : ensure_params_path(prefix, path);
std::vector<std::string> Params::allKeys() const {
std::vector<std::string> ret;
for (auto &p : keys) {
ret.push_back(p.first);
}
return ret;
} }
bool Params::checkKey(const std::string &key) { bool Params::checkKey(const std::string &key) {
@ -282,10 +300,13 @@ std::map<std::string, std::string> Params::readAll() {
void Params::clearAll(ParamKeyType key_type) { void Params::clearAll(ParamKeyType key_type) {
FileLock file_lock(params_path + "/.lock"); FileLock file_lock(params_path + "/.lock");
std::string path; if (key_type == ALL) {
for (auto &[key, type] : keys) { util::remove_files_in_dir(getParamPath());
if (type & key_type) { } else {
unlink(getParamPath(key).c_str()); for (auto &[key, type] : keys) {
if (type & key_type) {
unlink(getParamPath(key).c_str());
}
} }
} }

@ -2,6 +2,7 @@
#include <map> #include <map>
#include <string> #include <string>
#include <vector>
enum ParamKeyType { enum ParamKeyType {
PERSISTENT = 0x02, PERSISTENT = 0x02,
@ -15,6 +16,7 @@ enum ParamKeyType {
class Params { class Params {
public: public:
Params(const std::string &path = {}); Params(const std::string &path = {});
std::vector<std::string> allKeys() const;
bool checkKey(const std::string &key); bool checkKey(const std::string &key);
ParamKeyType getKeyType(const std::string &key); ParamKeyType getKeyType(const std::string &key);
inline std::string getParamPath(const std::string &key = {}) { inline std::string getParamPath(const std::string &key = {}) {
@ -27,8 +29,8 @@ public:
// helpers for reading values // helpers for reading values
std::string get(const std::string &key, bool block = false); std::string get(const std::string &key, bool block = false);
inline bool getBool(const std::string &key) { inline bool getBool(const std::string &key, bool block = false) {
return get(key) == "1"; return get(key, block) == "1";
} }
std::map<std::string, std::string> readAll(); std::map<std::string, std::string> readAll();

@ -2,6 +2,7 @@
# cython: language_level = 3 # cython: language_level = 3
from libcpp cimport bool from libcpp cimport bool
from libcpp.string cimport string from libcpp.string cimport string
from libcpp.vector cimport vector
import threading import threading
cdef extern from "common/params.h": cdef extern from "common/params.h":
@ -15,13 +16,14 @@ cdef extern from "common/params.h":
cdef cppclass c_Params "Params": cdef cppclass c_Params "Params":
c_Params(string) nogil c_Params(string) nogil
string get(string, bool) nogil string get(string, bool) nogil
bool getBool(string) nogil bool getBool(string, bool) nogil
int remove(string) nogil int remove(string) nogil
int put(string, string) nogil int put(string, string) nogil
int putBool(string, bool) nogil int putBool(string, bool) nogil
bool checkKey(string) nogil bool checkKey(string) nogil
string getParamPath(string) nogil string getParamPath(string) nogil
void clearAll(ParamKeyType) void clearAll(ParamKeyType)
vector[string] allKeys()
def ensure_bytes(v): def ensure_bytes(v):
@ -66,11 +68,11 @@ cdef class Params:
return val if encoding is None else val.decode(encoding) return val if encoding is None else val.decode(encoding)
def get_bool(self, key): def get_bool(self, key, bool block=False):
cdef string k = self.check_key(key) cdef string k = self.check_key(key)
cdef bool r cdef bool r
with nogil: with nogil:
r = self.p.getBool(k) r = self.p.getBool(k, block)
return r return r
def put(self, key, dat): def put(self, key, dat):
@ -99,6 +101,9 @@ cdef class Params:
cdef string key_bytes = ensure_bytes(key) cdef string key_bytes = ensure_bytes(key)
return self.p.getParamPath(key_bytes).decode("utf-8") return self.p.getParamPath(key_bytes).decode("utf-8")
def all_keys(self):
return self.p.allKeys()
def put_nonblocking(key, val, d=""): def put_nonblocking(key, val, d=""):
threading.Thread(target=lambda: Params(d).put(key, val)).start() threading.Thread(target=lambda: Params(d).put(key, val)).start()

@ -31,7 +31,7 @@ class Priority:
def set_realtime_priority(level: int) -> None: def set_realtime_priority(level: int) -> None:
if not PC: if not PC:
os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # type: ignore[attr-defined] # pylint: disable=no-member os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) # pylint: disable=no-member
def set_core_affinity(cores: List[int]) -> None: def set_core_affinity(cores: List[int]) -> None:

@ -98,6 +98,14 @@ class TestParams(unittest.TestCase):
assert q.get("CarParams") is None assert q.get("CarParams") is None
assert q.get("CarParams", True) == b"1" assert q.get("CarParams", True) == b"1"
def test_params_all_keys(self):
keys = Params().all_keys()
# sanity checks
assert len(keys) > 20
assert len(keys) == len(set(keys))
assert b"CarParams" in keys
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -143,3 +143,20 @@ TEST_CASE("util::create_directories") {
REQUIRE(util::create_directories("", 0755) == false); REQUIRE(util::create_directories("", 0755) == false);
} }
} }
TEST_CASE("util::remove_files_in_dir") {
std::string tmp_dir = "/tmp/test_remove_all_in_dir";
system("rm /tmp/test_remove_all_in_dir -rf");
REQUIRE(util::create_directories(tmp_dir, 0755));
const int tmp_file_cnt = 10;
for (int i = 0; i < tmp_file_cnt; ++i) {
std::string tmp_file = tmp_dir + "/test_XXXXXX";
int fd = mkstemp((char*)tmp_file.c_str());
close(fd);
REQUIRE(util::file_exists(tmp_file.c_str()));
}
REQUIRE(util::read_files_in_dir(tmp_dir).size() == tmp_file_cnt);
util::remove_files_in_dir(tmp_dir);
REQUIRE(util::read_files_in_dir(tmp_dir).empty());
}

@ -1,5 +1,6 @@
#include "common/util.h" #include "common/util.h"
#include <sys/ioctl.h>
#include <sys/stat.h> #include <sys/stat.h>
#include <dirent.h> #include <dirent.h>
@ -97,6 +98,22 @@ std::map<std::string, std::string> read_files_in_dir(const std::string &path) {
return ret; return ret;
} }
void remove_files_in_dir(const std::string &path) {
DIR *d = opendir(path.c_str());
if (!d) return;
std::string fn;
struct dirent *de = NULL;
while ((de = readdir(d))) {
if (de->d_type != DT_DIR) {
fn = path + "/" + de->d_name;
unlink(fn.c_str());
}
}
closedir(d);
}
int write_file(const char* path, const void* data, size_t size, int flags, mode_t mode) { int write_file(const char* path, const void* data, size_t size, int flags, mode_t mode) {
int fd = HANDLE_EINTR(open(path, flags, mode)); int fd = HANDLE_EINTR(open(path, flags, mode));
if (fd == -1) { if (fd == -1) {
@ -133,6 +150,14 @@ int safe_fflush(FILE *stream) {
return ret; return ret;
} }
int safe_ioctl(int fd, unsigned long request, void *argp) {
int ret;
do {
ret = ioctl(fd, request, argp);
} while ((ret == -1) && (errno == EINTR));
return ret;
}
std::string readlink(const std::string &path) { std::string readlink(const std::string &path) {
char buff[4096]; char buff[4096];
ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1); ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1);

@ -80,11 +80,13 @@ std::string dir_name(std::string const& path);
// **** file fhelpers ***** // **** file fhelpers *****
std::string read_file(const std::string& fn); std::string read_file(const std::string& fn);
std::map<std::string, std::string> read_files_in_dir(const std::string& path); std::map<std::string, std::string> read_files_in_dir(const std::string& path);
void remove_files_in_dir(const std::string& path);
int write_file(const char* path, const void* data, size_t size, int flags = O_WRONLY, mode_t mode = 0664); int write_file(const char* path, const void* data, size_t size, int flags = O_WRONLY, mode_t mode = 0664);
FILE* safe_fopen(const char* filename, const char* mode); FILE* safe_fopen(const char* filename, const char* mode);
size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream); size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream);
int safe_fflush(FILE *stream); int safe_fflush(FILE *stream);
int safe_ioctl(int fd, unsigned long request, void *argp);
std::string readlink(const std::string& path); std::string readlink(const std::string& path);
bool file_exists(const std::string& fn); bool file_exists(const std::string& fn);

@ -1 +1 @@
#define COMMA_VERSION "0.8.17" #define COMMA_VERSION "0.9.1"

@ -4,127 +4,137 @@
A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system.
# 205 Supported Cars # 215 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Acura|RDX 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Acura|RDX 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Audi|A3 2014-19|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Audi|A3 Sportback e-tron 2017-18|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Audi|Q2 2018|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Audi|Q3 2020-21|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Audi|RS3 2018|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Audi|S3 2015-17|ACC + Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Cadillac|Escalade ESV 2016[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Chevrolet|Volt 2017-18[<sup>1</sup>](#footnotes)|Adaptive Cruise Control|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Chevrolet|Volt 2017-18[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|Chrysler|Pacifica 2017-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica 2019-20|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|
|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G70 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Genesis|G70 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Genesis|G90 2017-18|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|GMC|Acadia 2018[<sup>1</sup>](#footnotes)|Adaptive Cruise Control|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| |Genesis|GV70 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| |GMC|Acadia 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|
|Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[<sup>1</sup>](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|
|Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Accord 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Accord Hybrid 2018-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Civic 2019-21|All|openpilot|0 mph|2 mph[<sup>2</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Civic 2019-21|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|2 mph[<sup>4</sup>](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| |Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|
|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| |Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|
|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|CR-V 2017-22|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|CR-V 2017-22|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[<sup>1</sup>](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|e 2020|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|e 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Insight 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Insight 2019-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Inspire 2018|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| |Honda|Inspire 2018|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|
|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| |Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|
|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC) & LKAS|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|
|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Hyundai|Elantra Hybrid 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| |Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC) & LKAS|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| |Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Hyundai|Ioniq 5 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| |Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|
|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|i30 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Hyundai|Ioniq Electric 2020|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq 5 (with HDA II) 2022-23|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|
|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq 5 (without HDA II) 2022-23|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC) & LFA|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Hyundai|Ioniq Plug-in Hybrid 2020-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|
|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O| |Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O|
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I|
|Hyundai|Palisade 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Hyundai|Santa Fe 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D| |Hyundai|Santa Cruz 2021-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Hyundai|Santa Fe 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Santa Fe 2019-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|
|Hyundai|Santa Fe Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Santa Fe 2021-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Santa Fe Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Sonata 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Hyundai|Sonata Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Hyundai|Sonata 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Sonata Hybrid 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Hyundai|Tucson Hybrid 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|
|Kia|Ceed 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Kia|EV6 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| |Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|
|Kia|Forte 2018|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Kia|EV6 (without HDA II) 2022|Highway Driving Assist|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|
|Kia|Forte 2019-21|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|
|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|
|Kia|Niro Electric 2019|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Kia|Niro EV 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Kia|Niro Electric 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Kia|Niro EV 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Kia|Niro Electric 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Niro EV 2021|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Kia|Niro Electric 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Kia|Niro EV 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| |Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|
|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC) & LKAS|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Kia|Niro Plug-in Hybrid 2018-19|Smart Cruise Control (SCC) & LKAS|openpilot|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[<sup>1</sup>](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Kia|Optima 2017|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|
|Kia|Optima 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| |Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|
|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| |Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|
|Kia|Sorento 2018|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Kia|Sorento 2019|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| |Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|
|Kia|Stinger 2018-20|Smart Cruise Control (SCC) & LKAS|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| |Kia|Sportage 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Kia|Telluride 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| |Kia|Sportage Hybrid 2023|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|
|Kia|Stinger 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|
|Kia|Telluride 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|
|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|ES Hybrid 2017-18|Lexus Safety System+|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|ES Hybrid 2017-18|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|NX 2018-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|NX 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|NX Hybrid 2018-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|NX Hybrid 2018-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|RX 2016-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|RX 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|RX Hybrid 2016-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|RX Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Mazda|CX-5 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| |Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|
|Mazda|CX-9 2021-22|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| |Mazda|CX-9 2021-22|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|
|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B| |Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B|
|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| |Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|
|Ram|1500 2019-22|Adaptive Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram| |Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|
|SEAT|Ateca 2018|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|SEAT|Leon 2014-20|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
@ -135,94 +145,95 @@ A supported vehicle is one that just works when you install a comma three. All s
|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| |Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|
|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| |Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|
|Škoda|Kamiq 2021[<sup>5</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Kamiq 2021[<sup>6</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Škoda|Karoq 2019-21[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Kodiaq 2018-19|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Octavia 2015, 2018-19|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Octavia RS 2016|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Škoda|Scala 2020|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Škoda|Superb 2015-18|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Avalon 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Avalon 2017-18|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Avalon 2019-21|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon 2019-21|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Avalon Hybrid 2019-21|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Avalon Hybrid 2019-21|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Camry 2018-20|All|Stock|0 mph[<sup>4</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Camry 2018-20|All|Stock|0 mph[<sup>5</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Camry 2021-22|All|openpilot|0 mph[<sup>4</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Camry 2021-22|All|openpilot|0 mph[<sup>5</sup>](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla 2017-19|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Corolla 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Cross (Non-US only) 2020-21|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander 2017-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Highlander 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander Hybrid 2017-19|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2017-19|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Prius 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Prius 2017-20|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius 2017-20|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Prius Prime 2017-20|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius Prime 2017-20|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|Prius v 2017|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|RAV4 2017-18|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 Hybrid 2017-18|All|Stock[<sup>3</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|
|Toyota|Sienna 2018-20|All|Stock[<sup>3</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| |Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|
|Volkswagen|Arteon 2018-22[<sup>7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Arteon eHybrid 2020-22[<sup>7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Arteon R 2020-22[<sup>7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Atlas 2018-23[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Atlas Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|California 2021[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Caravelle 2020[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|CC 2018-22[<sup>7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|e-Golf 2014-20|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf 2015-20[<sup>8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf Alltrack 2015-19|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf GTD 2015-20|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf GTE 2015-20|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf GTI 2015-21|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf R 2015-19[<sup>8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Golf SportsVan 2015-20|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Jetta 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Jetta GLI 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat 2015-22[<sup>6,7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat 2015-22[<sup>7</sup>](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat Alltrack 2015-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Passat GTE 2015-22[<sup>7,8</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Polo 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Volkswagen|Polo GTI 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Volkswagen|T-Cross 2021[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Volkswagen|T-Roc 2021[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[<sup>9</sup>](#footnotes)|
|Volkswagen|Taos 2022[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Teramont X 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Tiguan 2019-22[<sup>7</sup>](#footnotes)|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| |Volkswagen|Tiguan 2019-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
|Volkswagen|Touran 2017|Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| |Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,8</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|
<a id="footnotes"></a> <a id="footnotes"></a>
<sup>1</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br /> <sup>1</sup>Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. <br />
<sup>2</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br /> <sup>2</sup>By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. <b><i>NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b> <br />
<sup>3</sup>When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). <b><i>NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b> <br /> <sup>3</sup>Requires a <a href="https://github.com/commaai/openpilot/wiki/GM#hardware">community built ASCM harness</a>. <b><i>NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).</i></b> <br />
<sup>4</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br /> <sup>4</sup>2019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph. <br />
<sup>5</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br /> <sup>5</sup>openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control. <br />
<sup>6</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br /> <sup>6</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
<sup>7</sup>Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway inside the dashboard, choose "VW J533 Development" from the vehicle drop-down for a suitable harness. (Some newer models are also observed to not have a J533 connector.) <br /> <sup>7</sup>Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets. <br />
<sup>8</sup>Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.) <br /> <sup>8</sup>Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC. <br />
<sup>9</sup>Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store. <br />
## Community Maintained Cars ## Community Maintained Cars
Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/).

@ -4,16 +4,11 @@ ENV PYTHONUNBUFFERED 1
ENV OPENPILOT_PATH /home/batman/openpilot/ ENV OPENPILOT_PATH /home/batman/openpilot/
ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH}
ENV POETRY_VIRUALENVS_CREATE false
RUN mkdir -p ${OPENPILOT_PATH} RUN mkdir -p ${OPENPILOT_PATH}
WORKDIR ${OPENPILOT_PATH} WORKDIR ${OPENPILOT_PATH}
COPY Pipfile Pipfile.lock $OPENPILOT_PATH
RUN pip install --no-cache-dir pipenv==2021.5.29 pip==21.3.1 && \
pipenv install --system --deploy --dev --clear && \
pip uninstall -y pipenv
COPY SConstruct ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH}
COPY ./pyextra ${OPENPILOT_PATH}/pyextra COPY ./pyextra ${OPENPILOT_PATH}/pyextra

@ -1 +1 @@
Subproject commit c8bc1fa01be9f22592efb991ee52d3d965d21968 Subproject commit e1049cde0a68f7d4a70b1ebd76befdc0e163ad55

@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1
export VECLIB_MAXIMUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1
if [ -z "$AGNOS_VERSION" ]; then if [ -z "$AGNOS_VERSION" ]; then
export AGNOS_VERSION="5.2" export AGNOS_VERSION="6.2"
fi fi
if [ -z "$PASSIVE" ]; then if [ -z "$PASSIVE" ]; then

@ -1,4 +1,16 @@
[mypy] [mypy]
python_version = 3.8 python_version = 3.8
ignore_missing_imports = True
plugins = numpy.typing.mypy_plugin plugins = numpy.typing.mypy_plugin
files = body, common, docs, scripts, selfdrive, site_scons, system, tools
exclude = ^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)
; third-party packages
ignore_missing_imports = True
; helpful warnings
warn_redundant_casts = True
warn_unreachable = True
warn_unused_ignores = True
; restrict dynamic typing
warn_return_any = True

@ -1 +1 @@
Subproject commit e95ed311c10547026143b539a33341425cbec9ea Subproject commit 871e054d9a94629d92c22fe89cae71af5b0d3823

@ -1 +1 @@
Subproject commit 88c399b96e1a5248b09f6df1c16b3585be82cb5b Subproject commit e4c4253964a25ff980520b70ea9f50aede4a1db6

7989
poetry.lock generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,178 @@
[tool.poetry]
name = "openpilot"
version = "0.1.0"
description = "an open source driver assistance system"
authors = ["Vehicle Researcher <user@comma.ai>"]
license = "MIT"
readme = "README.md"
repository = "https://github.com/commaai/openpilot"
documentation = "https://docs.comma.ai"
[tool.poetry.dependencies]
python = "~3.8"
atomicwrites = "^1.4.0"
casadi = { version = "==3.5.5", markers = "platform_system != 'Darwin'" }
cffi = "^1.15.1"
crcmod = "^1.7"
cryptography = "^37.0.4"
Cython = "^0.29.30"
flake8 = "^4.0.1"
Flask = "^2.1.2"
future-fstrings = "^1.2.0" # for acados
gunicorn = "^20.1.0"
hatanaka = "==2.4"
hexdump = "^3.3"
Jinja2 = "^3.1.2"
json-rpc = "^1.13.0"
libusb1 = "^3.0.0"
nose = "^1.3.7"
numpy = "^1.23.0"
onnx = "^1.12.0"
onnxruntime-gpu = { version = "^1.11.1", markers = "platform_system != 'Darwin'" }
pillow = "^9.2.0"
poetry = "==1.2.2"
protobuf = "==3.20.1"
psutil = "^5.9.1"
pycapnp = "==1.1.0"
pycryptodome = "^3.15.0"
PyJWT = "^2.5.0"
pylint = "^2.15.4"
pyopencl = "^2022.2.4"
pyserial = "^3.5"
python-dateutil = "^2.8.2"
PyYAML = "^6.0"
pyzmq = "^23.2.0"
requests = "^2.28.1"
scons = "^4.3.0"
sentry-sdk = "^1.6.0"
setproctitle = "^1.2.3"
six = "^1.16.0"
smbus2 = "^0.4.2"
sympy = "^1.10.1"
timezonefinder = "^6.0.1"
tqdm = "^4.64.0"
urllib3 = "^1.26.10"
utm = "^0.7.0"
websocket_client = "^1.3.3"
spidev = "^3.6"
[tool.poetry.group.dev.dependencies]
av = "^9.2.0"
azure-storage-blob = "~2.1"
breathe = "^4.34.0"
carla = "==0.9.13"
control = "^0.9.2"
coverage = "^6.4.1"
dictdiffer = "^0.9.0"
fastcluster = "^1.2.6"
ft4222 = "^1.4.1"
hexdump = "^3.3"
hypothesis = "==6.46.7"
inputs = "^0.5"
lru-dict = "^1.1.7"
lxml = "^4.9.1"
markdown-it-py = "^2.1.0"
matplotlib = "^3.5.2"
mpld3 = "^0.5.8"
mypy = "^0.961"
myst-parser = "^0.18.0"
natsort = "^8.1.0"
numpy = "^1.23.0"
opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" }
pandas = "^1.4.3"
parameterized = "^0.8.1"
paramiko = "^2.11.0"
pprofile = "^2.1.0"
pre-commit = "^2.19.0"
pycurl = "^7.45.1"
pygame = "^2.1.2"
pyprof2calltree = "^1.4.5"
pytest = "^7.1.2"
pytest-xdist = "^2.5.0"
reverse_geocoder = "^1.5.1"
scipy = "^1.8.1"
sphinx = "^5.0.2"
sphinx-rtd-theme = "^1.0.0"
sphinx-sitemap = "^2.2.0"
subprocess32 = "^3.5.4"
tabulate = "^0.8.10"
tenacity = "^8.0.1"
types-atomicwrites = "^1.4.5"
types-certifi = "^2021.10.8"
types-pycurl = "^7.45.1"
types-PyYAML = "^6.0"
types-requests = "^2.28.11"
[tool.poetry.group.xx]
optional = true
[tool.poetry.group.xx.dependencies]
aenum = "^3.1.11"
aiohttp = "^3.8.1"
albumentations = "^1.2.1"
apex = { url = "https://github.com/commaai/apex/releases/download/pytorch1.10.0%2Bcu11.1/apex-0.1-cp38-cp38-linux_x86_64.whl" }
azure-cli-core = "^2.38.0"
azure-common = "^1.1.28"
azure-core = "^1.24.2"
azure-nspkg = "~3.0"
azure-storage-blob = "~2.1"
azure-storage-common = "~2.1"
azure-storage-nspkg = "~3.1"
blosc = "==1.9.2"
cloudpickle = "^2.1.0"
configargparse = "^1.5.3"
cupy-cuda113 = "^10.6.0"
datadog = "^0.44.0"
dotmap = "^1.3.30"
einops = "^0.5.0"
elasticsearch = "^8.3.1"
Flask-Cors = "^3.0.10"
Flask-SocketIO = "^5.2.0"
GeoAlchemy2 = "^0.12.1"
imageio = "^2.19.5"
influxdb-client = "^1.30.0"
ipykernel = "^6.15.1"
ipython = "^8.4.0"
joblib = "^1.1.0"
json-logging-py = "^0.2"
jupyter = "^1.0.0"
jupyterlab = "^3.4.4"
jupyterlab-vim = "^0.15.1"
Markdown = "^3.4.1"
mpld3 = "^0.5.8"
msgpack-python = "^0.5.6"
networkx = "~2.3"
nvidia-ml-py3 = "^7.352.0"
onnxoptimizer = "^0.3.1"
opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu113/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" }
osmium = "^3.3.0"
pandas = "^1.4.3"
pillow-avif-plugin = "^1.2.2"
pipenv = "==2022.10.12"
plotly = "^5.9.0"
pycuda = "^2022.1"
Pygments = "^2.12.0"
PyMySQL = "~0.9"
pyproj = "^3.3.1"
python-logstash = "^0.4.8"
redis = "^4.3.4"
s2sphere = "^0.2.5"
scikit-image = "^0.19.3"
scikit-learn = "^1.1.1"
segmentation-models-pytorch = "==0.2.1"
simplejson = "^3.17.6"
SQLAlchemy = "^1.4.39"
torch = { url = "https://download.pytorch.org/whl/cu113/torch-1.11.0%2Bcu113-cp38-cp38-linux_x86_64.whl" }
torchsummary = "^1.5.1"
torchvision = { url = "https://download.pytorch.org/whl/cu113/torchvision-0.12.0%2Bcu113-cp38-cp38-linux_x86_64.whl" }
triton = "^1.1.1"
Werkzeug = "^2.1.2"
zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" }
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

@ -12,10 +12,7 @@ fi
# set git identity # set git identity
source $DIR/identity.sh source $DIR/identity.sh
echo "[-] Setting up repo T=$SECONDS" echo "[-] Setting up target repo T=$SECONDS"
cd $SOURCE_DIR
git fetch origin
rm -rf $TARGET_DIR rm -rf $TARGET_DIR
mkdir -p $TARGET_DIR mkdir -p $TARGET_DIR
@ -25,14 +22,14 @@ pre-commit uninstall || true
echo "[-] bringing master-ci and devel in sync T=$SECONDS" echo "[-] bringing master-ci and devel in sync T=$SECONDS"
cd $TARGET_DIR cd $TARGET_DIR
git fetch origin master-ci git fetch --depth 1 origin master-ci
git fetch origin devel git fetch --depth 1 origin devel
git checkout -f --track origin/master-ci git checkout -f --track origin/master-ci
git reset --hard master-ci git reset --hard master-ci
git checkout master-ci git checkout master-ci
git reset --hard origin/devel git reset --hard origin/devel
git clean -xdf git clean -xdff
git lfs uninstall git lfs uninstall
# remove everything except .git # remove everything except .git
@ -41,7 +38,7 @@ find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -
# reset source tree # reset source tree
cd $SOURCE_DIR cd $SOURCE_DIR
git clean -xdf git clean -xdff
# do the files copy # do the files copy
echo "[-] copying files T=$SECONDS" echo "[-] copying files T=$SECONDS"

@ -90,8 +90,12 @@ selfdrive/boardd/boardd_api_impl.pyx
selfdrive/boardd/can_list_to_can_capnp.cc selfdrive/boardd/can_list_to_can_capnp.cc
selfdrive/boardd/panda.cc selfdrive/boardd/panda.cc
selfdrive/boardd/panda.h selfdrive/boardd/panda.h
selfdrive/boardd/spi.cc
selfdrive/boardd/panda_comms.h
selfdrive/boardd/panda_comms.cc
selfdrive/boardd/set_time.py selfdrive/boardd/set_time.py
selfdrive/boardd/pandad.py selfdrive/boardd/pandad.py
selfdrive/boardd/tests/test_boardd_loopback.py
selfdrive/car/__init__.py selfdrive/car/__init__.py
selfdrive/car/docs_definitions.py selfdrive/car/docs_definitions.py
@ -240,6 +244,7 @@ selfdrive/locationd/models/live_kf.cc
selfdrive/locationd/models/constants.py selfdrive/locationd/models/constants.py
selfdrive/locationd/models/gnss_helpers.py selfdrive/locationd/models/gnss_helpers.py
selfdrive/locationd/torqued.py
selfdrive/locationd/calibrationd.py selfdrive/locationd/calibrationd.py
system/logcatd/.gitignore system/logcatd/.gitignore
@ -304,6 +309,8 @@ selfdrive/ui/soundd/soundd
selfdrive/ui/soundd/.gitignore selfdrive/ui/soundd/.gitignore
selfdrive/ui/translations/*.ts selfdrive/ui/translations/*.ts
selfdrive/ui/translations/languages.json selfdrive/ui/translations/languages.json
selfdrive/ui/update_translations.py
selfdrive/ui/tests/test_translations.py
selfdrive/ui/qt/*.cc selfdrive/ui/qt/*.cc
selfdrive/ui/qt/*.h selfdrive/ui/qt/*.h
@ -534,7 +541,7 @@ opendbc/honda_civic_ex_2022_can_generated.dbc
opendbc/hyundai_canfd.dbc opendbc/hyundai_canfd.dbc
opendbc/hyundai_kia_generic.dbc opendbc/hyundai_kia_generic.dbc
opendbc/hyundai_kia_mando_front_radar.dbc opendbc/hyundai_kia_mando_front_radar_generated.dbc
opendbc/mazda_2017.dbc opendbc/mazda_2017.dbc
@ -562,6 +569,7 @@ opendbc/tesla_powertrain.dbc
tinygrad_repo/openpilot/compile.py tinygrad_repo/openpilot/compile.py
tinygrad_repo/accel/opencl/* tinygrad_repo/accel/opencl/*
tinygrad_repo/extra/onnx.py tinygrad_repo/extra/onnx.py
tinygrad_repo/extra/thneed.py
tinygrad_repo/extra/utils.py tinygrad_repo/extra/utils.py
tinygrad_repo/tinygrad/llops/ops_gpu.py tinygrad_repo/tinygrad/llops/ops_gpu.py
tinygrad_repo/tinygrad/llops/ops_opencl.py tinygrad_repo/tinygrad/llops/ops_opencl.py
@ -571,3 +579,4 @@ tinygrad_repo/tinygrad/ops.py
tinygrad_repo/tinygrad/shapetracker.py tinygrad_repo/tinygrad/shapetracker.py
tinygrad_repo/tinygrad/tensor.py tinygrad_repo/tinygrad/tensor.py
tinygrad_repo/tinygrad/nn/__init__.py tinygrad_repo/tinygrad/nn/__init__.py
tinygrad_repo/tinygrad/nn/optim.py

@ -3,4 +3,5 @@
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
export FINGERPRINT="TOYOTA COROLLA TSS2 2019" export FINGERPRINT="TOYOTA COROLLA TSS2 2019"
export SKIP_FW_QUERY="1"
$DIR/../launch_openpilot.sh $DIR/../launch_openpilot.sh

@ -1,16 +0,0 @@
#!/usr/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
cd $DIR/..
git clean -xdf .
git rm -r --cached .
git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*"
git fetch origin master
git checkout master
git reset --hard
git submodule update --init
printf '\n\n'
echo "master checked out. reboot to start building openpilot master"

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<svg viewBox="0 0 300 300" xmlns="http://www.w3.org/2000/svg">
<path d="M 278.364 122.673 C 282.129 126.545 284.418 132.064 284.5 137.465 L 284.5 200.562 C 284.419 205.963 282.129 211.48 278.364 215.352 C 274.492 219.119 268.972 221.407 263.571 221.488 L 259.262 221.488 L 259.262 238.417 C 259.128 241.171 257.425 244.12 255.107 245.614 C 252.655 246.875 249.249 246.874 246.796 245.613 C 244.48 244.12 242.777 241.171 242.643 238.417 L 242.643 221.488 L 57.357 221.488 L 57.357 238.417 C 57.223 241.171 55.519 244.121 53.203 245.614 C 50.75 246.875 47.344 246.875 44.892 245.614 C 42.575 244.12 40.872 241.171 40.738 238.417 L 40.738 221.488 L 36.427 221.488 C 31.027 221.407 25.508 219.119 21.636 215.352 C 17.871 211.48 15.582 205.961 15.5 200.56 L 15.5 137.463 C 15.581 132.062 17.871 126.545 21.636 122.673 C 25.508 118.906 31.029 116.618 36.429 116.536 L 40.738 116.536 L 40.738 87.024 C 40.426 78.719 43.434 70.271 48.95 64.028 C 54.579 57.887 62.818 53.986 71.131 53.441 L 228.982 53.444 C 237.295 53.989 245.421 57.888 251.05 64.028 C 256.566 70.272 259.574 78.719 259.262 87.024 L 259.262 116.536 L 263.573 116.536 C 268.974 116.618 274.492 118.906 278.364 122.673 Z M 60.911 75.581 C 58.147 78.782 56.932 82.617 57.35 86.825 L 57.357 118.356 C 60.791 119.893 63.643 122.022 66.115 125.423 C 68.512 128.877 69.916 133.256 69.976 137.46 L 69.976 167.014 L 230.024 167.014 L 230.024 137.466 C 230.084 133.262 231.488 128.877 233.884 125.423 C 236.357 122.021 239.208 119.892 242.643 118.356 L 242.643 86.988 C 243.06 82.779 241.852 78.782 239.089 75.581 C 236.438 72.283 232.951 70.432 228.785 70.059 L 71.215 70.059 C 67.049 70.433 63.563 72.283 60.911 75.581 Z M 33.383 203.605 C 34.241 204.572 35.14 204.944 36.431 204.87 L 53.357 204.87 L 53.357 137.465 C 53.432 136.174 53.06 135.278 52.093 134.42 C 51.236 133.454 50.336 133.08 49.046 133.155 L 36.429 133.155 C 35.139 133.08 34.295 133.506 33.382 134.419 C 32.47 135.332 32.044 136.176 32.119 137.466 L 32.119 200.56 C 32.044 201.851 32.416 202.747 33.383 203.605 Z M 69.976 204.87 L 230.024 204.87 L 230.024 183.631 L 69.976 183.631 L 69.976 204.87 Z M 267.881 137.465 C 267.956 136.174 267.584 135.278 266.618 134.419 C 265.758 133.453 264.86 133.08 263.57 133.155 L 250.953 133.155 C 249.663 133.08 248.766 133.452 247.908 134.419 C 246.941 135.277 246.568 136.176 246.643 137.466 L 246.643 204.87 L 263.571 204.87 C 264.862 204.944 265.759 204.572 266.616 203.606 C 267.583 202.748 267.956 201.849 267.881 200.559 L 267.881 137.465 Z" fill="black" style="stroke-width: 4px; vector-effect: non-scaling-stroke; fill-opacity: 0.4;"/>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

@ -0,0 +1,10 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M182.019 181.218C123.429 239.83 67.543 258.769 54.9241 245.243C51.3182 241.636 51.3182 236.226 51.3182 231.717C51.3182 207.37 74.7548 160.481 124.331 114.489C129.74 109.98 129.74 101.864 125.233 96.454C120.725 91.0438 112.612 91.0438 107.204 95.5525C95.4866 106.373 85.5713 117.194 76.5569 128.016C51.3181 89.242 45.9096 61.2866 54.0213 52.2701C63.9367 42.351 100.894 50.4672 144.162 82.9291C157.683 92.8482 171.203 104.57 183.823 117.196C193.738 127.115 203.654 137.936 211.767 148.757C202.754 160.48 192.838 170.399 182.021 181.22L182.019 181.218ZM246.019 54.0753C255.033 63.0928 248.724 91.9498 225.287 128.918C217.174 118.999 209.062 109.982 200.048 100.063C191.034 91.0451 181.119 82.0274 171.204 73.9125C209.064 49.5654 237.006 45.0578 246.019 54.0753ZM264.048 36.04C243.316 15.3002 200.047 24.318 150.471 58.5842C100.894 24.317 56.7244 15.3005 35.9947 36.04C15.2627 56.7799 24.2771 99.1619 59.4313 149.66C38.6993 179.417 26.0793 208.272 26.0793 231.718C26.0793 247.949 32.3888 257.868 36.896 263.278C45.0091 271.394 54.9244 275 67.5433 275C105.403 275 158.583 241.635 200.049 200.157C209.063 191.14 218.077 181.22 225.288 171.301C249.625 209.176 255.034 237.128 246.02 246.144C238.809 253.357 218.076 250.653 193.739 238.028C187.43 235.322 180.218 237.127 176.613 243.438C173.008 249.75 175.712 256.965 182.022 260.571C200.951 270.49 218.979 274.999 232.501 274.999C245.121 274.999 255.036 271.391 263.148 263.277C283.88 242.537 274.866 199.252 240.612 149.657C272.163 105.473 286.583 59.4852 264.05 36.0397L264.048 36.04Z" fill="url(#paint0_radial_1352_397)"/>
<path d="M175.414 154.169C175.414 171.6 161.29 185.729 143.866 185.729C126.441 185.729 112.317 171.6 112.317 154.169C112.317 136.738 126.441 122.609 143.866 122.609C161.29 122.609 175.414 136.738 175.414 154.169Z" fill="#E11C1C"/>
<defs>
<radialGradient id="paint0_radial_1352_397" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(150 150) rotate(90) scale(125)">
<stop offset="0.135417" stop-color="#E11C1C"/>
<stop offset="1" stop-color="#FF8C22"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,4 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M182.019 181.218C123.429 239.83 67.543 258.769 54.9241 245.243C51.3182 241.636 51.3182 236.226 51.3182 231.717C51.3182 207.37 74.7548 160.481 124.331 114.489C129.74 109.98 129.74 101.864 125.233 96.454C120.725 91.0438 112.612 91.0438 107.204 95.5525C95.4866 106.373 85.5713 117.194 76.5569 128.016C51.3181 89.242 45.9096 61.2866 54.0213 52.2701C63.9367 42.351 100.894 50.4672 144.162 82.9291C157.683 92.8482 171.203 104.57 183.823 117.196C193.738 127.115 203.654 137.936 211.767 148.757C202.754 160.48 192.838 170.399 182.021 181.22L182.019 181.218ZM246.019 54.0753C255.033 63.0928 248.724 91.9498 225.287 128.918C217.174 118.999 209.062 109.982 200.048 100.063C191.034 91.0451 181.119 82.0274 171.204 73.9125C209.064 49.5654 237.006 45.0578 246.019 54.0753ZM264.048 36.04C243.316 15.3002 200.047 24.318 150.471 58.5842C100.894 24.317 56.7244 15.3005 35.9947 36.04C15.2627 56.7799 24.2771 99.1619 59.4313 149.66C38.6993 179.417 26.0793 208.272 26.0793 231.718C26.0793 247.949 32.3888 257.868 36.896 263.278C45.0091 271.394 54.9244 275 67.5433 275C105.403 275 158.583 241.635 200.049 200.157C209.063 191.14 218.077 181.22 225.288 171.301C249.625 209.176 255.034 237.128 246.02 246.144C238.809 253.357 218.076 250.653 193.739 238.028C187.43 235.322 180.218 237.127 176.613 243.438C173.008 249.75 175.712 256.965 182.022 260.571C200.951 270.49 218.979 274.999 232.501 274.999C245.121 274.999 255.036 271.391 263.148 263.277C283.88 242.537 274.866 199.252 240.612 149.657C272.163 105.473 286.583 59.4852 264.05 36.0397L264.048 36.04Z" fill="black" fill-opacity="0.36"/>
<path d="M175.414 154.169C175.414 171.6 161.29 185.729 143.866 185.729C126.441 185.729 112.317 171.6 112.317 154.169C112.317 136.738 126.441 122.609 143.866 122.609C161.29 122.609 175.414 136.738 175.414 154.169Z" fill="black" fill-opacity="0.36"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1,4 @@
<svg width="300" height="300" viewBox="0 0 300 300" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M182.019 181.218C123.429 239.83 67.543 258.769 54.9241 245.243C51.3182 241.636 51.3182 236.226 51.3182 231.717C51.3182 207.37 74.7548 160.481 124.331 114.489C129.74 109.98 129.74 101.864 125.233 96.454C120.725 91.0438 112.612 91.0438 107.204 95.5525C95.4866 106.373 85.5713 117.194 76.5569 128.016C51.3181 89.242 45.9096 61.2866 54.0213 52.2701C63.9367 42.351 100.894 50.4672 144.162 82.9291C157.683 92.8482 171.203 104.57 183.823 117.196C193.738 127.115 203.654 137.936 211.767 148.757C202.754 160.48 192.838 170.399 182.021 181.22L182.019 181.218ZM246.019 54.0753C255.033 63.0928 248.724 91.9498 225.287 128.918C217.174 118.999 209.062 109.982 200.048 100.063C191.034 91.0451 181.119 82.0274 171.204 73.9125C209.064 49.5654 237.006 45.0578 246.019 54.0753ZM264.048 36.04C243.316 15.3002 200.047 24.318 150.471 58.5842C100.894 24.317 56.7244 15.3005 35.9947 36.04C15.2627 56.7799 24.2771 99.1619 59.4313 149.66C38.6993 179.417 26.0793 208.272 26.0793 231.718C26.0793 247.949 32.3888 257.868 36.896 263.278C45.0091 271.394 54.9244 275 67.5433 275C105.403 275 158.583 241.635 200.049 200.157C209.063 191.14 218.077 181.22 225.288 171.301C249.625 209.176 255.034 237.128 246.02 246.144C238.809 253.357 218.076 250.653 193.739 238.028C187.43 235.322 180.218 237.127 176.613 243.438C173.008 249.75 175.712 256.965 182.022 260.571C200.951 270.49 218.979 274.999 232.501 274.999C245.121 274.999 255.036 271.391 263.148 263.277C283.88 242.537 274.866 199.252 240.612 149.657C272.163 105.473 286.583 59.4852 264.05 36.0397L264.048 36.04Z" fill="white"/>
<path d="M175.414 154.169C175.414 171.6 161.29 185.729 143.866 185.729C126.441 185.729 112.317 171.6 112.317 154.169C112.317 136.738 126.441 122.609 143.866 122.609C161.29 122.609 175.414 136.738 175.414 154.169Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

@ -53,8 +53,8 @@ class MockParams():
default_params = { default_params = {
"DongleId": b"0000000000000000", "DongleId": b"0000000000000000",
"GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501
"GsmMetered": True,
"AthenadUploadQueue": '[]', "AthenadUploadQueue": '[]',
"CellularUnmetered": False,
} }
params = default_params.copy() params = default_params.copy()

@ -9,6 +9,7 @@ import threading
import queue import queue
import unittest import unittest
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Optional
from multiprocessing import Process from multiprocessing import Process
from pathlib import Path from pathlib import Path
@ -46,12 +47,26 @@ class TestAthenadMethods(unittest.TestCase):
else: else:
os.unlink(p) os.unlink(p)
def wait_for_upload(self):
# *** test helpers ***
@staticmethod
def _wait_for_upload():
now = time.time() now = time.time()
while time.time() - now < 5: while time.time() - now < 5:
if athenad.upload_queue.qsize() == 0: if athenad.upload_queue.qsize() == 0:
break break
@staticmethod
def _create_file(file: str, parent: Optional[str] = None) -> str:
fn = os.path.join(athenad.ROOT if parent is None else parent, file)
os.makedirs(os.path.dirname(fn), exist_ok=True)
Path(fn).touch()
return fn
# *** test cases ***
def test_echo(self): def test_echo(self):
assert dispatcher["echo"]("bob") == "bob" assert dispatcher["echo"]("bob") == "bob"
@ -85,9 +100,7 @@ class TestAthenadMethods(unittest.TestCase):
filenames = ['qlog', 'qcamera.ts', 'rlog', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc'] filenames = ['qlog', 'qcamera.ts', 'rlog', 'fcamera.hevc', 'ecamera.hevc', 'dcamera.hevc']
files = [f'{route}--{s}/{f}' for s in segments for f in filenames] files = [f'{route}--{s}/{f}' for s in segments for f in filenames]
for file in files: for file in files:
fn = os.path.join(athenad.ROOT, file) self._create_file(file)
os.makedirs(os.path.dirname(fn), exist_ok=True)
Path(fn).touch()
resp = dispatcher["listDataDirectory"]() resp = dispatcher["listDataDirectory"]()
self.assertTrue(resp, 'list empty!') self.assertTrue(resp, 'list empty!')
@ -121,16 +134,14 @@ class TestAthenadMethods(unittest.TestCase):
self.assertCountEqual(resp, expected) self.assertCountEqual(resp, expected)
def test_strip_bz2_extension(self): def test_strip_bz2_extension(self):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
if fn.endswith('.bz2'): if fn.endswith('.bz2'):
self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4]) self.assertEqual(athenad.strip_bz2_extension(fn), fn[:-4])
@with_http_server @with_http_server
def test_do_upload(self, host): def test_do_upload(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='') item = athenad.UploadItem(path=fn, url="http://localhost:1238", headers={}, created_at=int(time.time()*1000), id='')
with self.assertRaises(requests.exceptions.ConnectionError): with self.assertRaises(requests.exceptions.ConnectionError):
@ -142,8 +153,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server @with_http_server
def test_uploadFileToUrl(self, host): def test_uploadFileToUrl(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {}) resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {})
self.assertEqual(resp['enqueued'], 1) self.assertEqual(resp['enqueued'], 1)
@ -154,8 +164,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server @with_http_server
def test_uploadFileToUrl_duplicate(self, host): def test_uploadFileToUrl_duplicate(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') self._create_file('qlog.bz2')
Path(fn).touch()
url1 = f"{host}/qlog.bz2?sig=sig1" url1 = f"{host}/qlog.bz2?sig=sig1"
dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {}) dispatcher["uploadFileToUrl"]("qlog.bz2", url1, {})
@ -172,8 +181,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server @with_http_server
def test_upload_handler(self, host): def test_upload_handler(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event() end_event = threading.Event()
@ -182,7 +190,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item) athenad.upload_queue.put_nowait(item)
try: try:
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
# TODO: verify that upload actually succeeded # TODO: verify that upload actually succeeded
@ -195,8 +203,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_retry(self, host, mock_put): def test_upload_handler_retry(self, host, mock_put):
for status, retry in ((500, True), (412, False)): for status, retry in ((500, True), (412, False)):
mock_put.return_value.status_code = status mock_put.return_value.status_code = status
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event() end_event = threading.Event()
@ -205,7 +212,7 @@ class TestAthenadMethods(unittest.TestCase):
athenad.upload_queue.put_nowait(item) athenad.upload_queue.put_nowait(item)
try: try:
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0)
@ -217,8 +224,7 @@ class TestAthenadMethods(unittest.TestCase):
def test_upload_handler_timeout(self): def test_upload_handler_timeout(self):
"""When an upload times out or fails to connect it should be placed back in the queue""" """When an upload times out or fails to connect it should be placed back in the queue"""
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT) item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT)
@ -228,14 +234,14 @@ class TestAthenadMethods(unittest.TestCase):
try: try:
athenad.upload_queue.put_nowait(item_no_retry) athenad.upload_queue.put_nowait(item_no_retry)
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
# Check that upload with retry count exceeded is not put back # Check that upload with retry count exceeded is not put back
self.assertEqual(athenad.upload_queue.qsize(), 0) self.assertEqual(athenad.upload_queue.qsize(), 0)
athenad.upload_queue.put_nowait(item) athenad.upload_queue.put_nowait(item)
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
# Check that upload item was put back in the queue with incremented retry count # Check that upload item was put back in the queue with incremented retry count
@ -256,7 +262,7 @@ class TestAthenadMethods(unittest.TestCase):
thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) thread = threading.Thread(target=athenad.upload_handler, args=(end_event,))
thread.start() thread.start()
try: try:
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0) self.assertEqual(athenad.upload_queue.qsize(), 0)
@ -269,8 +275,7 @@ class TestAthenadMethods(unittest.TestCase):
ts = int(t_future.strftime("%s")) * 1000 ts = int(t_future.strftime("%s")) * 1000
# Item that would time out if actually uploaded # Item that would time out if actually uploaded
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True)
@ -279,7 +284,7 @@ class TestAthenadMethods(unittest.TestCase):
thread.start() thread.start()
try: try:
athenad.upload_queue.put_nowait(item) athenad.upload_queue.put_nowait(item)
self.wait_for_upload() self._wait_for_upload()
time.sleep(0.1) time.sleep(0.1)
self.assertEqual(athenad.upload_queue.qsize(), 0) self.assertEqual(athenad.upload_queue.qsize(), 0)
@ -292,8 +297,7 @@ class TestAthenadMethods(unittest.TestCase):
@with_http_server @with_http_server
def test_listUploadQueueCurrent(self, host): def test_listUploadQueueCurrent(self, host):
fn = os.path.join(athenad.ROOT, 'qlog.bz2') fn = self._create_file('qlog.bz2')
Path(fn).touch()
item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True)
end_event = threading.Event() end_event = threading.Event()
@ -302,7 +306,7 @@ class TestAthenadMethods(unittest.TestCase):
try: try:
athenad.upload_queue.put_nowait(item) athenad.upload_queue.put_nowait(item)
self.wait_for_upload() self._wait_for_upload()
items = dispatcher["listUploadQueue"]() items = dispatcher["listUploadQueue"]()
self.assertEqual(len(items), 1) self.assertEqual(len(items), 1)
@ -405,9 +409,9 @@ class TestAthenadMethods(unittest.TestCase):
def test_get_logs_to_send_sorted(self): def test_get_logs_to_send_sorted(self):
fl = list() fl = list()
for i in range(10): for i in range(10):
fn = os.path.join(swaglog.SWAGLOG_DIR, f'swaglog.{i:010}') file = f'swaglog.{i:010}'
Path(fn).touch() self._create_file(file, athenad.SWAGLOG_DIR)
fl.append(os.path.basename(fn)) fl.append(file)
# ensure the list is all logs except most recent # ensure the list is all logs except most recent
sl = athenad.get_logs_to_send_sorted() sl = athenad.get_logs_to_send_sorted()

@ -1,9 +1,9 @@
Import('env', 'envCython', 'common', 'cereal', 'messaging') Import('env', 'envCython', 'common', 'cereal', 'messaging')
libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj']
env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc'], LIBS=libs) env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)
env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc'])
envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"])
if GetOption('test'): if GetOption('test'):
env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs)

@ -19,8 +19,6 @@
#include <future> #include <future>
#include <thread> #include <thread>
#include <libusb-1.0/libusb.h>
#include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/car.capnp.h"
#include "cereal/messaging/messaging.h" #include "cereal/messaging/messaging.h"
#include "common/params.h" #include "common/params.h"
@ -56,7 +54,6 @@
using namespace std::chrono_literals; using namespace std::chrono_literals;
std::atomic<bool> ignition(false); std::atomic<bool> ignition(false);
std::atomic<bool> pigeon_active(false);
ExitHandler do_exit; ExitHandler do_exit;
@ -68,7 +65,7 @@ static std::string get_time_str(const struct tm &time) {
bool check_all_connected(const std::vector<Panda *> &pandas) { bool check_all_connected(const std::vector<Panda *> &pandas) {
for (const auto& panda : pandas) { for (const auto& panda : pandas) {
if (!panda->connected) { if (!panda->connected()) {
do_exit = true; do_exit = true;
return false; return false;
} }
@ -114,9 +111,9 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
} }
// set to ELM327 for fingerprinting // set to ELM327 for fingerprinting
pandas[0]->set_safety_model(cereal::CarParams::SafetyModel::ELM327); for (int i = 0; i < pandas.size(); i++) {
for (int i = 1; i < pandas.size(); i++) { const uint16_t safety_param = (i > 0) ? 1U : 0U;
pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::SILENT); pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param);
} }
Params p = Params(); Params p = Params();
@ -138,7 +135,9 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
} }
// set to ELM327 for ECU knockouts // set to ELM327 for ECU knockouts
pandas[0]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); for (Panda *panda : pandas) {
panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U);
}
std::string params; std::string params;
LOGW("waiting for params to set safety model"); LOGW("waiting for params to set safety model");
@ -183,7 +182,7 @@ bool safety_setter_thread(std::vector<Panda *> pandas) {
return true; return true;
} }
Panda *usb_connect(std::string serial="", uint32_t index=0) { Panda *connect(std::string serial="", uint32_t index=0) {
std::unique_ptr<Panda> panda; std::unique_ptr<Panda> panda;
try { try {
panda = std::make_unique<Panda>(serial, (index * PANDA_BUS_CNT)); panda = std::make_unique<Panda>(serial, (index * PANDA_BUS_CNT));
@ -226,9 +225,9 @@ void can_send_thread(std::vector<Panda *> pandas, bool fake_send) {
//Dont send if older than 1 second //Dont send if older than 1 second
if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) {
for (const auto& panda : pandas) { for (const auto& panda : pandas) {
LOGT("sending sendcan to panda: %s", (panda->usb_serial).c_str()); LOGT("sending sendcan to panda: %s", (panda->hw_serial).c_str());
panda->can_send(event.getSendcan()); panda->can_send(event.getSendcan());
LOGT("sendcan sent to panda: %s", (panda->usb_serial).c_str()); LOGT("sendcan sent to panda: %s", (panda->hw_serial).c_str());
} }
} }
} }
@ -295,13 +294,19 @@ void send_empty_panda_state(PubMaster *pm) {
std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *> &pandas, bool spoofing_started) { std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *> &pandas, bool spoofing_started) {
bool ignition_local = false; bool ignition_local = false;
const uint32_t pandas_cnt = pandas.size();
// build msg // build msg
MessageBuilder msg; MessageBuilder msg;
auto evt = msg.initEvent(); auto evt = msg.initEvent();
auto pss = evt.initPandaStates(pandas.size()); auto pss = evt.initPandaStates(pandas_cnt);
std::vector<health_t> pandaStates; std::vector<health_t> pandaStates;
pandaStates.reserve(pandas_cnt);
std::vector<std::array<can_health_t, PANDA_CAN_CNT>> pandaCanStates;
pandaCanStates.reserve(pandas_cnt);
for (const auto& panda : pandas){ for (const auto& panda : pandas){
auto health_opt = panda->get_state(); auto health_opt = panda->get_state();
if (!health_opt) { if (!health_opt) {
@ -310,6 +315,16 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
health_t health = *health_opt; health_t health = *health_opt;
std::array<can_health_t, PANDA_CAN_CNT> can_health{};
for (uint32_t i = 0; i < PANDA_CAN_CNT; i++) {
auto can_health_opt = panda->get_can_state(i);
if (!can_health_opt) {
return std::nullopt;
}
can_health[i] = *can_health_opt;
}
pandaCanStates.push_back(can_health);
if (spoofing_started) { if (spoofing_started) {
health.ignition_line_pkt = 1; health.ignition_line_pkt = 1;
} }
@ -319,7 +334,7 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
pandaStates.push_back(health); pandaStates.push_back(health);
} }
for (uint32_t i = 0; i < pandas.size(); i++) { for (uint32_t i = 0; i < pandas_cnt; i++) {
auto panda = pandas[i]; auto panda = pandas[i];
const auto &health = pandaStates[i]; const auto &health = pandaStates[i];
@ -329,7 +344,7 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
} }
#ifndef __x86_64__ #ifndef __x86_64__
bool power_save_desired = !ignition_local && !pigeon_active; bool power_save_desired = !ignition_local;
if (health.power_save_enabled_pkt != power_save_desired) { if (health.power_save_enabled_pkt != power_save_desired) {
panda->set_power_saving(power_save_desired); panda->set_power_saving(power_save_desired);
} }
@ -340,20 +355,20 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
} }
#endif #endif
if (!panda->comms_healthy) { if (!panda->comms_healthy()) {
evt.setValid(false); evt.setValid(false);
} }
auto ps = pss[i]; auto ps = pss[i];
ps.setUptime(health.uptime_pkt); ps.setUptime(health.uptime_pkt);
ps.setBlockedCnt(health.blocked_msg_cnt_pkt); ps.setSafetyTxBlocked(health.safety_tx_blocked_pkt);
ps.setSafetyRxInvalid(health.safety_rx_invalid_pkt);
ps.setIgnitionLine(health.ignition_line_pkt); ps.setIgnitionLine(health.ignition_line_pkt);
ps.setIgnitionCan(health.ignition_can_pkt); ps.setIgnitionCan(health.ignition_can_pkt);
ps.setControlsAllowed(health.controls_allowed_pkt); ps.setControlsAllowed(health.controls_allowed_pkt);
ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt); ps.setGasInterceptorDetected(health.gas_interceptor_detected_pkt);
ps.setCanRxErrs(health.can_rx_errs_pkt); ps.setTxBufferOverflow(health.tx_buffer_overflow_pkt);
ps.setCanSendErrs(health.can_send_errs_pkt); ps.setRxBufferOverflow(health.rx_buffer_overflow_pkt);
ps.setCanFwdErrs(health.can_fwd_errs_pkt);
ps.setGmlanSendErrs(health.gmlan_send_errs_pkt); ps.setGmlanSendErrs(health.gmlan_send_errs_pkt);
ps.setPandaType(panda->hw_type); ps.setPandaType(panda->hw_type);
ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt)); ps.setSafetyModel(cereal::CarParams::SafetyModel(health.safety_mode_pkt));
@ -365,6 +380,34 @@ std::optional<bool> send_panda_states(PubMaster *pm, const std::vector<Panda *>
ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt)); ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt));
ps.setInterruptLoad(health.interrupt_load); ps.setInterruptLoad(health.interrupt_load);
ps.setFanPower(health.fan_power); ps.setFanPower(health.fan_power);
ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid));
std::array<cereal::PandaState::PandaCanState::Builder, PANDA_CAN_CNT> cs = {ps.initCanState0(), ps.initCanState1(), ps.initCanState2()};
for (uint32_t j = 0; j < PANDA_CAN_CNT; j++) {
const auto &can_health = pandaCanStates[i][j];
cs[j].setBusOff((bool)can_health.bus_off);
cs[j].setBusOffCnt(can_health.bus_off_cnt);
cs[j].setErrorWarning((bool)can_health.error_warning);
cs[j].setErrorPassive((bool)can_health.error_passive);
cs[j].setLastError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_error));
cs[j].setLastStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_stored_error));
cs[j].setLastDataError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_error));
cs[j].setLastDataStoredError(cereal::PandaState::PandaCanState::LecErrorCode(can_health.last_data_stored_error));
cs[j].setReceiveErrorCnt(can_health.receive_error_cnt);
cs[j].setTransmitErrorCnt(can_health.transmit_error_cnt);
cs[j].setTotalErrorCnt(can_health.total_error_cnt);
cs[j].setTotalTxLostCnt(can_health.total_tx_lost_cnt);
cs[j].setTotalRxLostCnt(can_health.total_rx_lost_cnt);
cs[j].setTotalTxCnt(can_health.total_tx_cnt);
cs[j].setTotalRxCnt(can_health.total_rx_cnt);
cs[j].setTotalFwdCnt(can_health.total_fwd_cnt);
cs[j].setCanSpeed(can_health.can_speed);
cs[j].setCanDataSpeed(can_health.can_data_speed);
cs[j].setCanfdEnabled(can_health.canfd_enabled);
cs[j].setBrsEnabled(can_health.brs_enabled);
cs[j].setCanfdNonIso(can_health.canfd_non_iso);
}
// Convert faults bitset to capnp list // Convert faults bitset to capnp list
std::bitset<sizeof(health.faults_pkt) * 8> fault_bits(health.faults_pkt); std::bitset<sizeof(health.faults_pkt) * 8> fault_bits(health.faults_pkt);
@ -388,7 +431,7 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) {
// build msg // build msg
MessageBuilder msg; MessageBuilder msg;
auto evt = msg.initEvent(); auto evt = msg.initEvent();
evt.setValid(panda->comms_healthy); evt.setValid(panda->comms_healthy());
auto ps = evt.initPeripheralState(); auto ps = evt.initPeripheralState();
ps.setPandaType(panda->hw_type); ps.setPandaType(panda->hw_type);
@ -468,7 +511,7 @@ void panda_state_thread(PubMaster *pm, std::vector<Panda *> pandas, bool spoofin
} }
void peripheral_control_thread(Panda *panda) { void peripheral_control_thread(Panda *panda, bool no_fan_control) {
util::set_thread_name("boardd_peripheral_control"); util::set_thread_name("boardd_peripheral_control");
SubMaster sm({"deviceState", "driverCameraState"}); SubMaster sm({"deviceState", "driverCameraState"});
@ -481,14 +524,11 @@ void peripheral_control_thread(Panda *panda) {
FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); FirstOrderFilter integ_lines_filter(0, 30.0, 0.05);
while (!do_exit && panda->connected) { while (!do_exit && panda->connected()) {
cnt++; cnt++;
sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update?
// Other pandas don't have fan/IR to control if (sm.updated("deviceState") && !no_fan_control) {
if (panda->hw_type != cereal::PandaState::PandaType::UNO && panda->hw_type != cereal::PandaState::PandaType::DOS) continue;
if (sm.updated("deviceState")) {
// Fan speed // Fan speed
uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired(); uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired();
if (fan_speed != prev_fan_speed || cnt % 100 == 0) { if (fan_speed != prev_fan_speed || cnt % 100 == 0) {
@ -496,6 +536,7 @@ void peripheral_control_thread(Panda *panda) {
prev_fan_speed = fan_speed; prev_fan_speed = fan_speed;
} }
} }
if (sm.updated("driverCameraState")) { if (sm.updated("driverCameraState")) {
auto event = sm["driverCameraState"]; auto event = sm["driverCameraState"];
int cur_integ_lines = event.getDriverCameraState().getIntegLines(); int cur_integ_lines = event.getDriverCameraState().getIntegLines();
@ -549,7 +590,7 @@ void boardd_main_thread(std::vector<std::string> serials) {
// connect to all provided serials // connect to all provided serials
std::vector<Panda *> pandas; std::vector<Panda *> pandas;
for (int i = 0; i < serials.size() && !do_exit; /**/) { for (int i = 0; i < serials.size() && !do_exit; /**/) {
Panda *p = usb_connect(serials[i], i); Panda *p = connect(serials[i], i);
if (!p) { if (!p) {
// send empty pandaState & peripheralState and try again // send empty pandaState & peripheralState and try again
send_empty_panda_state(&pm); send_empty_panda_state(&pm);
@ -568,7 +609,7 @@ void boardd_main_thread(std::vector<std::string> serials) {
std::vector<std::thread> threads; std::vector<std::thread> threads;
threads.emplace_back(panda_state_thread, &pm, pandas, getenv("STARTED") != nullptr); threads.emplace_back(panda_state_thread, &pm, pandas, getenv("STARTED") != nullptr);
threads.emplace_back(peripheral_control_thread, peripheral_panda); threads.emplace_back(peripheral_control_thread, peripheral_panda, getenv("NO_FAN_CONTROL") != nullptr);
threads.emplace_back(can_send_thread, pandas, getenv("FAKESEND") != nullptr); threads.emplace_back(can_send_thread, pandas, getenv("FAKESEND") != nullptr);
threads.emplace_back(can_recv_thread, pandas); threads.emplace_back(can_recv_thread, pandas);

@ -4,273 +4,68 @@
#include <cassert> #include <cassert>
#include <stdexcept> #include <stdexcept>
#include <vector>
#include "cereal/messaging/messaging.h" #include "cereal/messaging/messaging.h"
#include "panda/board/dlc_to_len.h" #include "panda/board/dlc_to_len.h"
#include "common/gpio.h"
#include "common/swaglog.h" #include "common/swaglog.h"
#include "common/util.h" #include "common/util.h"
static int init_usb_ctx(libusb_context **context) {
assert(context != nullptr);
int err = libusb_init(context);
if (err != 0) {
LOGE("libusb initialization error");
return err;
}
#if LIBUSB_API_VERSION >= 0x01000106
libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
#else
libusb_set_debug(*context, 3);
#endif
return err;
}
Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) {
// init libusb // TODO: support SPI here one day...
ssize_t num_devices; if (serial.find("spi") != std::string::npos) {
libusb_device **dev_list = NULL; handle = std::make_unique<PandaSpiHandle>(serial);
int err = init_usb_ctx(&ctx); } else {
if (err != 0) { goto fail; } handle = std::make_unique<PandaUsbHandle>(serial);
// connect by serial
num_devices = libusb_get_device_list(ctx, &dev_list);
if (num_devices < 0) { goto fail; }
for (size_t i = 0; i < num_devices; ++i) {
libusb_device_descriptor desc;
libusb_get_device_descriptor(dev_list[i], &desc);
if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
int ret = libusb_open(dev_list[i], &dev_handle);
if (dev_handle == NULL || ret < 0) { goto fail; }
unsigned char desc_serial[26] = { 0 };
ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
if (ret < 0) { goto fail; }
usb_serial = std::string((char *)desc_serial, ret).c_str();
if (serial.empty() || serial == usb_serial) {
break;
}
libusb_close(dev_handle);
dev_handle = NULL;
}
}
if (dev_handle == NULL) goto fail;
libusb_free_device_list(dev_list, 1);
dev_list = nullptr;
if (libusb_kernel_driver_active(dev_handle, 0) == 1) {
libusb_detach_kernel_driver(dev_handle, 0);
} }
err = libusb_set_configuration(dev_handle, 1);
if (err != 0) { goto fail; }
err = libusb_claim_interface(dev_handle, 0);
if (err != 0) { goto fail; }
hw_type = get_hw_type(); hw_type = get_hw_type();
assert((hw_type != cereal::PandaState::PandaType::WHITE_PANDA) && assert((hw_type != cereal::PandaState::PandaType::WHITE_PANDA) &&
(hw_type != cereal::PandaState::PandaType::GREY_PANDA)); (hw_type != cereal::PandaState::PandaType::GREY_PANDA));
has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) || has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) ||
(hw_type == cereal::PandaState::PandaType::DOS); (hw_type == cereal::PandaState::PandaType::DOS) ||
(hw_type == cereal::PandaState::PandaType::TRES);
return; return;
fail:
if (dev_list != NULL) {
libusb_free_device_list(dev_list, 1);
}
cleanup();
throw std::runtime_error("Error connecting to panda");
} }
Panda::~Panda() { bool Panda::connected() {
std::lock_guard lk(usb_lock); return handle->connected;
cleanup();
connected = false;
} }
void Panda::cleanup() { bool Panda::comms_healthy() {
if (dev_handle) { return handle->comms_healthy;
libusb_release_interface(dev_handle, 0);
libusb_close(dev_handle);
}
if (ctx) {
libusb_exit(ctx);
}
} }
std::vector<std::string> Panda::list() { std::vector<std::string> Panda::list() {
// init libusb return PandaUsbHandle::list();
ssize_t num_devices;
libusb_context *context = NULL;
libusb_device **dev_list = NULL;
std::vector<std::string> serials;
int err = init_usb_ctx(&context);
if (err != 0) { return serials; }
num_devices = libusb_get_device_list(context, &dev_list);
if (num_devices < 0) {
LOGE("libusb can't get device list");
goto finish;
}
for (size_t i = 0; i < num_devices; ++i) {
libusb_device *device = dev_list[i];
libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
libusb_device_handle *handle = NULL;
int ret = libusb_open(device, &handle);
if (ret < 0) { goto finish; }
unsigned char desc_serial[26] = { 0 };
ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
libusb_close(handle);
if (ret < 0) { goto finish; }
serials.push_back(std::string((char *)desc_serial, ret).c_str());
}
}
finish:
if (dev_list != NULL) {
libusb_free_device_list(dev_list, 1);
}
if (context) {
libusb_exit(context);
}
return serials;
}
void Panda::handle_usb_issue(int err, const char func[]) {
LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func);
if (err == LIBUSB_ERROR_NO_DEVICE) {
LOGE("lost connection");
connected = false;
}
// TODO: check other errors, is simply retrying okay?
}
int Panda::usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) {
int err;
const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
if (!connected) {
return LIBUSB_ERROR_NO_DEVICE;
}
std::lock_guard lk(usb_lock);
do {
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout);
if (err < 0) handle_usb_issue(err, __func__);
} while (err < 0 && connected);
return err;
}
int Panda::usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) {
int err;
const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
if (!connected) {
return LIBUSB_ERROR_NO_DEVICE;
}
std::lock_guard lk(usb_lock);
do {
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout);
if (err < 0) handle_usb_issue(err, __func__);
} while (err < 0 && connected);
return err;
}
int Panda::usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
int err;
int transferred = 0;
if (!connected) {
return 0;
}
std::lock_guard lk(usb_lock);
do {
// Try sending can messages. If the receive buffer on the panda is full it will NAK
// and libusb will try again. After 5ms, it will time out. We will drop the messages.
err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
if (err == LIBUSB_ERROR_TIMEOUT) {
LOGW("Transmit buffer full");
break;
} else if (err != 0 || length != transferred) {
handle_usb_issue(err, __func__);
}
} while(err != 0 && connected);
return transferred;
}
int Panda::usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
int err;
int transferred = 0;
if (!connected) {
return 0;
}
std::lock_guard lk(usb_lock);
do {
err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
if (err == LIBUSB_ERROR_TIMEOUT) {
break; // timeout is okay to exit, recv still happened
} else if (err == LIBUSB_ERROR_OVERFLOW) {
comms_healthy = false;
LOGE_100("overflow got 0x%x", transferred);
} else if (err != 0) {
handle_usb_issue(err, __func__);
}
} while(err != 0 && connected);
return transferred;
} }
void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) {
usb_write(0xdc, (uint16_t)safety_model, safety_param); handle->control_write(0xdc, (uint16_t)safety_model, safety_param);
} }
void Panda::set_alternative_experience(uint16_t alternative_experience) { void Panda::set_alternative_experience(uint16_t alternative_experience) {
usb_write(0xdf, alternative_experience, 0); handle->control_write(0xdf, alternative_experience, 0);
} }
cereal::PandaState::PandaType Panda::get_hw_type() { cereal::PandaState::PandaType Panda::get_hw_type() {
unsigned char hw_query[1] = {0}; unsigned char hw_query[1] = {0};
usb_read(0xc1, 0, 0, hw_query, 1); handle->control_read(0xc1, 0, 0, hw_query, 1);
return (cereal::PandaState::PandaType)(hw_query[0]); return (cereal::PandaState::PandaType)(hw_query[0]);
} }
void Panda::set_rtc(struct tm sys_time) { void Panda::set_rtc(struct tm sys_time) {
// tm struct has year defined as years since 1900 // tm struct has year defined as years since 1900
usb_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); handle->control_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0);
usb_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); handle->control_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0);
usb_write(0xa3, (uint16_t)sys_time.tm_mday, 0); handle->control_write(0xa3, (uint16_t)sys_time.tm_mday, 0);
// usb_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); // handle->control_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0);
usb_write(0xa5, (uint16_t)sys_time.tm_hour, 0); handle->control_write(0xa5, (uint16_t)sys_time.tm_hour, 0);
usb_write(0xa6, (uint16_t)sys_time.tm_min, 0); handle->control_write(0xa6, (uint16_t)sys_time.tm_min, 0);
usb_write(0xa7, (uint16_t)sys_time.tm_sec, 0); handle->control_write(0xa7, (uint16_t)sys_time.tm_sec, 0);
} }
struct tm Panda::get_rtc() { struct tm Panda::get_rtc() {
@ -284,7 +79,7 @@ struct tm Panda::get_rtc() {
uint8_t second; uint8_t second;
} rtc_time = {0}; } rtc_time = {0};
usb_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); handle->control_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time));
struct tm new_time = { 0 }; struct tm new_time = { 0 };
new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900 new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900
@ -298,60 +93,70 @@ struct tm Panda::get_rtc() {
} }
void Panda::set_fan_speed(uint16_t fan_speed) { void Panda::set_fan_speed(uint16_t fan_speed) {
usb_write(0xb1, fan_speed, 0); handle->control_write(0xb1, fan_speed, 0);
} }
uint16_t Panda::get_fan_speed() { uint16_t Panda::get_fan_speed() {
uint16_t fan_speed_rpm = 0; uint16_t fan_speed_rpm = 0;
usb_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); handle->control_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm));
return fan_speed_rpm; return fan_speed_rpm;
} }
void Panda::set_ir_pwr(uint16_t ir_pwr) { void Panda::set_ir_pwr(uint16_t ir_pwr) {
usb_write(0xb0, ir_pwr, 0); handle->control_write(0xb0, ir_pwr, 0);
} }
std::optional<health_t> Panda::get_state() { std::optional<health_t> Panda::get_state() {
health_t health {0}; health_t health {0};
int err = usb_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); int err = handle->control_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health));
return err >= 0 ? std::make_optional(health) : std::nullopt; return err >= 0 ? std::make_optional(health) : std::nullopt;
} }
std::optional<can_health_t> Panda::get_can_state(uint16_t can_number) {
can_health_t can_health {0};
int err = handle->control_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health));
return err >= 0 ? std::make_optional(can_health) : std::nullopt;
}
void Panda::set_loopback(bool loopback) { void Panda::set_loopback(bool loopback) {
usb_write(0xe5, loopback, 0); handle->control_write(0xe5, loopback, 0);
} }
std::optional<std::vector<uint8_t>> Panda::get_firmware_version() { std::optional<std::vector<uint8_t>> Panda::get_firmware_version() {
std::vector<uint8_t> fw_sig_buf(128); std::vector<uint8_t> fw_sig_buf(128);
int read_1 = usb_read(0xd3, 0, 0, &fw_sig_buf[0], 64); int read_1 = handle->control_read(0xd3, 0, 0, &fw_sig_buf[0], 64);
int read_2 = usb_read(0xd4, 0, 0, &fw_sig_buf[64], 64); int read_2 = handle->control_read(0xd4, 0, 0, &fw_sig_buf[64], 64);
return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt; return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt;
} }
std::optional<std::string> Panda::get_serial() { std::optional<std::string> Panda::get_serial() {
char serial_buf[17] = {'\0'}; char serial_buf[17] = {'\0'};
int err = usb_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); int err = handle->control_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16);
return err >= 0 ? std::make_optional(serial_buf) : std::nullopt; return err >= 0 ? std::make_optional(serial_buf) : std::nullopt;
} }
void Panda::set_power_saving(bool power_saving) { void Panda::set_power_saving(bool power_saving) {
usb_write(0xe7, power_saving, 0); handle->control_write(0xe7, power_saving, 0);
} }
void Panda::enable_deepsleep() { void Panda::enable_deepsleep() {
usb_write(0xfb, 0, 0); handle->control_write(0xfb, 0, 0);
} }
void Panda::send_heartbeat(bool engaged) { void Panda::send_heartbeat(bool engaged) {
usb_write(0xf3, engaged, 0); handle->control_write(0xf3, engaged, 0);
} }
void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) {
usb_write(0xde, bus, (speed * 10)); handle->control_write(0xde, bus, (speed * 10));
} }
void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) {
usb_write(0xf9, bus, (speed * 10)); handle->control_write(0xf9, bus, (speed * 10));
}
void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) {
handle->control_write(0xfc, bus, non_iso);
} }
static uint8_t len_to_dlc(uint8_t len) { static uint8_t len_to_dlc(uint8_t len) {
@ -389,7 +194,7 @@ void Panda::pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data
} }
auto can_data = cmsg.getDat(); auto can_data = cmsg.getDat();
uint8_t data_len_code = len_to_dlc(can_data.size()); uint8_t data_len_code = len_to_dlc(can_data.size());
assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA) ? 64 : 8)); assert(can_data.size() <= 64);
assert(can_data.size() == dlc_to_len[data_len_code]); assert(can_data.size() == dlc_to_len[data_len_code]);
can_header header; can_header header;
@ -412,14 +217,14 @@ void Panda::pack_can_buffer(const capnp::List<cereal::CanData>::Reader &can_data
void Panda::can_send(capnp::List<cereal::CanData>::Reader can_data_list) { void Panda::can_send(capnp::List<cereal::CanData>::Reader can_data_list) {
pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) {
usb_bulk_write(3, data, size, 5); handle->bulk_write(3, data, size, 5);
}); });
} }
bool Panda::can_receive(std::vector<can_frame>& out_vec) { bool Panda::can_receive(std::vector<can_frame>& out_vec) {
uint8_t data[RECV_SIZE]; uint8_t data[RECV_SIZE];
int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE); int recv = handle->bulk_read(0x81, (uint8_t*)data, RECV_SIZE);
if (!comms_healthy) { if (!comms_healthy()) {
return false; return false;
} }
if (recv == RECV_SIZE) { if (recv == RECV_SIZE) {
@ -434,7 +239,7 @@ bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector<can_frame> &o
for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) {
if (data[i] != i / USBPACKET_MAX_SIZE) { if (data[i] != i / USBPACKET_MAX_SIZE) {
LOGE("CAN: MALFORMED USB RECV PACKET"); LOGE("CAN: MALFORMED USB RECV PACKET");
comms_healthy = false; handle->comms_healthy = false;
return false; return false;
} }
int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i));

@ -1,25 +1,26 @@
#pragma once #pragma once
#include <atomic>
#include <cstdint> #include <cstdint>
#include <ctime> #include <ctime>
#include <functional> #include <functional>
#include <list> #include <list>
#include <mutex> #include <memory>
#include <optional> #include <optional>
#include <vector> #include <vector>
#include <libusb-1.0/libusb.h>
#include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/car.capnp.h"
#include "cereal/gen/cpp/log.capnp.h" #include "cereal/gen/cpp/log.capnp.h"
#include "panda/board/health.h" #include "panda/board/health.h"
#include "selfdrive/boardd/panda_comms.h"
#define TIMEOUT 0 #define PANDA_CAN_CNT 3
#define PANDA_BUS_CNT 4 #define PANDA_BUS_CNT 4
#define RECV_SIZE (0x4000U)
#define USB_TX_SOFT_LIMIT (0x100U) #define USB_TX_SOFT_LIMIT (0x100U)
#define USBPACKET_MAX_SIZE (0x40) #define USBPACKET_MAX_SIZE (0x40)
#define RECV_SIZE (0x4000U)
#define CANPACKET_HEAD_SIZE 5U #define CANPACKET_HEAD_SIZE 5U
#define CANPACKET_MAX_SIZE 72U #define CANPACKET_MAX_SIZE 72U
#define CANPACKET_REJECTED (0xC0U) #define CANPACKET_REJECTED (0xC0U)
@ -36,41 +37,32 @@ struct __attribute__((packed)) can_header {
}; };
struct can_frame { struct can_frame {
long address; long address;
std::string dat; std::string dat;
long busTime; long busTime;
long src; long src;
}; };
class Panda { class Panda {
private: private:
libusb_context *ctx = NULL; std::unique_ptr<PandaCommsHandle> handle;
libusb_device_handle *dev_handle = NULL;
std::mutex usb_lock;
std::vector<uint8_t> recv_buf; std::vector<uint8_t> recv_buf;
void handle_usb_issue(int err, const char func[]);
void cleanup();
public: public:
Panda(std::string serial="", uint32_t bus_offset=0); Panda(std::string serial="", uint32_t bus_offset=0);
~Panda();
std::string usb_serial; std::string hw_serial;
std::atomic<bool> connected = true;
std::atomic<bool> comms_healthy = true;
cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN;
bool has_rtc = false; bool has_rtc = false;
const uint32_t bus_offset; const uint32_t bus_offset;
bool connected();
bool comms_healthy();
// Static functions // Static functions
static std::vector<std::string> list(); static std::vector<std::string> list();
// HW communication
int usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout=TIMEOUT);
int usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout=TIMEOUT);
int usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
int usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
// Panda functionality // Panda functionality
cereal::PandaState::PandaType get_hw_type(); cereal::PandaState::PandaType get_hw_type();
void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U); void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U);
@ -81,6 +73,7 @@ class Panda {
uint16_t get_fan_speed(); uint16_t get_fan_speed();
void set_ir_pwr(uint16_t ir_pwr); void set_ir_pwr(uint16_t ir_pwr);
std::optional<health_t> get_state(); std::optional<health_t> get_state();
std::optional<can_health_t> get_can_state(uint16_t can_number);
void set_loopback(bool loopback); void set_loopback(bool loopback);
std::optional<std::vector<uint8_t>> get_firmware_version(); std::optional<std::vector<uint8_t>> get_firmware_version();
std::optional<std::string> get_serial(); std::optional<std::string> get_serial();
@ -89,6 +82,7 @@ class Panda {
void send_heartbeat(bool engaged); void send_heartbeat(bool engaged);
void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_can_speed_kbps(uint16_t bus, uint16_t speed);
void set_data_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed);
void set_canfd_non_iso(uint16_t bus, bool non_iso);
void can_send(capnp::List<cereal::CanData>::Reader can_data_list); void can_send(capnp::List<cereal::CanData>::Reader can_data_list);
bool can_receive(std::vector<can_frame>& out_vec); bool can_receive(std::vector<can_frame>& out_vec);

@ -0,0 +1,232 @@
#include "selfdrive/boardd/panda.h"
#include <cassert>
#include <stdexcept>
#include "common/swaglog.h"
static int init_usb_ctx(libusb_context **context) {
assert(context != nullptr);
int err = libusb_init(context);
if (err != 0) {
LOGE("libusb initialization error");
return err;
}
#if LIBUSB_API_VERSION >= 0x01000106
libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO);
#else
libusb_set_debug(*context, 3);
#endif
return err;
}
PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) {
// init libusb
ssize_t num_devices;
libusb_device **dev_list = NULL;
int err = init_usb_ctx(&ctx);
if (err != 0) { goto fail; }
// connect by serial
num_devices = libusb_get_device_list(ctx, &dev_list);
if (num_devices < 0) { goto fail; }
for (size_t i = 0; i < num_devices; ++i) {
libusb_device_descriptor desc;
libusb_get_device_descriptor(dev_list[i], &desc);
if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
int ret = libusb_open(dev_list[i], &dev_handle);
if (dev_handle == NULL || ret < 0) { goto fail; }
unsigned char desc_serial[26] = { 0 };
ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
if (ret < 0) { goto fail; }
auto hw_serial = std::string((char *)desc_serial, ret);
if (serial.empty() || serial == hw_serial) {
break;
}
libusb_close(dev_handle);
dev_handle = NULL;
}
}
if (dev_handle == NULL) goto fail;
libusb_free_device_list(dev_list, 1);
dev_list = nullptr;
if (libusb_kernel_driver_active(dev_handle, 0) == 1) {
libusb_detach_kernel_driver(dev_handle, 0);
}
err = libusb_set_configuration(dev_handle, 1);
if (err != 0) { goto fail; }
err = libusb_claim_interface(dev_handle, 0);
if (err != 0) { goto fail; }
return;
fail:
if (dev_list != NULL) {
libusb_free_device_list(dev_list, 1);
}
cleanup();
throw std::runtime_error("Error connecting to panda");
}
PandaUsbHandle::~PandaUsbHandle() {
std::lock_guard lk(hw_lock);
cleanup();
connected = false;
}
void PandaUsbHandle::cleanup() {
if (dev_handle) {
libusb_release_interface(dev_handle, 0);
libusb_close(dev_handle);
}
if (ctx) {
libusb_exit(ctx);
}
}
std::vector<std::string> PandaUsbHandle::list() {
// init libusb
ssize_t num_devices;
libusb_context *context = NULL;
libusb_device **dev_list = NULL;
std::vector<std::string> serials;
int err = init_usb_ctx(&context);
if (err != 0) { return serials; }
num_devices = libusb_get_device_list(context, &dev_list);
if (num_devices < 0) {
LOGE("libusb can't get device list");
goto finish;
}
for (size_t i = 0; i < num_devices; ++i) {
libusb_device *device = dev_list[i];
libusb_device_descriptor desc;
libusb_get_device_descriptor(device, &desc);
if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) {
libusb_device_handle *handle = NULL;
int ret = libusb_open(device, &handle);
if (ret < 0) { goto finish; }
unsigned char desc_serial[26] = { 0 };
ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial));
libusb_close(handle);
if (ret < 0) { goto finish; }
serials.push_back(std::string((char *)desc_serial, ret).c_str());
}
}
finish:
if (dev_list != NULL) {
libusb_free_device_list(dev_list, 1);
}
if (context) {
libusb_exit(context);
}
return serials;
}
void PandaUsbHandle::handle_usb_issue(int err, const char func[]) {
LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func);
if (err == LIBUSB_ERROR_NO_DEVICE) {
LOGE("lost connection");
connected = false;
}
// TODO: check other errors, is simply retrying okay?
}
int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) {
int err;
const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
if (!connected) {
return LIBUSB_ERROR_NO_DEVICE;
}
std::lock_guard lk(hw_lock);
do {
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout);
if (err < 0) handle_usb_issue(err, __func__);
} while (err < 0 && connected);
return err;
}
int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) {
int err;
const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE;
if (!connected) {
return LIBUSB_ERROR_NO_DEVICE;
}
std::lock_guard lk(hw_lock);
do {
err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout);
if (err < 0) handle_usb_issue(err, __func__);
} while (err < 0 && connected);
return err;
}
int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
int err;
int transferred = 0;
if (!connected) {
return 0;
}
std::lock_guard lk(hw_lock);
do {
// Try sending can messages. If the receive buffer on the panda is full it will NAK
// and libusb will try again. After 5ms, it will time out. We will drop the messages.
err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
if (err == LIBUSB_ERROR_TIMEOUT) {
LOGW("Transmit buffer full");
break;
} else if (err != 0 || length != transferred) {
handle_usb_issue(err, __func__);
}
} while(err != 0 && connected);
return transferred;
}
int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
int err;
int transferred = 0;
if (!connected) {
return 0;
}
std::lock_guard lk(hw_lock);
do {
err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout);
if (err == LIBUSB_ERROR_TIMEOUT) {
break; // timeout is okay to exit, recv still happened
} else if (err == LIBUSB_ERROR_OVERFLOW) {
comms_healthy = false;
LOGE_100("overflow got 0x%x", transferred);
} else if (err != 0) {
handle_usb_issue(err, __func__);
}
} while(err != 0 && connected);
return transferred;
}

@ -0,0 +1,80 @@
#pragma once
#include <mutex>
#include <atomic>
#include <cstdint>
#include <vector>
#include <linux/spi/spidev.h>
#include <libusb-1.0/libusb.h>
#define TIMEOUT 0
#define SPI_BUF_SIZE 1024
const bool PANDA_NO_RETRY = getenv("PANDA_NO_RETRY");
// comms base class
class PandaCommsHandle {
public:
PandaCommsHandle(std::string serial) {};
virtual ~PandaCommsHandle() {};
virtual void cleanup() = 0;
std::atomic<bool> connected = true;
std::atomic<bool> comms_healthy = true;
static std::vector<std::string> list();
// HW communication
virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0;
virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0;
virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0;
protected:
std::recursive_mutex hw_lock;
};
class PandaUsbHandle : public PandaCommsHandle {
public:
PandaUsbHandle(std::string serial);
~PandaUsbHandle();
int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT);
int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT);
int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
void cleanup();
static std::vector<std::string> list();
private:
libusb_context *ctx = NULL;
libusb_device_handle *dev_handle = NULL;
std::vector<uint8_t> recv_buf;
void handle_usb_issue(int err, const char func[]);
};
class PandaSpiHandle : public PandaCommsHandle {
public:
PandaSpiHandle(std::string serial);
~PandaSpiHandle();
int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT);
int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT);
int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT);
void cleanup();
static std::vector<std::string> list();
private:
int spi_fd = -1;
uint8_t tx_buf[SPI_BUF_SIZE];
uint8_t rx_buf[SPI_BUF_SIZE];
int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack);
int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len);
int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len);
};

@ -0,0 +1,287 @@
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
#include <cassert>
#include <cmath>
#include <cstring>
#include "common/util.h"
#include "common/swaglog.h"
#include "panda/board/comms_definitions.h"
#include "selfdrive/boardd/panda_comms.h"
#define SPI_SYNC 0x5AU
#define SPI_HACK 0x79U
#define SPI_DACK 0x85U
#define SPI_NACK 0x1FU
#define SPI_CHECKSUM_START 0xABU
struct __attribute__((packed)) spi_header {
uint8_t sync;
uint8_t endpoint;
uint16_t tx_len;
uint16_t max_rx_len;
};
PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) {
LOGD("opening SPI panda: %s", serial.c_str());
int err;
uint32_t spi_mode = SPI_MODE_0;
uint32_t spi_speed = 30000000;
uint8_t spi_bits_per_word = 8;
spi_fd = open(serial.c_str(), O_RDWR);
if (spi_fd < 0) {
LOGE("failed opening SPI device %d", err);
goto fail;
}
// SPI settings
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode);
if (err < 0) {
LOGE("failed setting SPI mode %d", err);
goto fail;
}
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed);
if (err < 0) {
LOGE("failed setting SPI speed");
goto fail;
}
err = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word);
if (err < 0) {
LOGE("failed setting SPI bits per word");
goto fail;
}
return;
fail:
cleanup();
throw std::runtime_error("Error connecting to panda");
}
PandaSpiHandle::~PandaSpiHandle() {
std::lock_guard lk(hw_lock);
cleanup();
}
void PandaSpiHandle::cleanup() {
if (spi_fd != -1) {
close(spi_fd);
spi_fd = -1;
}
}
int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) {
ControlPacket_t packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = 0
};
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), NULL, 0);
}
int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) {
ControlPacket_t packet = {
.request = request,
.param1 = param1,
.param2 = param2,
.length = length
};
return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), data, length);
}
int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return bulk_transfer(endpoint, data, length, NULL, 0);
}
int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) {
return bulk_transfer(endpoint, NULL, 0, data, length);
}
int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) {
std::lock_guard lk(hw_lock);
const int xfer_size = 0x40;
int ret = 0;
uint16_t length = (tx_data != NULL) ? tx_len : rx_len;
for (int i = 0; i < (int)std::ceil((float)length / xfer_size); i++) {
int d;
if (tx_data != NULL) {
int len = std::min(xfer_size, tx_len - (xfer_size * i));
d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0);
} else {
d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), xfer_size);
}
if (d < 0) {
LOGE("SPI: bulk transfer failed with %d", d);
comms_healthy = false;
return -1;
}
ret += d;
if ((rx_data != NULL) && d < xfer_size) {
break;
}
}
return ret;
}
std::vector<std::string> PandaSpiHandle::list() {
// TODO: list all pandas available over SPI
return {};
}
void add_checksum(uint8_t *data, int data_len) {
data[data_len] = SPI_CHECKSUM_START;
for (int i=0; i < data_len; i++) {
data[data_len] ^= data[i];
}
}
bool check_checksum(uint8_t *data, int data_len) {
uint8_t checksum = SPI_CHECKSUM_START;
for (uint16_t i = 0U; i < data_len; i++) {
checksum ^= data[i];
}
return checksum == 0U;
}
int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
int ret;
std::lock_guard lk(hw_lock);
do {
// TODO: handle error
ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len);
} while (ret < 0 && connected && !PANDA_NO_RETRY);
return ret;
}
int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) {
// TODO: add timeout?
while (true) {
int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send ACK request");
return ret;
}
if (rx_buf[0] == ack) {
break;
} else if (rx_buf[0] == SPI_NACK) {
LOGW("SPI: got NACK");
return -1;
}
}
return 0;
}
int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) {
int ret;
uint16_t rx_data_len;
// needs to be less, since we need to have space for the checksum
assert(tx_len < SPI_BUF_SIZE);
assert(max_rx_len < SPI_BUF_SIZE);
spi_header header = {
.sync = SPI_SYNC,
.endpoint = endpoint,
.tx_len = tx_len,
.max_rx_len = max_rx_len
};
spi_ioc_transfer transfer = {
.tx_buf = (uint64_t)tx_buf,
.rx_buf = (uint64_t)rx_buf
};
// Send header
memcpy(tx_buf, &header, sizeof(header));
add_checksum(tx_buf, sizeof(header));
transfer.len = sizeof(header) + 1;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send header");
goto transfer_fail;
}
// Wait for (N)ACK
tx_buf[0] = 0x12;
transfer.len = 1;
ret = wait_for_ack(transfer, SPI_HACK);
if (ret < 0) {
goto transfer_fail;
}
// Send data
if (tx_data != NULL) {
memcpy(tx_buf, tx_data, tx_len);
}
add_checksum(tx_buf, tx_len);
transfer.len = tx_len + 1;
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to send data");
goto transfer_fail;
}
// Wait for (N)ACK
tx_buf[0] = 0xab;
transfer.len = 1;
ret = wait_for_ack(transfer, SPI_DACK);
if (ret < 0) {
goto transfer_fail;
}
// Read data len
transfer.len = 2;
transfer.rx_buf = (uint64_t)(rx_buf + 1);
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to read rx data len");
goto transfer_fail;
}
rx_data_len = *(uint16_t *)(rx_buf+1);
assert(rx_data_len < SPI_BUF_SIZE);
// Read data
transfer.len = rx_data_len + 1;
transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1);
ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer);
if (ret < 0) {
LOGE("SPI: failed to read rx data");
goto transfer_fail;
}
if (!check_checksum(rx_buf, rx_data_len + 4)) {
LOGE("SPI: bad checksum");
goto transfer_fail;
}
if (rx_data != NULL) {
memcpy(rx_data, rx_buf + 3, rx_data_len);
}
return rx_data_len;
transfer_fail:
return ret;
}

@ -3,7 +3,7 @@ import capnp
from cereal import car from cereal import car
from common.numpy_fast import clip from common.numpy_fast import clip
from typing import Dict, List from typing import Dict
# kg of standard extra cargo to count for drive, gas, etc... # kg of standard extra cargo to count for drive, gas, etc...
STD_CARGO_KG = 136. STD_CARGO_KG = 136.
@ -12,6 +12,14 @@ ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float:
if val > val_steady + hyst_gap:
val_steady = val - hyst_gap
elif val < val_steady - hyst_gap:
val_steady = val + hyst_gap
return val_steady
def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule], def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule],
unpressed: int = 0) -> capnp.lib.capnp._DynamicStructBuilder: unpressed: int = 0) -> capnp.lib.capnp._DynamicStructBuilder:
if cur_but != unpressed: if cur_but != unpressed:
@ -24,19 +32,6 @@ def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, cap
return be return be
def create_button_enable_events(buttonEvents: capnp.lib.capnp._DynamicListBuilder, pcm_cruise: bool = False) -> List[int]:
events = []
for b in buttonEvents:
# do enable on both accel and decel buttons
if not pcm_cruise:
if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed:
events.append(EventName.buttonEnable)
# do disable on button down
if b.type == ButtonType.cancel and b.pressed:
events.append(EventName.buttonCancel)
return events
def gen_empty_fingerprint(): def gen_empty_fingerprint():
return {i: {} for i in range(0, 8)} return {i: {} for i in range(0, 8)}

@ -29,6 +29,12 @@ CAR_INFO: Dict[str, CarInfo] = {
CAR.BODY: CarInfo("comma body", package="All"), CAR.BODY: CarInfo("comma body", package="All"),
} }
FINGERPRINTS = {
CAR.BODY: [{
513: 8, 516: 8, 514: 3, 515: 4,
}],
}
FW_QUERY_CONFIG = FwQueryConfig( FW_QUERY_CONFIG = FwQueryConfig(
requests=[ requests=[
Request( Request(
@ -43,10 +49,13 @@ FW_VERSIONS = {
CAR.BODY: { CAR.BODY: {
(Ecu.engine, 0x720, None): [ (Ecu.engine, 0x720, None): [
b'0.0.01', b'0.0.01',
b'02/27/2022' b'02/27/2022',
b'0.3.00a',
], ],
# git hash of the firmware used
(Ecu.debug, 0x721, None): [ (Ecu.debug, 0x721, None): [
b'166bd860' # git hash of the firmware used b'166bd860',
b'dc780f85',
], ],
}, },
} }

@ -76,12 +76,12 @@ interfaces = load_interfaces(interface_names)
# **** for use live only **** # **** for use live only ****
def fingerprint(logcan, sendcan): def fingerprint(logcan, sendcan, num_pandas):
fixed_fingerprint = os.environ.get('FINGERPRINT', "") fixed_fingerprint = os.environ.get('FINGERPRINT', "")
skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) skip_fw_query = os.environ.get('SKIP_FW_QUERY', False)
ecu_rx_addrs = set() ecu_rx_addrs = set()
if not fixed_fingerprint and not skip_fw_query: if not skip_fw_query:
# Vin query only reliably works through OBDII # Vin query only reliably works through OBDII
bus = 1 bus = 1
@ -95,16 +95,19 @@ def fingerprint(logcan, sendcan):
cloudlog.warning("Using cached CarParams") cloudlog.warning("Using cached CarParams")
vin, vin_rx_addr = cached_params.carVin, 0 vin, vin_rx_addr = cached_params.carVin, 0
car_fw = list(cached_params.carFw) car_fw = list(cached_params.carFw)
cached = True
else: else:
cloudlog.warning("Getting VIN & FW versions") cloudlog.warning("Getting VIN & FW versions")
_, vin_rx_addr, vin = get_vin(logcan, sendcan, bus) vin_rx_addr, vin = get_vin(logcan, sendcan, bus)
ecu_rx_addrs = get_present_ecus(logcan, sendcan) ecu_rx_addrs = get_present_ecus(logcan, sendcan)
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas)
cached = False
exact_fw_match, fw_candidates = match_fw_to_car(car_fw) exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
else: else:
vin, vin_rx_addr = VIN_UNKNOWN, 0 vin, vin_rx_addr = VIN_UNKNOWN, 0
exact_fw_match, fw_candidates, car_fw = True, set(), [] exact_fw_match, fw_candidates, car_fw = True, set(), []
cached = False
if not is_valid_vin(vin): if not is_valid_vin(vin):
cloudlog.event("Malformed VIN", vin=vin, error=True) cloudlog.event("Malformed VIN", vin=vin, error=True)
@ -165,13 +168,13 @@ def fingerprint(logcan, sendcan):
car_fingerprint = fixed_fingerprint car_fingerprint = fixed_fingerprint
source = car.CarParams.FingerprintSource.fixed source = car.CarParams.FingerprintSource.fixed
cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached,
fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, error=True) fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, error=True)
return car_fingerprint, finger, vin, car_fw, source, exact_match return car_fingerprint, finger, vin, car_fw, source, exact_match
def get_car(logcan, sendcan): def get_car(logcan, sendcan, num_pandas=1):
candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan) candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas)
if candidate is None: if candidate is None:
cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints) cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints)

@ -53,7 +53,7 @@ RAM_CARS = RAM_DT | RAM_HD
@dataclass @dataclass
class ChryslerCarInfo(CarInfo): class ChryslerCarInfo(CarInfo):
package: str = "Adaptive Cruise Control" package: str = "Adaptive Cruise Control (ACC)"
harness: Enum = Harness.fca harness: Enum = Harness.fca
CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
@ -150,28 +150,30 @@ FW_QUERY_CONFIG = FwQueryConfig(
Request( Request(
[CHRYSLER_VERSION_REQUEST], [CHRYSLER_VERSION_REQUEST],
[CHRYSLER_VERSION_RESPONSE], [CHRYSLER_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.srs, Ecu.gateway, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.combinationMeter], whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.srs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.combinationMeter],
rx_offset=CHRYSLER_RX_OFFSET, rx_offset=CHRYSLER_RX_OFFSET,
bus=0,
), ),
Request( Request(
[CHRYSLER_VERSION_REQUEST], [CHRYSLER_VERSION_REQUEST],
[CHRYSLER_VERSION_RESPONSE], [CHRYSLER_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.hcp, Ecu.engine, Ecu.transmission], whitelist_ecus=[Ecu.abs, Ecu.hcp, Ecu.engine, Ecu.transmission],
bus=0,
), ),
Request( Request(
[CHRYSLER_SOFTWARE_VERSION_REQUEST], [CHRYSLER_SOFTWARE_VERSION_REQUEST],
[CHRYSLER_SOFTWARE_VERSION_RESPONSE], [CHRYSLER_SOFTWARE_VERSION_RESPONSE],
whitelist_ecus=[Ecu.engine, Ecu.transmission], whitelist_ecus=[Ecu.engine, Ecu.transmission],
bus=0,
), ),
], ],
extra_ecus=[
(Ecu.hcp, 0x7e2, None), # manages transmission on hybrids
(Ecu.abs, 0x7e4, None), # alt address for abs on hybrids
],
) )
FW_VERSIONS = { FW_VERSIONS = {
CAR.PACIFICA_2019_HYBRID: {
(Ecu.hcp, 0x7e2, None): [],
(Ecu.abs, 0x7e4, None): [],
},
CAR.RAM_1500: { CAR.RAM_1500: {
(Ecu.combinationMeter, 0x742, None): [ (Ecu.combinationMeter, 0x742, None): [
b'68294063AH', b'68294063AH',
@ -224,12 +226,6 @@ FW_VERSIONS = {
b'68540431AB', b'68540431AB',
b'68484467AC', b'68484467AC',
], ],
(Ecu.gateway, 0x18DACBF1, None): [
b'68402660AB',
b'68445283AB',
b'68533631AB',
b'68500483AB',
],
}, },
CAR.RAM_HD: { CAR.RAM_HD: {
@ -246,6 +242,7 @@ FW_VERSIONS = {
b'68334977AH', b'68334977AH',
b'68504022AB', b'68504022AB',
b'68530686AB', b'68530686AB',
b'68504022AC',
], ],
(Ecu.fwdRadar, 0x753, None): [ (Ecu.fwdRadar, 0x753, None): [
b'04672895AB', b'04672895AB',
@ -261,10 +258,6 @@ FW_VERSIONS = {
b'M2370131MB', b'M2370131MB',
b'M2421132MB', b'M2421132MB',
], ],
(Ecu.gateway, 0x18DACBF1, None): [
b'68488419AB',
b'68535476AB',
],
}, },
} }

@ -7,14 +7,15 @@ from enum import Enum
from natsort import natsorted from natsort import natsorted
from typing import Dict, List from typing import Dict, List
from cereal import car
from common.basedir import BASEDIR from common.basedir import BASEDIR
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
from selfdrive.car.docs_definitions import CarInfo, Column from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote
from selfdrive.car.car_helpers import interfaces, get_interface_attr from selfdrive.car.car_helpers import interfaces, get_interface_attr
def get_all_footnotes() -> Dict[Enum, int]: def get_all_footnotes() -> Dict[Enum, int]:
all_footnotes = [] all_footnotes = list(CommonFootnote)
for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): for footnotes in get_interface_attr("Footnote", ignore_none=True).values():
all_footnotes.extend(footnotes) all_footnotes.extend(footnotes)
return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)}
@ -28,7 +29,7 @@ def get_all_car_info() -> List[CarInfo]:
all_car_info: List[CarInfo] = [] all_car_info: List[CarInfo] = []
footnotes = get_all_footnotes() footnotes = get_all_footnotes()
for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items():
CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), experimental_long=True) CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")])
if CP.dashcamOnly or car_info is None: if CP.dashcamOnly or car_info is None:
continue continue
@ -39,6 +40,7 @@ def get_all_car_info() -> List[CarInfo]:
for _car_info in car_info: for _car_info in car_info:
if not hasattr(_car_info, "row"): if not hasattr(_car_info, "row"):
_car_info.init_make(CP)
_car_info.init(CP, footnotes) _car_info.init(CP, footnotes)
all_car_info.append(_car_info) all_car_info.append(_car_info)

@ -68,7 +68,17 @@ class Harness(Enum):
none = "None" none = "None"
CarFootnote = namedtuple("CarFootnote", ["text", "column"], defaults=[None]) CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only"], defaults=(None, False))
class CommonFootnote(Enum):
EXP_LONG_AVAIL = CarFootnote(
"Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. ",
Column.LONGITUDINAL, docs_only=True)
EXP_LONG_DSU = CarFootnote(
"By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace " +
"stock ACC. <b><i>NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).</i></b>",
Column.LONGITUDINAL)
def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]: def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]:
@ -128,11 +138,22 @@ class CarInfo:
self.car_name = CP.carName self.car_name = CP.carName
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.make, self.model, self.years = split_name(self.name) self.make, self.model, self.years = split_name(self.name)
op_long = "Stock"
if CP.openpilotLongitudinalControl and not CP.enableDsu:
op_long = "openpilot"
elif CP.experimentalLongitudinalAvailable or CP.enableDsu:
op_long = "openpilot available"
if CP.enableDsu:
self.footnotes.append(CommonFootnote.EXP_LONG_DSU)
else:
self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL)
self.row = { self.row = {
Column.MAKE: self.make, Column.MAKE: self.make,
Column.MODEL: self.model, Column.MODEL: self.model,
Column.PACKAGE: self.package, Column.PACKAGE: self.package,
Column.LONGITUDINAL: "openpilot" if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable else "Stock", Column.LONGITUDINAL: op_long,
Column.FSR_LONGITUDINAL: f"{max(self.min_enable_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.FSR_LONGITUDINAL: f"{max(self.min_enable_speed * CV.MS_TO_MPH, 0):.0f} mph",
Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph",
Column.STEERING_TORQUE: Star.EMPTY, Column.STEERING_TORQUE: Star.EMPTY,
@ -151,6 +172,9 @@ class CarInfo:
return self return self
def init_make(self, CP: car.CarParams):
"""CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc."""
def get_detail_sentence(self, CP): def get_detail_sentence(self, CP):
if not CP.notCar: if not CP.notCar:
sentence_builder = "openpilot upgrades your <strong>{car_model}</strong> with automated lane centering{alc} and adaptive cruise control{acc}." sentence_builder = "openpilot upgrades your <strong>{car_model}</strong> with automated lane centering{alc} and adaptive cruise control{acc}."

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import capnp import capnp
import time import time
import traceback
from typing import Optional, Set, Tuple from typing import Optional, Set, Tuple
import cereal.messaging as messaging import cereal.messaging as messaging
@ -62,7 +61,7 @@ def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, que
print(f"Duplicate ECU address: {hex(msg.address)}") print(f"Duplicate ECU address: {hex(msg.address)}")
ecu_responses.add((msg.address, subaddr, msg.src)) ecu_responses.add((msg.address, subaddr, msg.src))
except Exception: except Exception:
cloudlog.warning(f"ECU addr scan exception: {traceback.format_exc()}") cloudlog.exception("ECU addr scan exception")
return ecu_responses return ecu_responses
@ -71,6 +70,8 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Get addresses of all ECUs') parser = argparse.ArgumentParser(description='Get addresses of all ECUs')
parser.add_argument('--debug', action='store_true') parser.add_argument('--debug', action='store_true')
parser.add_argument('--bus', type=int, default=1)
parser.add_argument('--timeout', type=float, default=1.0)
args = parser.parse_args() args = parser.parse_args()
logcan = messaging.sub_sock('can') logcan = messaging.sub_sock('can')
@ -79,7 +80,7 @@ if __name__ == "__main__":
time.sleep(1.0) time.sleep(1.0)
print("Getting ECU addresses ...") print("Getting ECU addresses ...")
ecu_addrs = get_all_ecu_addrs(logcan, sendcan, 1, debug=args.debug) ecu_addrs = get_all_ecu_addrs(logcan, sendcan, args.bus, args.timeout, debug=args.debug)
print() print()
print("Found ECUs on addresses:") print("Found ECUs on addresses:")

@ -3,7 +3,7 @@ from cereal import car
from common.numpy_fast import clip, interp from common.numpy_fast import clip, interp
from opendbc.can.packer import CANPacker from opendbc.can.packer import CANPacker
from selfdrive.car.ford import fordcan from selfdrive.car.ford import fordcan
from selfdrive.car.ford.values import CarControllerParams from selfdrive.car.ford.values import CANBUS, CarControllerParams
VisualAlert = car.CarControl.HUDControl.VisualAlert VisualAlert = car.CarControl.HUDControl.VisualAlert
@ -16,9 +16,9 @@ def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo):
apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff)) apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff))
# absolute limit (LatCtlPath_An_Actl) # absolute limit (LatCtlPath_An_Actl)
apply_path_angle = math.radians(apply_angle) / CarControllerParams.STEER_RATIO apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
apply_path_angle = clip(apply_path_angle, -0.4995, 0.5240) apply_path_angle = clip(apply_path_angle, -0.5, 0.5235)
apply_angle = math.degrees(apply_path_angle) * CarControllerParams.STEER_RATIO apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO
return apply_angle return apply_angle
@ -47,40 +47,46 @@ class CarController:
### acc buttons ### ### acc buttons ###
if CC.cruiseControl.cancel: if CC.cruiseControl.cancel:
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True)) can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True))
elif CC.cruiseControl.resume: can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main))
elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0:
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True)) can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True))
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main))
# if stock lane centering is active or in standby, toggle it off # if stock lane centering isn't off, send a button press to toggle it off
# the stock system checks for steering pressed, and eventually disengages cruise control # the stock system checks for steering pressed, and eventually disengages cruise control
if (self.frame % 200) == 0 and CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0: elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0:
can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True)) can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True))
### lateral control ### ### lateral control ###
if CC.latActive: if CC.latActive:
lca_rq = 1
apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo) apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo)
else: else:
apply_angle = CS.out.steeringAngleDeg lca_rq = 0
apply_angle = 0.
# send steering commands at 20Hz # send steering commands at 20Hz
if (self.frame % CarControllerParams.STEER_STEP) == 0: if (self.frame % CarControllerParams.STEER_STEP) == 0:
lca_rq = 1 if CC.latActive else 0
# use LatCtlPath_An_Actl to actuate steering # use LatCtlPath_An_Actl to actuate steering
# path angle is the car wheel angle, not the steering wheel angle path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO
path_angle = math.radians(apply_angle) / CarControllerParams.STEER_RATIO
# set slower ramp type when small steering angle change
# ramp rate: 0=Slow, 1=Medium, 2=Fast, 3=Immediately # 0=Slow, 1=Medium, 2=Fast, 3=Immediately
# TODO: try slower ramp speed when driver torque detected steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg)
ramp_type = 3 if steer_change < 2.0:
ramp_type = 0
elif steer_change < 4.0:
ramp_type = 1
elif steer_change < 6.0:
ramp_type = 2
else:
ramp_type = 3
precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable) precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable)
offset_roll_compensation_curvature = clip(self.VM.calc_curvature(0, CS.out.vEgo, -CS.yaw_data["VehYaw_W_Actl"]), -0.02, 0.02094)
self.apply_angle_last = apply_angle self.apply_angle_last = apply_angle
can_sends.append(fordcan.create_lka_command(self.packer, apply_angle, 0)) can_sends.append(fordcan.create_lka_command(self.packer, 0, 0))
can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision, can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision,
0, path_angle, 0, offset_roll_compensation_curvature)) 0, path_angle, 0, 0))
### ui ### ### ui ###
@ -99,7 +105,7 @@ class CarController:
self.steer_alert_last = steer_alert self.steer_alert_last = steer_alert
new_actuators = actuators.copy() new_actuators = actuators.copy()
new_actuators.steeringAngleDeg = apply_angle new_actuators.steeringAngleDeg = self.apply_angle_last
self.frame += 1 self.frame += 1
return new_actuators, can_sends return new_actuators, can_sends

@ -20,7 +20,7 @@ class CarState(CarStateBase):
ret = car.CarState.new_message() ret = car.CarState.new_message()
# car speed # car speed
ret.vEgoRaw = cp.vl["EngVehicleSpThrottle2"]["Veh_V_ActlEng"] * CV.KPH_TO_MS ret.vEgoRaw = cp.vl["BrakeSysFeatures"]["Veh_V_ActlBrk"] * CV.KPH_TO_MS
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.yawRate = cp.vl["Yaw_Data_FD1"]["VehYaw_W_Actl"] ret.yawRate = cp.vl["Yaw_Data_FD1"]["VehYaw_W_Actl"]
ret.standstill = cp.vl["DesiredTorqBrk"]["VehStop_D_Stat"] == 1 ret.standstill = cp.vl["DesiredTorqBrk"]["VehStop_D_Stat"] == 1
@ -85,8 +85,6 @@ class CarState(CarStateBase):
# Stock values from IPMA so that we can retain some stock functionality # Stock values from IPMA so that we can retain some stock functionality
self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"] self.acc_tja_status_stock_values = cp_cam.vl["ACCDATA_3"]
self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"] self.lkas_status_stock_values = cp_cam.vl["IPMA_Data"]
# Use stock sensor values
self.yaw_data = cp.vl["Yaw_Data_FD1"]
return ret return ret
@ -94,7 +92,7 @@ class CarState(CarStateBase):
def get_can_parser(CP): def get_can_parser(CP):
signals = [ signals = [
# sig_name, sig_address # sig_name, sig_address
("Veh_V_ActlEng", "EngVehicleSpThrottle2"), # ABS vehicle speed (kph) ("Veh_V_ActlBrk", "BrakeSysFeatures"), # ABS vehicle speed (kph)
("VehYaw_W_Actl", "Yaw_Data_FD1"), # ABS vehicle yaw rate (rad/s) ("VehYaw_W_Actl", "Yaw_Data_FD1"), # ABS vehicle yaw rate (rad/s)
("VehStop_D_Stat", "DesiredTorqBrk"), # ABS vehicle stopped ("VehStop_D_Stat", "DesiredTorqBrk"), # ABS vehicle stopped
("PrkBrkStatus", "DesiredTorqBrk"), # ABS park brake status ("PrkBrkStatus", "DesiredTorqBrk"), # ABS park brake status
@ -156,7 +154,7 @@ class CarState(CarStateBase):
checks = [ checks = [
# sig_address, frequency # sig_address, frequency
("EngVehicleSpThrottle2", 50), ("BrakeSysFeatures", 50),
("Yaw_Data_FD1", 100), ("Yaw_Data_FD1", 100),
("DesiredTorqBrk", 50), ("DesiredTorqBrk", 50),
("EngVehicleSpThrottle", 100), ("EngVehicleSpThrottle", 100),

@ -8,8 +8,7 @@ def create_lka_command(packer, angle_deg: float, curvature: float):
""" """
Creates a CAN message for the Ford LKAS Command. Creates a CAN message for the Ford LKAS Command.
This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout.
PSCM lockout.
Frequency is 20Hz. Frequency is 20Hz.
""" """
@ -30,12 +29,20 @@ def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path
""" """
Creates a CAN message for the Ford TJA/LCA Command. Creates a CAN message for the Ford TJA/LCA Command.
This command can apply "Lane Centering" manoeuvres: continuous lane centering This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam
for traffic jam assist and highway driving. It is not subject to the PSCM assist and highway driving. It is not subject to the PSCM lockout.
lockout.
The PSCM should be configured to accept TJA/LCA commands before these Ford lane centering command uses a third order polynomial to describe the road centerline. The
commands will be processed. This can be done using tools such as Forscan. polynomial is defined by the following coefficients:
c0: lateral offset between the vehicle and the centerline
c1: heading angle between the vehicle and the centerline
c2: curvature of the centerline
c3: rate of change of curvature of the centerline
As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and
speed, the steering angle cannot be easily controlled.
The PSCM should be configured to accept TJA/LCA commands before these commands will be processed.
This can be done using tools such as Forscan.
Frequency is 20Hz. Frequency is 20Hz.
""" """
@ -47,7 +54,7 @@ def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path
"LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] "LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3]
"LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] "LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3]
"LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter
"LatCtlPath_An_Actl": path_angle, # Path angle [-0.4995|0.5240] radians "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians
"LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2
"LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter
} }
@ -108,8 +115,8 @@ def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bo
def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict):
""" """
Creates a CAN message for the Ford IPC adaptive cruise, forward collision Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam
warning and traffic jam assist status. assist status.
Stock functionality is maintained by passing through unmodified signals. Stock functionality is maintained by passing through unmodified signals.
@ -141,7 +148,7 @@ def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, sto
return packer.make_can_msg("ACCDATA_3", CANBUS.main, values) return packer.make_can_msg("ACCDATA_3", CANBUS.main, values)
def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus = CANBUS.camera): def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera):
""" """
Creates a CAN message for the Ford SCCM buttons/switches. Creates a CAN message for the Ford SCCM buttons/switches.

@ -5,8 +5,7 @@ from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness,
from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
CarParams = car.CarParams
EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@ -19,10 +18,10 @@ class CarInterface(CarInterfaceBase):
ret.carName = "ford" ret.carName = "ford"
ret.dashcamOnly = True ret.dashcamOnly = True
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)] ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)]
# Angle-based steering # Angle-based steering
ret.steerControlType = car.CarParams.SteerControlType.angle ret.steerControlType = CarParams.SteerControlType.angle
ret.steerActuatorDelay = 0.4 ret.steerActuatorDelay = 0.4
ret.steerLimitTimer = 1.0 ret.steerLimitTimer = 1.0
tire_stiffness_factor = 1.0 tire_stiffness_factor = 1.0
@ -43,7 +42,7 @@ class CarInterface(CarInterfaceBase):
ret.mass = 1350 + STD_CARGO_KG ret.mass = 1350 + STD_CARGO_KG
else: else:
raise ValueError(f"Unsupported car: ${candidate}") raise ValueError(f"Unsupported car: {candidate}")
# Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1
found_ecus = [fw.ecu for fw in car_fw] found_ecus = [fw.ecu for fw in car_fw]

@ -1,4 +1,4 @@
from collections import namedtuple from collections import defaultdict, namedtuple
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Dict, List, Union from typing import Dict, List, Union
@ -22,9 +22,12 @@ class CarControllerParams:
LKAS_UI_STEP = 100 LKAS_UI_STEP = 100
# Message: ACCDATA_3 # Message: ACCDATA_3
ACC_UI_STEP = 5 ACC_UI_STEP = 5
# Message: Steering_Data_FD1, but send twice as fast
BUTTONS_STEP = 10 / 2
STEER_RATIO = 2.75 LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians
STEER_DRIVER_ALLOWANCE = 0.8 # TODO: remove this once we understand how the EPS calculates the steering angle better
STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm
RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15])
RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4])
@ -33,11 +36,6 @@ class CarControllerParams:
pass pass
class RADAR:
DELPHI_ESR = 'ford_fusion_2018_adas'
DELPHI_MRR = 'FORD_CADS'
class CANBUS: class CANBUS:
main = 0 main = 0
radar = 1 radar = 1
@ -50,6 +48,14 @@ class CAR:
FOCUS_MK4 = "FORD FOCUS 4TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN"
class RADAR:
DELPHI_ESR = 'ford_fusion_2018_adas'
DELPHI_MRR = 'FORD_CADS'
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR))
@dataclass @dataclass
class FordCarInfo(CarInfo): class FordCarInfo(CarInfo):
package: str = "Co-Pilot360 Assist+" package: str = "Co-Pilot360 Assist+"
@ -58,10 +64,10 @@ class FordCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
CAR.ESCAPE_MK4: [ CAR.ESCAPE_MK4: [
FordCarInfo("Ford Escape 2020"), FordCarInfo("Ford Escape 2020-21"),
FordCarInfo("Ford Kuga EU", "Driver Assistance Pack"), FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"),
], ],
CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-21"), CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-22"),
CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"),
} }
@ -84,31 +90,41 @@ FW_QUERY_CONFIG = FwQueryConfig(
FW_VERSIONS = { FW_VERSIONS = {
CAR.ESCAPE_MK4: { CAR.ESCAPE_MK4: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdCamera, 0x706, None): [ (Ecu.fwdCamera, 0x706, None): [
b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7E0, None): [ (Ecu.engine, 0x7E0, None): [
b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.shiftByWire, 0x732, None): [ (Ecu.shiftByWire, 0x732, None): [
b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.EXPLORER_MK6: { CAR.EXPLORER_MK6: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -120,10 +136,12 @@ FW_VERSIONS = {
(Ecu.engine, 0x7E0, None): [ (Ecu.engine, 0x7E0, None): [
b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.shiftByWire, 0x732, None): [ (Ecu.shiftByWire, 0x732, None): [
b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.FOCUS_MK4: { CAR.FOCUS_MK4: {
@ -146,10 +164,3 @@ FW_VERSIONS = {
], ],
}, },
} }
DBC = {
CAR.ESCAPE_MK4: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR),
CAR.EXPLORER_MK6: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR),
CAR.FOCUS_MK4: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR),
}

@ -2,7 +2,7 @@
import capnp import capnp
from dataclasses import dataclass, field from dataclasses import dataclass, field
import struct import struct
from typing import Dict, List from typing import Dict, List, Optional, Tuple
import panda.python.uds as uds import panda.python.uds as uds
@ -64,3 +64,5 @@ class FwQueryConfig:
requests: List[Request] requests: List[Request]
# Overrides and removes from essential ecus for specific models and ecus (exact matching) # Overrides and removes from essential ecus for specific models and ecus (exact matching)
non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict)
# Ecus added for data collection, not to be fingerprinted on
extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list)

@ -1,5 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import traceback
from collections import defaultdict from collections import defaultdict
from typing import Any, Optional, Set, Tuple from typing import Any, Optional, Set, Tuple
from tqdm import tqdm from tqdm import tqdm
@ -195,14 +194,14 @@ def get_brand_ecu_matches(ecu_rx_addrs):
return brand_matches return brand_matches
def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=False, progress=False): def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False):
"""Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" """Queries for FW versions ordering brands by likelihood, breaks when exact match is found"""
all_car_fw = [] all_car_fw = []
brand_matches = get_brand_ecu_matches(ecu_rx_addrs) brand_matches = get_brand_ecu_matches(ecu_rx_addrs)
for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True): for brand in sorted(brand_matches, key=lambda b: len(brand_matches[b]), reverse=True):
car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, debug=debug, progress=progress) car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, num_pandas=num_pandas, debug=debug, progress=progress)
all_car_fw.extend(car_fw) all_car_fw.extend(car_fw)
# Try to match using FW returned from this brand only # Try to match using FW returned from this brand only
matches = match_fw_to_car_exact(build_fw_dict(car_fw)) matches = match_fw_to_car_exact(build_fw_dict(car_fw))
@ -212,8 +211,13 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=Fa
return all_car_fw return all_car_fw
def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, debug=False, progress=False): def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False):
versions = VERSIONS.copy() versions = VERSIONS.copy()
# Each brand can define extra ECUs to query for data collection
for brand, config in FW_QUERY_CONFIGS.items():
versions[brand]["debug"] = {ecu: [] for ecu in config.extra_ecus}
if query_brand is not None: if query_brand is not None:
versions = {query_brand: versions[query_brand]} versions = {query_brand: versions[query_brand]}
@ -248,29 +252,33 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1,
for addr in tqdm(addrs, disable=not progress): for addr in tqdm(addrs, disable=not progress):
for addr_chunk in chunks(addr): for addr_chunk in chunks(addr):
for brand, r in requests: for brand, r in requests:
# Skip query if no panda available
if r.bus > num_pandas * 4 - 1:
continue
try: try:
addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and
(len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)] (len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)]
if addrs: if addrs:
query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug) query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug)
for (addr, rx_addr), version in query.get_data(timeout).items(): for (tx_addr, sub_addr), version in query.get_data(timeout).items():
f = car.CarParams.CarFw.new_message() f = car.CarParams.CarFw.new_message()
f.ecu = ecu_types.get((brand, addr[0], addr[1]), Ecu.unknown) f.ecu = ecu_types.get((brand, tx_addr, sub_addr), Ecu.unknown)
f.fwVersion = version f.fwVersion = version
f.address = addr[0] f.address = tx_addr
f.responseAddress = rx_addr f.responseAddress = uds.get_rx_addr_for_tx_addr(tx_addr, r.rx_offset)
f.request = r.request f.request = r.request
f.brand = brand f.brand = brand
f.bus = r.bus f.bus = r.bus
if addr[1] is not None: if sub_addr is not None:
f.subAddress = addr[1] f.subAddress = sub_addr
car_fw.append(f) car_fw.append(f)
except Exception: except Exception:
cloudlog.warning(f"FW query exception: {traceback.format_exc()}") cloudlog.exception("FW query exception")
return car_fw return car_fw
@ -288,6 +296,7 @@ if __name__ == "__main__":
args = parser.parse_args() args = parser.parse_args()
logcan = messaging.sub_sock('can') logcan = messaging.sub_sock('can')
pandaStates_sock = messaging.sub_sock('pandaStates')
sendcan = messaging.pub_sock('sendcan') sendcan = messaging.pub_sock('sendcan')
extra: Any = None extra: Any = None
@ -301,16 +310,17 @@ if __name__ == "__main__":
extra = {"any": {"debug": extra}} extra = {"any": {"debug": extra}}
time.sleep(1.) time.sleep(1.)
num_pandas = len(messaging.recv_one_retry(pandaStates_sock).pandaStates)
t = time.time() t = time.time()
print("Getting vin...") print("Getting vin...")
addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}') print(f'RX: {hex(vin_rx_addr)}, VIN: {vin}')
print(f"Getting VIN took {time.time() - t:.3f} s") print(f"Getting VIN took {time.time() - t:.3f} s")
print() print()
t = time.time() t = time.time()
fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, debug=args.debug, progress=True) fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, num_pandas=num_pandas, debug=args.debug, progress=True)
_, candidates = match_fw_to_car(fw_vers) _, candidates = match_fw_to_car(fw_vers)
print() print()

@ -5,10 +5,14 @@ from common.realtime import DT_CTRL
from opendbc.can.packer import CANPacker from opendbc.can.packer import CANPacker
from selfdrive.car import apply_std_steer_torque_limits from selfdrive.car import apply_std_steer_torque_limits
from selfdrive.car.gm import gmcan from selfdrive.car.gm import gmcan
from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, EV_CAR from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons
VisualAlert = car.CarControl.HUDControl.VisualAlert VisualAlert = car.CarControl.HUDControl.VisualAlert
NetworkLocation = car.CarParams.NetworkLocation NetworkLocation = car.CarParams.NetworkLocation
LongCtrlState = car.CarControl.Actuators.LongControlState
# Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s
CAMERA_CANCEL_DELAY_FRAMES = 10
class CarController: class CarController:
@ -19,12 +23,15 @@ class CarController:
self.apply_gas = 0 self.apply_gas = 0
self.apply_brake = 0 self.apply_brake = 0
self.frame = 0 self.frame = 0
self.last_steer_frame = 0
self.last_button_frame = 0 self.last_button_frame = 0
self.cancel_counter = 0
self.lka_steering_cmd_counter_last = -1 self.lka_steering_cmd_counter = 0
self.sent_lka_steering_cmd = False
self.lka_icon_status_last = (False, False) self.lka_icon_status_last = (False, False)
self.params = CarControllerParams self.params = CarControllerParams(self.CP)
self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt']) self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt'])
self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar'])
@ -41,47 +48,59 @@ class CarController:
# Send CAN commands. # Send CAN commands.
can_sends = [] can_sends = []
# Steering (50Hz) # Steering (Active: 50Hz, inactive: 10Hz)
# Attempt to sync with camera on startup at 50Hz, first few msgs are blocked
init_lka_counter = not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera
steer_step = self.params.INACTIVE_STEER_STEP
if CC.latActive or init_lka_counter:
steer_step = self.params.ACTIVE_STEER_STEP
# Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the
# next Panda loopback confirmation in the current CS frame. # next Panda loopback confirmation in the current CS frame.
if CS.lka_steering_cmd_counter != self.lka_steering_cmd_counter_last: if CS.loopback_lka_steering_cmd_updated:
self.lka_steering_cmd_counter_last = CS.lka_steering_cmd_counter self.lka_steering_cmd_counter += 1
elif (self.frame % self.params.STEER_STEP) == 0: self.sent_lka_steering_cmd = True
elif (self.frame - self.last_steer_frame) >= steer_step:
# Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus
if init_lka_counter:
self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1
if CC.latActive: if CC.latActive:
new_steer = int(round(actuators.steer * self.params.STEER_MAX)) new_steer = int(round(actuators.steer * self.params.STEER_MAX))
apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params)
else: else:
apply_steer = 0 apply_steer = 0
self.last_steer_frame = self.frame
self.apply_steer_last = apply_steer self.apply_steer_last = apply_steer
# GM EPS faults on any gap in received message counters. To handle transient OP/Panda safety sync issues at the idx = self.lka_steering_cmd_counter % 4
# moment of disengaging, increment the counter based on the last message known to pass Panda safety checks.
idx = (CS.lka_steering_cmd_counter + 1) % 4
can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive)) can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive))
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
# Gas/regen, brakes, and UI commands - all at 25Hz # Gas/regen, brakes, and UI commands - all at 25Hz
if self.frame % 4 == 0: if self.frame % 4 == 0:
if not CC.longActive: if not CC.longActive:
# Stock ECU sends max regen when not enabled # ASCM sends max regen when not enabled
self.apply_gas = self.params.MAX_ACC_REGEN self.apply_gas = self.params.INACTIVE_REGEN
self.apply_brake = 0 self.apply_brake = 0
else: else:
if self.CP.carFingerprint in EV_CAR: self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_gas = int(round(interp(actuators.accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
self.apply_brake = int(round(interp(actuators.accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
else:
self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V)))
self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V)))
idx = (self.frame // 4) % 4 idx = (self.frame // 4) % 4
at_full_stop = CC.longActive and CS.out.standstill at_full_stop = CC.longActive and CS.out.standstill
near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE)
friction_brake_bus = CanBus.CHASSIS
# GM Camera exceptions
# TODO: can we always check the longControlState?
if self.CP.networkLocation == NetworkLocation.fwdCamera:
at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping
friction_brake_bus = CanBus.POWERTRAIN
# GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation
can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop)) can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.enabled, at_full_stop))
can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP))
# Send dashboard UI commands (ACC status) # Send dashboard UI commands (ACC status)
send_fcw = hud_alert == VisualAlert.fcw send_fcw = hud_alert == VisualAlert.fcw
@ -108,12 +127,21 @@ class CarController:
can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN) can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN)
else: else:
# While car is braking, cancel button causes ECM to enter a soft disable state with a fault status.
# A delayed cancellation allows camera to cancel and avoids a fault when user depresses brake quickly
self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0
# Stock longitudinal, integrated at camera # Stock longitudinal, integrated at camera
if (self.frame - self.last_button_frame) * DT_CTRL > 0.04: if (self.frame - self.last_button_frame) * DT_CTRL > 0.04:
if CC.cruiseControl.cancel: if self.cancel_counter > CAMERA_CANCEL_DELAY_FRAMES:
self.last_button_frame = self.frame self.last_button_frame = self.frame
can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL)) can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL))
if self.CP.networkLocation == NetworkLocation.fwdCamera:
# Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1
if self.frame % 10 == 0:
can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status))
# Show green icon when LKA torque is applied, and # Show green icon when LKA torque is applied, and
# alarming orange icon when approaching torque limit. # alarming orange icon when approaching torque limit.
# If not sent again, LKA icon disappears in about 5 seconds. # If not sent again, LKA icon disappears in about 5 seconds.

@ -1,3 +1,4 @@
import copy
from cereal import car from cereal import car
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from common.numpy_fast import mean from common.numpy_fast import mean
@ -8,6 +9,7 @@ from selfdrive.car.gm.values import DBC, AccState, CanBus, STEER_THRESHOLD
TransmissionType = car.CarParams.TransmissionType TransmissionType = car.CarParams.TransmissionType
NetworkLocation = car.CarParams.NetworkLocation NetworkLocation = car.CarParams.NetworkLocation
STANDSTILL_THRESHOLD = 10 * 0.0311 * CV.KPH_TO_MS
class CarState(CarStateBase): class CarState(CarStateBase):
@ -15,7 +17,8 @@ class CarState(CarStateBase):
super().__init__(CP) super().__init__(CP)
can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) can_define = CANDefine(DBC[CP.carFingerprint]["pt"])
self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"]
self.lka_steering_cmd_counter = 0 self.loopback_lka_steering_cmd_updated = False
self.camera_lka_steering_cmd_counter = 0
self.buttons_counter = 0 self.buttons_counter = 0
def update(self, pt_cp, cam_cp, loopback_cp): def update(self, pt_cp, cam_cp, loopback_cp):
@ -24,6 +27,13 @@ class CarState(CarStateBase):
self.prev_cruise_buttons = self.cruise_buttons self.prev_cruise_buttons = self.cruise_buttons
self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"]
self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"]
self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"])
self.moving_backward = pt_cp.vl["EBCMWheelSpdRear"]["MovingBackward"] != 0
# Variables used for avoiding LKAS faults
self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0
if self.CP.networkLocation == NetworkLocation.fwdCamera:
self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
ret.wheelSpeeds = self.get_wheel_speeds( ret.wheelSpeeds = self.get_wheel_speeds(
pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"],
@ -33,20 +43,27 @@ class CarState(CarStateBase):
) )
ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.01 # sample rear wheel speeds, standstill=True if ECM allows engagement with brake
ret.standstill = ret.wheelSpeeds.rl <= STANDSTILL_THRESHOLD and ret.wheelSpeeds.rr <= STANDSTILL_THRESHOLD
if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1: if pt_cp.vl["ECMPRDNL2"]["ManualMode"] == 1:
ret.gearShifter = self.parse_gear_shifter("T") ret.gearShifter = self.parse_gear_shifter("T")
else: else:
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None))
# Brake pedal's potentiometer returns near-zero reading even when pedal is not pressed. ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"]
ret.brake = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] / 0xd0 if self.CP.networkLocation == NetworkLocation.fwdCamera:
ret.brakePressed = pt_cp.vl["EBCMBrakePedalPosition"]["BrakePedalPosition"] >= 10 ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0
else:
# Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe
# that the brake is being intermittently pressed without user interaction.
# To avoid a cruise fault we need to use a conservative brake position threshold
# https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf
ret.brakePressed = ret.brake >= 8
# Regen braking is braking # Regen braking is braking
if self.CP.transmissionType == TransmissionType.direct: if self.CP.transmissionType == TransmissionType.direct:
ret.brakePressed = ret.brakePressed or pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0 ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0
ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254. ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254.
ret.gasPressed = ret.gas > 1e-5 ret.gasPressed = ret.gas > 1e-5
@ -56,7 +73,6 @@ class CarState(CarStateBase):
ret.steeringTorque = pt_cp.vl["PSCMStatus"]["LKADriverAppldTrq"] ret.steeringTorque = pt_cp.vl["PSCMStatus"]["LKADriverAppldTrq"]
ret.steeringTorqueEps = pt_cp.vl["PSCMStatus"]["LKATorqueDelivered"] ret.steeringTorqueEps = pt_cp.vl["PSCMStatus"]["LKATorqueDelivered"]
ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD
self.lka_steering_cmd_counter = loopback_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"]
# 0 inactive, 1 active, 2 temporarily limited, 3 failed # 0 inactive, 1 active, 2 temporarily limited, 3 failed
self.lkas_status = pt_cp.vl["PSCMStatus"]["LKATorqueDeliveredStatus"] self.lkas_status = pt_cp.vl["PSCMStatus"]["LKATorqueDeliveredStatus"]
@ -83,6 +99,10 @@ class CarState(CarStateBase):
ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL
if self.CP.networkLocation == NetworkLocation.fwdCamera: if self.CP.networkLocation == NetworkLocation.fwdCamera:
ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS
ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0
# openpilot controls nonAdaptive when not pcmCruise
if self.CP.pcmCruise:
ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3)
return ret return ret
@ -91,8 +111,17 @@ class CarState(CarStateBase):
signals = [] signals = []
checks = [] checks = []
if CP.networkLocation == NetworkLocation.fwdCamera: if CP.networkLocation == NetworkLocation.fwdCamera:
signals.append(("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus")) signals += [
checks.append(("ASCMActiveCruiseControlStatus", 25)) ("AEBCmdActive", "AEBCmd"),
("RollingCounter", "ASCMLKASteeringCmd"),
("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"),
("ACCCruiseState", "ASCMActiveCruiseControlStatus"),
]
checks += [
("AEBCmd", 10),
("ASCMLKASteeringCmd", 10),
("ASCMActiveCruiseControlStatus", 25),
]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA) return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.CAMERA)
@ -100,7 +129,7 @@ class CarState(CarStateBase):
def get_can_parser(CP): def get_can_parser(CP):
signals = [ signals = [
# sig_name, sig_address # sig_name, sig_address
("BrakePedalPosition", "EBCMBrakePedalPosition"), ("BrakePedalPos", "ECMAcceleratorPos"),
("FrontLeftDoor", "BCMDoorBeltStatus"), ("FrontLeftDoor", "BCMDoorBeltStatus"),
("FrontRightDoor", "BCMDoorBeltStatus"), ("FrontRightDoor", "BCMDoorBeltStatus"),
("RearLeftDoor", "BCMDoorBeltStatus"), ("RearLeftDoor", "BCMDoorBeltStatus"),
@ -118,14 +147,21 @@ class CarState(CarStateBase):
("FRWheelSpd", "EBCMWheelSpdFront"), ("FRWheelSpd", "EBCMWheelSpdFront"),
("RLWheelSpd", "EBCMWheelSpdRear"), ("RLWheelSpd", "EBCMWheelSpdRear"),
("RRWheelSpd", "EBCMWheelSpdRear"), ("RRWheelSpd", "EBCMWheelSpdRear"),
("MovingBackward", "EBCMWheelSpdRear"),
("PRNDL2", "ECMPRDNL2"), ("PRNDL2", "ECMPRDNL2"),
("ManualMode", "ECMPRDNL2"), ("ManualMode", "ECMPRDNL2"),
("LKADriverAppldTrq", "PSCMStatus"), ("LKADriverAppldTrq", "PSCMStatus"),
("LKATorqueDelivered", "PSCMStatus"), ("LKATorqueDelivered", "PSCMStatus"),
("LKATorqueDeliveredStatus", "PSCMStatus"), ("LKATorqueDeliveredStatus", "PSCMStatus"),
("HandsOffSWlDetectionStatus", "PSCMStatus"),
("HandsOffSWDetectionMode", "PSCMStatus"),
("LKATotalTorqueDelivered", "PSCMStatus"),
("PSCMStatusChecksum", "PSCMStatus"),
("RollingCounter", "PSCMStatus"),
("TractionControlOn", "ESPStatus"), ("TractionControlOn", "ESPStatus"),
("ParkBrake", "VehicleIgnitionAlt"), ("ParkBrake", "VehicleIgnitionAlt"),
("CruiseMainOn", "ECMEngineStatus"), ("CruiseMainOn", "ECMEngineStatus"),
("BrakePressed", "ECMEngineStatus"),
] ]
checks = [ checks = [
@ -141,7 +177,7 @@ class CarState(CarStateBase):
("ASCMSteeringButton", 33), ("ASCMSteeringButton", 33),
("ECMEngineStatus", 100), ("ECMEngineStatus", 100),
("PSCMSteeringAngle", 100), ("PSCMSteeringAngle", 100),
("EBCMBrakePedalPosition", 100), ("ECMAcceleratorPos", 80),
] ]
if CP.transmissionType == TransmissionType.direct: if CP.transmissionType == TransmissionType.direct:
@ -157,9 +193,7 @@ class CarState(CarStateBase):
] ]
checks = [ checks = [
("ASCMLKASteeringCmd", 10), # 10 Hz is the stock inactive rate (every 100ms). ("ASCMLKASteeringCmd", 0),
# While active 50 Hz (every 20 ms) is normal
# EPS will tolerate around 200ms when active before faulting
] ]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK) return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, CanBus.LOOPBACK, enforce_checks=False)

@ -1,4 +1,6 @@
from selfdrive.car import make_can_msg from selfdrive.car import make_can_msg
from selfdrive.car.gm.values import CAR
def create_buttons(packer, bus, idx, button): def create_buttons(packer, bus, idx, button):
values = { values = {
@ -7,8 +9,15 @@ def create_buttons(packer, bus, idx, button):
} }
return packer.make_can_msg("ASCMSteeringButton", bus, values) return packer.make_can_msg("ASCMSteeringButton", bus, values)
def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
def create_pscm_status(packer, bus, pscm_status):
checksum_mod = int(1 - pscm_status["HandsOffSWlDetectionStatus"]) << 5
pscm_status["HandsOffSWlDetectionStatus"] = 1
pscm_status["PSCMStatusChecksum"] += checksum_mod
return packer.make_can_msg("PSCMStatus", bus, pscm_status)
def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
values = { values = {
"LKASteeringCmdActive": lkas_active, "LKASteeringCmdActive": lkas_active,
"LKASteeringCmd": apply_steer, "LKASteeringCmd": apply_steer,
@ -18,15 +27,17 @@ def create_steering_control(packer, bus, apply_steer, idx, lkas_active):
return packer.make_can_msg("ASCMLKASteeringCmd", bus, values) return packer.make_can_msg("ASCMLKASteeringCmd", bus, values)
def create_adas_keepalive(bus): def create_adas_keepalive(bus):
dat = b"\x00\x00\x00\x00\x00\x00\x00" dat = b"\x00\x00\x00\x00\x00\x00\x00"
return [make_can_msg(0x409, dat, bus), make_can_msg(0x40a, dat, bus)] return [make_can_msg(0x409, dat, bus), make_can_msg(0x40a, dat, bus)]
def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_stop):
def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop):
values = { values = {
"GasRegenCmdActive": acc_engaged, "GasRegenCmdActive": enabled,
"RollingCounter": idx, "RollingCounter": idx,
"GasRegenCmdActiveInv": 1 - acc_engaged, "GasRegenCmdActiveInv": 1 - enabled,
"GasRegenCmd": throttle, "GasRegenCmd": throttle,
"GasRegenFullStopActive": at_full_stop, "GasRegenFullStopActive": at_full_stop,
"GasRegenAlwaysOne": 1, "GasRegenAlwaysOne": 1,
@ -41,15 +52,21 @@ def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_st
return packer.make_can_msg("ASCMGasRegenCmd", bus, values) return packer.make_can_msg("ASCMGasRegenCmd", bus, values)
def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_full_stop):
def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP):
mode = 0x1 mode = 0x1
# TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake
if enabled and CP.carFingerprint in (CAR.BOLT_EUV,):
mode = 0x9
if apply_brake > 0: if apply_brake > 0:
mode = 0xa mode = 0xa
if at_full_stop: if at_full_stop:
mode = 0xd mode = 0xd
# TODO: this is to have GM bringing the car to complete stop, # TODO: this is to have GM bringing the car to complete stop,
# but currently it conflicts with OP controls, so turned off. # but currently it conflicts with OP controls, so turned off. Not set by all cars
#elif near_stop: #elif near_stop:
# mode = 0xb # mode = 0xb
@ -57,44 +74,48 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_f
checksum = (0x10000 - (mode << 12) - brake - idx) & 0xffff checksum = (0x10000 - (mode << 12) - brake - idx) & 0xffff
values = { values = {
"RollingCounter" : idx, "RollingCounter": idx,
"FrictionBrakeMode" : mode, "FrictionBrakeMode": mode,
"FrictionBrakeChecksum": checksum, "FrictionBrakeChecksum": checksum,
"FrictionBrakeCmd" : -apply_brake "FrictionBrakeCmd": -apply_brake
} }
return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values) return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values)
def create_acc_dashboard_command(packer, bus, acc_engaged, target_speed_kph, lead_car_in_sight, fcw):
def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, lead_car_in_sight, fcw):
target_speed = min(target_speed_kph, 255) target_speed = min(target_speed_kph, 255)
values = { values = {
"ACCAlwaysOne" : 1, "ACCAlwaysOne": 1,
"ACCResumeButton" : 0, "ACCResumeButton": 0,
"ACCSpeedSetpoint" : target_speed, "ACCSpeedSetpoint": target_speed,
"ACCGapLevel" : 3 * acc_engaged, # 3 "far", 0 "inactive" "ACCGapLevel": 3 * enabled, # 3 "far", 0 "inactive"
"ACCCmdActive" : acc_engaged, "ACCCmdActive": enabled,
"ACCAlwaysOne2" : 1, "ACCAlwaysOne2": 1,
"ACCLeadCar" : lead_car_in_sight, "ACCLeadCar": lead_car_in_sight,
"FCWAlert": 0x3 if fcw else 0 "FCWAlert": 0x3 if fcw else 0
} }
return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values) return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values)
def create_adas_time_status(bus, tt, idx): def create_adas_time_status(bus, tt, idx):
dat = [(tt >> 20) & 0xff, (tt >> 12) & 0xff, (tt >> 4) & 0xff, dat = [(tt >> 20) & 0xff, (tt >> 12) & 0xff, (tt >> 4) & 0xff,
((tt & 0xf) << 4) + (idx << 2)] ((tt & 0xf) << 4) + (idx << 2)]
chksum = 0x1000 - dat[0] - dat[1] - dat[2] - dat[3] chksum = 0x1000 - dat[0] - dat[1] - dat[2] - dat[3]
chksum = chksum & 0xfff chksum = chksum & 0xfff
dat += [0x40 + (chksum >> 8), chksum & 0xff, 0x12] dat += [0x40 + (chksum >> 8), chksum & 0xff, 0x12]
return make_can_msg(0xa1, bytes(dat), bus) return make_can_msg(0xa1, bytes(dat), bus)
def create_adas_steering_status(bus, idx): def create_adas_steering_status(bus, idx):
dat = [idx << 6, 0xf0, 0x20, 0, 0, 0] dat = [idx << 6, 0xf0, 0x20, 0, 0, 0]
chksum = 0x60 + sum(dat) chksum = 0x60 + sum(dat)
dat += [chksum >> 8, chksum & 0xff] dat += [chksum >> 8, chksum & 0xff]
return make_can_msg(0x306, bytes(dat), bus) return make_can_msg(0x306, bytes(dat), bus)
def create_adas_accelerometer_speed_status(bus, speed_ms, idx): def create_adas_accelerometer_speed_status(bus, speed_ms, idx):
spd = int(speed_ms * 16) & 0xfff spd = int(speed_ms * 16) & 0xfff
accel = 0 & 0xfff accel = 0 & 0xfff
@ -108,6 +129,7 @@ def create_adas_accelerometer_speed_status(bus, speed_ms, idx):
dat += [(idx << 5) + (far_range_mode << 4) + (near_range_mode << 3) + (chksum >> 8), chksum & 0xff] dat += [(idx << 5) + (far_range_mode << 4) + (near_range_mode << 3) + (chksum >> 8), chksum & 0xff]
return make_can_msg(0x308, bytes(dat), bus) return make_can_msg(0x308, bytes(dat), bus)
def create_adas_headlights_status(packer, bus): def create_adas_headlights_status(packer, bus):
values = { values = {
"Always42": 0x42, "Always42": 0x42,
@ -115,6 +137,7 @@ def create_adas_headlights_status(packer, bus):
} }
return packer.make_can_msg("ASCMHeadlight", bus, values) return packer.make_can_msg("ASCMHeadlight", bus, values)
def create_lka_icon_command(bus, active, critical, steer): def create_lka_icon_command(bus, active, critical, steer):
if active and steer == 1: if active and steer == 1:
if critical: if critical:

@ -20,8 +20,7 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@staticmethod @staticmethod
def get_pid_accel_limits(CP, current_speed, cruise_speed): def get_pid_accel_limits(CP, current_speed, cruise_speed):
params = CarControllerParams() return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX
return params.ACCEL_MIN, params.ACCEL_MAX
# Determined by iteratively plotting and minimizing error for f(angle, speed) = steer. # Determined by iteratively plotting and minimizing error for f(angle, speed) = steer.
@staticmethod @staticmethod
@ -56,42 +55,63 @@ class CarInterface(CarInterfaceBase):
else: else:
ret.transmissionType = TransmissionType.automatic ret.transmissionType = TransmissionType.automatic
ret.longitudinalTuning.deadzoneBP = [0.]
ret.longitudinalTuning.deadzoneV = [0.15]
ret.longitudinalTuning.kpBP = [5., 35.]
ret.longitudinalTuning.kiBP = [0.]
if candidate in CAMERA_ACC_CAR: if candidate in CAMERA_ACC_CAR:
ret.openpilotLongitudinalControl = False ret.experimentalLongitudinalAvailable = True
ret.networkLocation = NetworkLocation.fwdCamera ret.networkLocation = NetworkLocation.fwdCamera
ret.radarOffCan = True # no radar ret.radarOffCan = True # no radar
ret.pcmCruise = True ret.pcmCruise = True
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM
ret.minEnableSpeed = 5 * CV.KPH_TO_MS
if experimental_long:
ret.pcmCruise = False
ret.openpilotLongitudinalControl = True
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG
# Tuning
ret.longitudinalTuning.kpV = [2.0, 1.5]
ret.longitudinalTuning.kiV = [0.72]
ret.stopAccel = -2.0
ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling
ret.vEgoStopping = 0.25
ret.vEgoStarting = 0.25
ret.longitudinalActuatorDelayUpperBound = 0.5
else: # ASCM, OBD-II harness else: # ASCM, OBD-II harness
ret.openpilotLongitudinalControl = True ret.openpilotLongitudinalControl = True
ret.networkLocation = NetworkLocation.gateway ret.networkLocation = NetworkLocation.gateway
ret.radarOffCan = False ret.radarOffCan = False
ret.pcmCruise = False # stock non-adaptive cruise control is kept off ret.pcmCruise = False # stock non-adaptive cruise control is kept off
# supports stop and go, but initial engage must (conservatively) be above 18mph
ret.minEnableSpeed = 18 * CV.MPH_TO_MS
# Tuning
ret.longitudinalTuning.kpV = [2.4, 1.5]
ret.longitudinalTuning.kiV = [0.36]
# These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is # These cars likely still work fine. Once a user confirms each car works and a test route is
# added to selfdrive/car/tests/routes.py, we can remove it from this list. # added to selfdrive/car/tests/routes.py, we can remove it from this list.
ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL} ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX, CAR.BOLT_EV}
# Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below.
ret.minSteerSpeed = 10 * CV.KPH_TO_MS # Some GMs need some tolerance above 10 kph to avoid a fault
ret.minSteerSpeed = 10.1 * CV.KPH_TO_MS
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]]
ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594 ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594
ret.steerActuatorDelay = 0.1 # Default delay, not measured yet ret.steerActuatorDelay = 0.1 # Default delay, not measured yet
tire_stiffness_factor = 0.444 # not optimized yet tire_stiffness_factor = 0.444 # not optimized yet
ret.longitudinalTuning.kpBP = [5., 35.]
ret.longitudinalTuning.kpV = [2.4, 1.5]
ret.longitudinalTuning.kiBP = [0.]
ret.longitudinalTuning.kiV = [0.36]
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz
# supports stop and go, but initial engage must (conservatively) be above 18mph
ret.minEnableSpeed = 18 * CV.MPH_TO_MS
if candidate == CAR.VOLT: if candidate == CAR.VOLT:
ret.mass = 1607. + STD_CARGO_KG ret.mass = 1607. + STD_CARGO_KG
ret.wheelbase = 2.69 ret.wheelbase = 2.69
@ -138,31 +158,29 @@ class CarInterface(CarInterfaceBase):
ret.mass = 1601. + STD_CARGO_KG ret.mass = 1601. + STD_CARGO_KG
ret.wheelbase = 2.78 ret.wheelbase = 2.78
ret.steerRatio = 15.3 ret.steerRatio = 15.3
ret.centerToFront = ret.wheelbase * 0.49 ret.centerToFront = ret.wheelbase * 0.5
elif candidate == CAR.ESCALADE_ESV: elif candidate == CAR.ESCALADE_ESV:
ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.minEnableSpeed = -1. # engage speed is decided by pcm
ret.mass = 2739. + STD_CARGO_KG ret.mass = 2739. + STD_CARGO_KG
ret.wheelbase = 3.302 ret.wheelbase = 3.302
ret.steerRatio = 17.3 ret.steerRatio = 17.3
ret.centerToFront = ret.wheelbase * 0.49 ret.centerToFront = ret.wheelbase * 0.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[10., 41.0], [10., 41.0]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.13, 0.24], [0.01, 0.02]]
ret.lateralTuning.pid.kf = 0.000045 ret.lateralTuning.pid.kf = 0.000045
tire_stiffness_factor = 1.0 tire_stiffness_factor = 1.0
elif candidate == CAR.BOLT_EUV: elif candidate in (CAR.BOLT_EV, CAR.BOLT_EUV):
ret.minEnableSpeed = -1
ret.mass = 1669. + STD_CARGO_KG ret.mass = 1669. + STD_CARGO_KG
ret.wheelbase = 2.675 ret.wheelbase = 2.63779
ret.steerRatio = 16.8 ret.steerRatio = 16.8
ret.centerToFront = ret.wheelbase * 0.4 ret.centerToFront = 2.15 # measured
tire_stiffness_factor = 1.0 tire_stiffness_factor = 1.0
ret.steerActuatorDelay = 0.2 ret.steerActuatorDelay = 0.2
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.SILVERADO: elif candidate == CAR.SILVERADO:
ret.minEnableSpeed = -1
ret.mass = 2200. + STD_CARGO_KG ret.mass = 2200. + STD_CARGO_KG
ret.wheelbase = 3.75 ret.wheelbase = 3.75
ret.steerRatio = 16.3 ret.steerRatio = 16.3
@ -170,6 +188,13 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor = 1.0 tire_stiffness_factor = 1.0
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.EQUINOX:
ret.mass = 3500. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.72
ret.steerRatio = 14.4
ret.centerToFront = ret.wheelbase * 0.4
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
# TODO: get actual value, for now starting with reasonable value for # TODO: get actual value, for now starting with reasonable value for
# civic and scaling by mass and wheelbase # civic and scaling by mass and wheelbase
ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase)
@ -186,24 +211,31 @@ class CarInterface(CarInterfaceBase):
ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback)
if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT:
be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS) buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)]
# Handle ACCButtons changing buttons mid-press
if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS:
buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS))
# Suppress resume button if we're resuming from stop so we don't adjust speed. ret.buttonEvents = buttonEvents
if be.type == ButtonType.accelCruise and (ret.cruiseState.enabled and ret.standstill):
be.type = ButtonType.unknown
ret.buttonEvents = [be]
# The ECM allows enabling on falling edge of set, but only rising edge of resume
events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low,
GearShifter.eco, GearShifter.manumatic], GearShifter.eco, GearShifter.manumatic],
pcm_enable=self.CP.pcmCruise) pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,))
if not self.CP.pcmCruise:
if ret.vEgo < self.CP.minEnableSpeed: if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.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 = ret.vEgo < self.CP.minEnableSpeed or self.CS.moving_backward
if below_min_enable_speed and not (ret.standstill and ret.brake >= 20 and
self.CP.networkLocation == NetworkLocation.fwdCamera):
events.add(EventName.belowEngageSpeed) events.add(EventName.belowEngageSpeed)
if ret.cruiseState.standstill: if ret.cruiseState.standstill:
events.add(EventName.resumeRequired) events.add(EventName.resumeRequired)
if ret.vEgo < self.CP.minSteerSpeed: if ret.vEgo < self.CP.minSteerSpeed:
events.add(car.CarEvent.EventName.belowSteerSpeed) events.add(EventName.belowSteerSpeed)
ret.events = events.to_msg() ret.events = events.to_msg()

@ -1,5 +1,5 @@
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass, field from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Dict, List, Union from typing import Dict, List, Union
@ -11,7 +11,8 @@ Ecu = car.CarParams.Ecu
class CarControllerParams: class CarControllerParams:
STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output
STEER_STEP = 2 # Control frames per command (50hz) ACTIVE_STEER_STEP = 2 # Active control frames per command (50hz)
INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz)
STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness
STEER_DELTA_DOWN = 17 STEER_DELTA_DOWN = 17
STEER_DRIVER_ALLOWANCE = 50 STEER_DRIVER_ALLOWANCE = 50
@ -23,13 +24,6 @@ class CarControllerParams:
ADAS_KEEPALIVE_STEP = 100 ADAS_KEEPALIVE_STEP = 100
CAMERA_KEEPALIVE_STEP = 100 CAMERA_KEEPALIVE_STEP = 100
# Volt gasbrake lookups
# TODO: These values should be confirmed on non-Volt vehicles
MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
ZERO_GAS = 2048 # Coasting
MAX_BRAKE = 350 # ~ -3.5 m/s^2 with regen
MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
# Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we # Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we
# perform the closed loop control, and might need some # perform the closed loop control, and might need some
# to apply some more braking if we're on a downhill slope. # to apply some more braking if we're on a downhill slope.
@ -38,16 +32,32 @@ class CarControllerParams:
ACCEL_MAX = 2. # m/s^2 ACCEL_MAX = 2. # m/s^2
ACCEL_MIN = -4. # m/s^2 ACCEL_MIN = -4. # m/s^2
EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] def __init__(self, CP):
EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] # Gas/brake lookups
self.ZERO_GAS = 2048 # Coasting
# ICE has much less engine braking force compared to regen in EVs, self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen
# lower threshold removes some braking deadzone
GAS_LOOKUP_BP = [-0.1, 0., ACCEL_MAX] if CP.carFingerprint in CAMERA_ACC_CAR:
BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] self.MAX_GAS = 3400
self.MAX_ACC_REGEN = 1514
GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS] self.INACTIVE_REGEN = 1554
BRAKE_LOOKUP_V = [MAX_BRAKE, 0.] # Camera ACC vehicles have no regen while enabled.
# Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly
max_regen_acceleration = 0.
else:
self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill.
self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen
self.INACTIVE_REGEN = 1404
# ICE has much less engine braking force compared to regen in EVs,
# lower threshold removes some braking deadzone
max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1
self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX]
self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS]
self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration]
self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.]
def __init__(self, CP): def __init__(self, CP):
pass pass
@ -61,8 +71,10 @@ class CAR:
ACADIA = "GMC ACADIA DENALI 2018" ACADIA = "GMC ACADIA DENALI 2018"
BUICK_REGAL = "BUICK REGAL ESSENCE 2018" BUICK_REGAL = "BUICK REGAL ESSENCE 2018"
ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016"
BOLT_EV = "CHEVROLET BOLT EV 2022"
BOLT_EUV = "CHEVROLET BOLT EUV 2022" BOLT_EUV = "CHEVROLET BOLT EUV 2022"
SILVERADO = "CHEVROLET SILVERADO 1500 2020" SILVERADO = "CHEVROLET SILVERADO 1500 2020"
EQUINOX = "CHEVROLET EQUINOX 2019"
class Footnote(Enum): class Footnote(Enum):
@ -74,24 +86,31 @@ class Footnote(Enum):
@dataclass @dataclass
class GMCarInfo(CarInfo): class GMCarInfo(CarInfo):
package: str = "Adaptive Cruise Control" package: str = "Adaptive Cruise Control (ACC)"
harness: Enum = Harness.obd_ii
footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) def init_make(self, CP: car.CarParams):
if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera:
self.harness = Harness.gm
else:
self.harness = Harness.obd_ii
self.footnotes.append(Footnote.OBD_II)
CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = {
CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"), CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"),
CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0), CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"),
CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"),
CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"),
CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"),
CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"),
CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"),
CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23"),
CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"),
CAR.SILVERADO: [ CAR.SILVERADO: [
GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"),
GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"),
], ],
CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"),
} }
@ -169,13 +188,17 @@ FINGERPRINTS = {
{ {
190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7 190: 6, 193: 8, 197: 8, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 3, 309: 8, 311: 8, 313: 8, 320: 4, 322: 7, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 460: 5, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 528: 5, 532: 6, 534: 2, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 761: 7, 789: 5, 800: 6, 801: 8, 810: 8, 840: 5, 842: 5, 844: 8, 848: 4, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
}], }],
CAR.EQUINOX: [
{
190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1930: 7
}],
} }
DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis'))
EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} EV_CAR = {CAR.VOLT, CAR.BOLT_EV, CAR.BOLT_EUV}
# We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness)
CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO} CAMERA_ACC_CAR = {CAR.BOLT_EV, CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX}
STEER_THRESHOLD = 1.0 STEER_THRESHOLD = 1.0

@ -99,6 +99,12 @@ HUDData = namedtuple("HUDData",
"lanes_visible", "fcw", "acc_alert", "steer_required"]) "lanes_visible", "fcw", "acc_alert", "steer_required"])
def rate_limit_steer(new_steer, last_steer):
# TODO just hardcoded ramp to min/max in 0.33s for all Honda
MAX_DELTA = 3 * DT_CTRL
return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA)
class CarController: class CarController:
def __init__(self, dbc_name, CP, VM): def __init__(self, dbc_name, CP, VM):
self.CP = CP self.CP = CP
@ -116,6 +122,7 @@ class CarController:
self.speed = 0.0 self.speed = 0.0
self.gas = 0.0 self.gas = 0.0
self.brake = 0.0 self.brake = 0.0
self.last_steer = 0.0
def update(self, CC, CS): def update(self, CC, CS):
actuators = CC.actuators actuators = CC.actuators
@ -130,6 +137,10 @@ class CarController:
accel = 0.0 accel = 0.0
gas, brake = 0.0, 0.0 gas, brake = 0.0, 0.0
# *** rate limit steer ***
limited_steer = rate_limit_steer(actuators.steer, self.last_steer)
self.last_steer = limited_steer
# *** apply brake hysteresis *** # *** apply brake hysteresis ***
pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady, pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady,
CS.out.vEgo, self.CP.carFingerprint) CS.out.vEgo, self.CP.carFingerprint)
@ -143,7 +154,7 @@ class CarController:
# **** process the car messages **** # **** process the car messages ****
# steer torque is converted back to CAN reference (positive when steering right) # steer torque is converted back to CAN reference (positive when steering right)
apply_steer = int(interp(-actuators.steer * self.params.STEER_MAX, apply_steer = int(interp(-limited_steer * self.params.STEER_MAX,
self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V)) self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V))
# Send CAN commands # Send CAN commands
@ -250,6 +261,7 @@ class CarController:
new_actuators.accel = self.accel new_actuators.accel = self.accel
new_actuators.gas = self.gas new_actuators.gas = self.gas
new_actuators.brake = self.brake new_actuators.brake = self.brake
new_actuators.steer = self.last_steer
self.frame += 1 self.frame += 1
return new_actuators, can_sends return new_actuators, can_sends

@ -24,6 +24,7 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg):
("MOTOR_TORQUE", "STEER_MOTOR_TORQUE"), ("MOTOR_TORQUE", "STEER_MOTOR_TORQUE"),
("STEER_TORQUE_SENSOR", "STEER_STATUS"), ("STEER_TORQUE_SENSOR", "STEER_STATUS"),
("IMPERIAL_UNIT", "CAR_SPEED"), ("IMPERIAL_UNIT", "CAR_SPEED"),
("ROUGH_CAR_SPEED_2", "CAR_SPEED"),
("LEFT_BLINKER", "SCM_FEEDBACK"), ("LEFT_BLINKER", "SCM_FEEDBACK"),
("RIGHT_BLINKER", "SCM_FEEDBACK"), ("RIGHT_BLINKER", "SCM_FEEDBACK"),
("SEATBELT_DRIVER_LAMP", "SEATBELT_STATUS"), ("SEATBELT_DRIVER_LAMP", "SEATBELT_STATUS"),
@ -150,6 +151,10 @@ class CarState(CarStateBase):
self.cruise_setting = 0 self.cruise_setting = 0
self.v_cruise_pcm_prev = 0 self.v_cruise_pcm_prev = 0
# When available we use cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] to populate vEgoCluster
# However, on cars without a digital speedometer this is not always present (HRV, FIT, CRV 2016, ILX and RDX)
self.dash_speed_seen = False
def update(self, cp, cp_cam, cp_body): def update(self, cp, cp_cam, cp_body):
ret = car.CarState.new_message() ret = car.CarState.new_message()
@ -203,6 +208,11 @@ class CarState(CarStateBase):
ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel ret.vEgoRaw = (1. - v_weight) * cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] * CV.KPH_TO_MS * self.CP.wheelSpeedFactor + v_weight * v_wheel
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
self.dash_speed_seen = self.dash_speed_seen or cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] > 1e-3
if self.dash_speed_seen:
conversion = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
ret.vEgoCluster = cp.vl["CAR_SPEED"]["ROUGH_CAR_SPEED_2"] * conversion
ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"] ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE"]
ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE_RATE"] ret.steeringRateDeg = cp.vl["STEERING_SENSORS"]["STEER_ANGLE_RATE"]
@ -237,9 +247,9 @@ class CarState(CarStateBase):
ret.cruiseState.standstill = acc_hud["CRUISE_SPEED"] == 252. ret.cruiseState.standstill = acc_hud["CRUISE_SPEED"] == 252.
# on certain cars, CRUISE_SPEED changes to imperial with car's unit setting # on certain cars, CRUISE_SPEED changes to imperial with car's unit setting
conversion_factor = CV.MPH_TO_MS if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS and not self.is_metric else CV.KPH_TO_MS conversion = CV.MPH_TO_MS if self.CP.carFingerprint in HONDA_BOSCH_RADARLESS and not self.is_metric else CV.KPH_TO_MS
# On set, cruise set speed pulses between 254~255 and the set speed prev is set to avoid this. # On set, cruise set speed pulses between 254~255 and the set speed prev is set to avoid this.
ret.cruiseState.speed = self.v_cruise_pcm_prev if acc_hud["CRUISE_SPEED"] > 160.0 else acc_hud["CRUISE_SPEED"] * conversion_factor ret.cruiseState.speed = self.v_cruise_pcm_prev if acc_hud["CRUISE_SPEED"] > 160.0 else acc_hud["CRUISE_SPEED"] * conversion
self.v_cruise_pcm_prev = ret.cruiseState.speed self.v_cruise_pcm_prev = ret.cruiseState.speed
else: else:
ret.cruiseState.speed = cp.vl["CRUISE"]["CRUISE_SPEED_PCM"] * CV.KPH_TO_MS ret.cruiseState.speed = cp.vl["CRUISE"]["CRUISE_SPEED_PCM"] * CV.KPH_TO_MS

@ -220,23 +220,17 @@ class CarInterface(CarInterfaceBase):
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
tire_stiffness_factor = 0.677 tire_stiffness_factor = 0.677
elif candidate == CAR.ODYSSEY: elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN):
ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 1900. + STD_CARGO_KG
ret.wheelbase = 3.00 ret.wheelbase = 3.00
ret.centerToFront = ret.wheelbase * 0.41 ret.centerToFront = ret.wheelbase * 0.41
ret.steerRatio = 14.35 # as spec ret.steerRatio = 14.35 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
elif candidate == CAR.ODYSSEY_CHN:
ret.mass = 1849.2 + STD_CARGO_KG # mean of 4 models in kg
ret.wheelbase = 2.90
ret.centerToFront = ret.wheelbase * 0.41 # from CAR.ODYSSEY
ret.steerRatio = 14.35
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82 tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
if candidate == CAR.ODYSSEY_CHN:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end
else:
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
elif candidate in (CAR.PILOT, CAR.PASSPORT): elif candidate in (CAR.PILOT, CAR.PASSPORT):
ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight

@ -105,42 +105,47 @@ class Footnote(Enum):
@dataclass @dataclass
class HondaCarInfo(CarInfo): class HondaCarInfo(CarInfo):
package: str = "Honda Sensing" package: str = "Honda Sensing"
min_steer_speed: float = 12. * CV.MPH_TO_MS
def init_make(self, CP: car.CarParams):
if CP.carFingerprint in HONDA_BOSCH:
self.harness = Harness.bosch_b if CP.carFingerprint in HONDA_BOSCH_RADARLESS else Harness.bosch_a
else:
self.harness = Harness.nidec
CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
CAR.ACCORD: [ CAR.ACCORD: [
HondaCarInfo("Honda Accord 2018-22", "All", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS),
HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS),
], ],
CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"),
CAR.CIVIC_BOSCH: [ CAR.CIVIC_BOSCH: [
HondaCarInfo("Honda Civic 2019-21", "All", video_link="https://www.youtube.com/watch?v=4Iz1Mz5LGF8", footnotes=[Footnote.CIVIC_DIESEL], min_steer_speed=2. * CV.MPH_TO_MS, harness=Harness.bosch_a), HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS),
HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a), HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS),
], ],
CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_BOSCH_DIESEL: None, # same platform
CAR.CIVIC_2022: [ CAR.CIVIC_2022: [
HondaCarInfo("Honda Civic 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), HondaCarInfo("Honda Civic 2022", "All"),
HondaCarInfo("Honda Civic Hatchback 2022", "All", min_steer_speed=0., harness=Harness.bosch_b), HondaCarInfo("Honda Civic Hatchback 2022", "All"),
], ],
CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS),
CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", harness=Harness.nidec), CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", harness=Harness.bosch_a), CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring
CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", harness=Harness.bosch_a), CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.FIT: HondaCarInfo("Honda Fit 2018-20", harness=Harness.nidec), CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.FREED: HondaCarInfo("Honda Freed 2020", harness=Harness.nidec), CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", harness=Harness.nidec), CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", min_steer_speed=0., harness=Harness.nidec), CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"),
CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey
CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", harness=Harness.nidec), CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", harness=Harness.nidec), CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", harness=Harness.nidec), CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", harness=Harness.nidec), CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS),
CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS),
CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS),
} }
FW_QUERY_CONFIG = FwQueryConfig( FW_QUERY_CONFIG = FwQueryConfig(
@ -447,7 +452,7 @@ FW_VERSIONS = {
b'78109-TED-Q510\x00\x00', b'78109-TED-Q510\x00\x00',
b'78109-TEG-A310\x00\x00', b'78109-TEG-A310\x00\x00',
], ],
(Ecu.fwdCamera, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TBA-A020\x00\x00', b'36161-TBA-A020\x00\x00',
b'36161-TBA-A030\x00\x00', b'36161-TBA-A030\x00\x00',
b'36161-TBA-A040\x00\x00', b'36161-TBA-A040\x00\x00',
@ -965,7 +970,7 @@ FW_VERSIONS = {
b'77959-THR-A110\x00\x00', b'77959-THR-A110\x00\x00',
b'77959-THR-X010\x00\x00', b'77959-THR-X010\x00\x00',
], ],
(Ecu.fwdCamera, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-THR-A020\x00\x00', b'36161-THR-A020\x00\x00',
b'36161-THR-A030\x00\x00', b'36161-THR-A030\x00\x00',
b'36161-THR-A110\x00\x00', b'36161-THR-A110\x00\x00',
@ -1032,6 +1037,23 @@ FW_VERSIONS = {
b'54008-THR-A020\x00\x00', b'54008-THR-A020\x00\x00',
], ],
}, },
CAR.ODYSSEY_CHN: {
(Ecu.eps, 0x18da30f1, None): [
b'39990-T6D-H220\x00\x00',
],
(Ecu.gateway, 0x18daeff1, None): [
b'38897-T6A-J010\x00\x00',
],
(Ecu.combinationMeter, 0x18da60f1, None): [
b'78109-T6A-F310\x00\x00',
],
(Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T6A-P040\x00\x00',
],
(Ecu.srs, 0x18da53f1, None): [
b'77959-T6A-P110\x00\x00',
],
},
CAR.PILOT: { CAR.PILOT: {
(Ecu.shiftByWire, 0x18da0bf1, None): [ (Ecu.shiftByWire, 0x18da0bf1, None): [
b'54008-TG7-A520\x00\x00', b'54008-TG7-A520\x00\x00',
@ -1069,7 +1091,7 @@ FW_VERSIONS = {
b'39990-TG7-A070\x00\x00', b'39990-TG7-A070\x00\x00',
b'39990-TGS-A230\x00\x00', b'39990-TGS-A230\x00\x00',
], ],
(Ecu.fwdCamera, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TG7-A310\x00\x00', b'36161-TG7-A310\x00\x00',
b'36161-TG7-A520\x00\x00', b'36161-TG7-A520\x00\x00',
b'36161-TG7-A630\x00\x00', b'36161-TG7-A630\x00\x00',
@ -1177,7 +1199,7 @@ FW_VERSIONS = {
b'57114-TX5-A220\x00\x00', b'57114-TX5-A220\x00\x00',
b'57114-TX4-A220\x00\x00', b'57114-TX4-A220\x00\x00',
], ],
(Ecu.fwdCamera, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-TX5-A030\x00\x00', b'36161-TX5-A030\x00\x00',
b'36161-TX4-A030\x00\x00', b'36161-TX4-A030\x00\x00',
], ],
@ -1274,7 +1296,7 @@ FW_VERSIONS = {
b'39990-T6Z-A030\x00\x00', b'39990-T6Z-A030\x00\x00',
b'39990-T6Z-A050\x00\x00', b'39990-T6Z-A050\x00\x00',
], ],
(Ecu.fwdCamera, 0x18dab0f1, None): [ (Ecu.fwdRadar, 0x18dab0f1, None): [
b'36161-T6Z-A020\x00\x00', b'36161-T6Z-A020\x00\x00',
b'36161-T6Z-A310\x00\x00', b'36161-T6Z-A310\x00\x00',
b'36161-T6Z-A420\x00\x00', b'36161-T6Z-A420\x00\x00',

@ -10,6 +10,12 @@ from selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerPar
VisualAlert = car.CarControl.HUDControl.VisualAlert VisualAlert = car.CarControl.HUDControl.VisualAlert
LongCtrlState = car.CarControl.Actuators.LongControlState LongCtrlState = car.CarControl.Actuators.LongControlState
# EPS faults if you apply torque while the steering angle is above 90 degrees for more than 1 second
# All slightly below EPS thresholds to avoid fault
MAX_ANGLE = 85
MAX_ANGLE_FRAMES = 89
MAX_ANGLE_CONSECUTIVE_FRAMES = 2
def process_hud_alert(enabled, fingerprint, hud_control): def process_hud_alert(enabled, fingerprint, hud_control):
sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw)) sys_warning = (hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw))
@ -40,22 +46,22 @@ class CarController:
self.CP = CP self.CP = CP
self.params = CarControllerParams(CP) self.params = CarControllerParams(CP)
self.packer = CANPacker(dbc_name) self.packer = CANPacker(dbc_name)
self.angle_limit_counter = 0
self.frame = 0 self.frame = 0
self.accel_last = 0
self.apply_steer_last = 0 self.apply_steer_last = 0
self.car_fingerprint = CP.carFingerprint self.car_fingerprint = CP.carFingerprint
self.last_button_frame = 0 self.last_button_frame = 0
self.accel = 0
def update(self, CC, CS): def update(self, CC, CS):
actuators = CC.actuators actuators = CC.actuators
hud_control = CC.hudControl hud_control = CC.hudControl
# Steering Torque # steering torque
# These cars have significantly more torque than most HKG. Limit to 70% of max.
steer = actuators.steer steer = actuators.steer
if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022):
# these cars have significantly more torque than most HKG; limit to 70% of max
steer = clip(steer, -0.7, 0.7) steer = clip(steer, -0.7, 0.7)
new_steer = int(round(steer * self.params.STEER_MAX)) new_steer = int(round(steer * self.params.STEER_MAX))
apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params)
@ -65,51 +71,90 @@ class CarController:
self.apply_steer_last = apply_steer self.apply_steer_last = apply_steer
# accel + longitudinal
accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
stopping = actuators.longControlState == LongCtrlState.stopping
set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_KPH if CS.is_metric else CV.MS_TO_MPH)
# HUD messages
sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint, sys_warning, sys_state, left_lane_warning, right_lane_warning = process_hud_alert(CC.enabled, self.car_fingerprint,
hud_control) hud_control)
can_sends = [] can_sends = []
# *** common hyundai stuff ***
# tester present - w/ no response (keeps relevant ECU disabled)
if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and self.CP.openpilotLongitudinalControl:
addr, bus = 0x7d0, 0
if self.CP.flags & HyundaiFlags.CANFD_HDA2.value:
addr, bus = 0x730, 5
can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus])
# >90 degree steering fault prevention
# Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault
if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE:
self.angle_limit_counter += 1
else:
self.angle_limit_counter = 0
# Cut steer actuation bit for two frames and hold torque with induced temporary fault
torque_fault = CC.latActive and self.angle_limit_counter > MAX_ANGLE_FRAMES
lat_active = CC.latActive and not torque_fault
if self.angle_limit_counter >= MAX_ANGLE_FRAMES + MAX_ANGLE_CONSECUTIVE_FRAMES:
self.angle_limit_counter = 0
# CAN-FD platforms
if self.CP.carFingerprint in CANFD_CAR: if self.CP.carFingerprint in CANFD_CAR:
hda2 = self.CP.flags & HyundaiFlags.CANFD_HDA2
hda2_long = hda2 and self.CP.openpilotLongitudinalControl
# steering control # steering control
can_sends.append(hyundaicanfd.create_lkas(self.packer, self.CP, CC.enabled, CC.latActive, apply_steer)) can_sends.extend(hyundaicanfd.create_steering_messages(self.packer, self.CP, CC.enabled, lat_active, apply_steer))
# block LFA on HDA2 # disable LFA on HDA2
if self.frame % 5 == 0 and (self.CP.flags & HyundaiFlags.CANFD_HDA2): if self.frame % 5 == 0 and hda2:
can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, CS.cam_0x2a4)) can_sends.append(hyundaicanfd.create_cam_0x2a4(self.packer, CS.cam_0x2a4))
# LFA and HDA icons # LFA and HDA icons
if self.frame % 2 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_HDA2): if self.frame % 5 == 0 and (not hda2 or hda2_long):
can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, CC.enabled)) can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled))
# button presses
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
# cruise cancel
if CC.cruiseControl.cancel:
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_cruise_info(self.packer, CS.cruise_info_copy, True))
self.last_button_frame = self.frame
else:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL))
self.last_button_frame = self.frame
# cruise standstill resume
elif CC.cruiseControl.resume:
if not (self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS):
can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL))
self.last_button_frame = self.frame
else:
# tester present - w/ no response (keeps radar disabled)
if self.CP.openpilotLongitudinalControl: if self.CP.openpilotLongitudinalControl:
if self.frame % 100 == 0: if hda2:
can_sends.append([0x7D0, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 0]) can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame))
if self.frame % 2 == 0:
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, CC.latActive, can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override,
CS.lkas11, sys_warning, sys_state, CC.enabled, set_speed_in_units))
hud_control.leftLaneVisible, hud_control.rightLaneVisible, self.accel_last = accel
left_lane_warning, right_lane_warning)) else:
# button presses
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
# cruise cancel
if CC.cruiseControl.cancel:
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
can_sends.append(hyundaicanfd.create_acc_cancel(self.packer, self.CP, CS.cruise_info))
self.last_button_frame = self.frame
else:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.CANCEL))
self.last_button_frame = self.frame
# cruise standstill resume
elif CC.cruiseControl.resume:
if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
# TODO: resume for alt button cars
pass
else:
for _ in range(20):
can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.RES_ACCEL))
self.last_button_frame = self.frame
else:
can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active,
torque_fault, CS.lkas11, sys_warning, sys_state, CC.enabled,
hud_control.leftLaneVisible, hud_control.rightLaneVisible,
left_lane_warning, right_lane_warning))
if not self.CP.openpilotLongitudinalControl: if not self.CP.openpilotLongitudinalControl:
if CC.cruiseControl.cancel: if CC.cruiseControl.cancel:
@ -122,24 +167,16 @@ class CarController:
self.last_button_frame = self.frame self.last_button_frame = self.frame
if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl:
accel = actuators.accel # TODO: unclear if this is needed
#TODO unclear if this is needed
jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0 jerk = 3.0 if actuators.longControlState == LongCtrlState.pid else 1.0
accel = clip(accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX)
stopping = actuators.longControlState == LongCtrlState.stopping
set_speed_in_units = hud_control.setSpeed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH)
can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2), can_sends.extend(hyundaican.create_acc_commands(self.packer, CC.enabled, accel, jerk, int(self.frame / 2),
hud_control.leadVisible, set_speed_in_units, stopping, CS.out.gasPressed)) hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override))
self.accel = accel
# 20 Hz LFA MFA message # 20 Hz LFA MFA message
if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, if self.frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021,
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.KONA_EV_2022,
CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022,
CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022): CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled))
# 5 Hz ACC options # 5 Hz ACC options
@ -152,7 +189,7 @@ class CarController:
new_actuators = actuators.copy() new_actuators = actuators.copy()
new_actuators.steer = apply_steer / self.params.STEER_MAX new_actuators.steer = apply_steer / self.params.STEER_MAX
new_actuators.accel = self.accel new_actuators.accel = accel
self.frame += 1 self.frame += 1
return new_actuators, can_sends return new_actuators, can_sends

@ -1,5 +1,6 @@
from collections import deque from collections import deque
import copy import copy
import math
from cereal import car from cereal import car
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
@ -9,6 +10,7 @@ from selfdrive.car.hyundai.values import HyundaiFlags, DBC, FEATURES, CAMERA_SCC
from selfdrive.car.interfaces import CarStateBase from selfdrive.car.interfaces import CarStateBase
PREV_BUTTON_SAMPLES = 8 PREV_BUTTON_SAMPLES = 8
CLUSTER_SAMPLE_RATE = 20 # frames
class CarState(CarStateBase): class CarState(CarStateBase):
@ -19,8 +21,9 @@ class CarState(CarStateBase):
self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES)
self.gear_msg_canfd = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
if CP.carFingerprint in CANFD_CAR: if CP.carFingerprint in CANFD_CAR:
self.shifter_values = can_define.dv["GEAR_SHIFTER"]["GEAR"] self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"]
elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]:
self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"] self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"]
elif self.CP.carFingerprint in FEATURES["use_tcu_gears"]: elif self.CP.carFingerprint in FEATURES["use_tcu_gears"]:
@ -28,10 +31,16 @@ class CarState(CarStateBase):
else: # preferred and elect gear methods use same definition else: # preferred and elect gear methods use same definition
self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"]
self.is_metric = False
self.brake_error = False self.brake_error = False
self.park_brake = False
self.buttons_counter = 0 self.buttons_counter = 0
self.cruise_info = {}
# On some cars, CLU15->CF_Clu_VehicleSpeed can oscillate faster than the dash updates. Sample at 5 Hz
self.cluster_speed = 0
self.cluster_speed_counter = CLUSTER_SAMPLE_RATE
self.params = CarControllerParams(CP) self.params = CarControllerParams(CP)
def update(self, cp, cp_cam): def update(self, cp, cp_cam):
@ -39,8 +48,9 @@ class CarState(CarStateBase):
return self.update_canfd(cp, cp_cam) return self.update_canfd(cp, cp_cam)
ret = car.CarState.new_message() ret = car.CarState.new_message()
cp_cruise = cp_cam if self.CP.carFingerprint in CAMERA_SCC_CAR else cp cp_cruise = cp_cam if self.CP.carFingerprint in CAMERA_SCC_CAR else cp
self.is_metric = cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] == 0
speed_conv = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
ret.doorOpen = any([cp.vl["CGW1"]["CF_Gway_DrvDrSw"], cp.vl["CGW1"]["CF_Gway_AstDrSw"], ret.doorOpen = any([cp.vl["CGW1"]["CF_Gway_DrvDrSw"], cp.vl["CGW1"]["CF_Gway_AstDrSw"],
cp.vl["CGW2"]["CF_Gway_RLDrSw"], cp.vl["CGW2"]["CF_Gway_RRDrSw"]]) cp.vl["CGW2"]["CF_Gway_RLDrSw"], cp.vl["CGW2"]["CF_Gway_RRDrSw"]])
@ -55,9 +65,19 @@ class CarState(CarStateBase):
) )
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.1 ret.standstill = ret.vEgoRaw < 0.1
self.cluster_speed_counter += 1
if self.cluster_speed_counter > CLUSTER_SAMPLE_RATE:
self.cluster_speed = cp.vl["CLU15"]["CF_Clu_VehicleSpeed"]
self.cluster_speed_counter = 0
# mimic how dash converts to imperial
if not self.is_metric:
self.cluster_speed = math.floor(self.cluster_speed * CV.KPH_TO_MPH + CV.KPH_TO_MPH)
ret.vEgoCluster = self.cluster_speed * speed_conv
ret.steeringAngleDeg = cp.vl["SAS11"]["SAS_Angle"] ret.steeringAngleDeg = cp.vl["SAS11"]["SAS_Angle"]
ret.steeringRateDeg = cp.vl["SAS11"]["SAS_Speed"] ret.steeringRateDeg = cp.vl["SAS11"]["SAS_Speed"]
ret.yawRate = cp.vl["ESP12"]["YAW_RATE"] ret.yawRate = cp.vl["ESP12"]["YAW_RATE"]
@ -78,7 +98,6 @@ class CarState(CarStateBase):
ret.cruiseState.available = cp_cruise.vl["SCC11"]["MainMode_ACC"] == 1 ret.cruiseState.available = cp_cruise.vl["SCC11"]["MainMode_ACC"] == 1
ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0 ret.cruiseState.enabled = cp_cruise.vl["SCC12"]["ACCMode"] != 0
ret.cruiseState.standstill = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 4. ret.cruiseState.standstill = cp_cruise.vl["SCC11"]["SCCInfoDisplay"] == 4.
speed_conv = CV.MPH_TO_MS if cp.vl["CLU11"]["CF_Clu_SPEED_UNIT"] else CV.KPH_TO_MS
ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv ret.cruiseState.speed = cp_cruise.vl["SCC11"]["VSetDis"] * speed_conv
# TODO: Find brake pressure # TODO: Find brake pressure
@ -111,12 +130,12 @@ class CarState(CarStateBase):
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
if not self.CP.openpilotLongitudinalControl: if not self.CP.openpilotLongitudinalControl:
if self.CP.carFingerprint in FEATURES["use_fca"]: aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12"
ret.stockAeb = cp_cruise.vl["FCA11"]["FCA_CmdAct"] != 0 aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct"
ret.stockFcw = cp_cruise.vl["FCA11"]["CF_VSM_Warn"] == 2 aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0
else: aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0
ret.stockAeb = cp_cruise.vl["SCC12"]["AEB_CmdAct"] != 0 ret.stockFcw = aeb_warning and not aeb_braking
ret.stockFcw = cp_cruise.vl["SCC12"]["CF_VSM_Warn"] == 2 ret.stockAeb = aeb_warning and aeb_braking
if self.CP.enableBsm: if self.CP.enableBsm:
ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0 ret.leftBlindspot = cp.vl["LCA11"]["CF_Lca_IndLeft"] != 0
@ -136,17 +155,21 @@ class CarState(CarStateBase):
def update_canfd(self, cp, cp_cam): def update_canfd(self, cp, cp_cam):
ret = car.CarState.new_message() ret = car.CarState.new_message()
if self.CP.flags & HyundaiFlags.CANFD_HDA2: if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR):
ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. if self.CP.carFingerprint in EV_CAR:
ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255.
else:
ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023.
ret.gasPressed = ret.gas > 1e-5
else: else:
ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. ret.gasPressed = bool(cp.vl["ACCELERATOR_BRAKE_ALT"]["ACCELERATOR_PEDAL_PRESSED"])
ret.gasPressed = ret.gas > 1e-5
ret.brakePressed = cp.vl["BRAKE"]["BRAKE_PRESSED"] == 1 ret.brakePressed = cp.vl["TCS"]["DriverBraking"] == 1
ret.doorOpen = cp.vl["DOORS_SEATBELTS"]["DRIVER_DOOR_OPEN"] == 1 ret.doorOpen = cp.vl["DOORS_SEATBELTS"]["DRIVER_DOOR_OPEN"] == 1
ret.seatbeltUnlatched = cp.vl["DOORS_SEATBELTS"]["DRIVER_SEATBELT_LATCHED"] == 0 ret.seatbeltUnlatched = cp.vl["DOORS_SEATBELTS"]["DRIVER_SEATBELT_LATCHED"] == 0
gear = cp.vl["GEAR_SHIFTER"]["GEAR"] gear = cp.vl[self.gear_msg_canfd]["GEAR"]
ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear))
# TODO: figure out positions # TODO: figure out positions
@ -165,22 +188,29 @@ class CarState(CarStateBase):
ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"]
ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"]
ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD
ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"],
cp.vl["BLINKERS"]["RIGHT_LAMP"]) cp.vl["BLINKERS"]["RIGHT_LAMP"])
if self.CP.enableBsm:
ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0
ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0
ret.cruiseState.available = True ret.cruiseState.available = True
ret.cruiseState.enabled = cp.vl["SCC1"]["CRUISE_ACTIVE"] == 1 self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1
cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam if not self.CP.openpilotLongitudinalControl:
speed_factor = CV.MPH_TO_MS if cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] == 1 else CV.KPH_TO_MS speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS
ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp
ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor
ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1
ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2)
self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"])
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
self.prev_cruise_buttons = self.cruise_buttons[-1]
self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"])
self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"])
self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"]
self.cruise_info_copy = copy.copy(cp_cruise_info.vl["CRUISE_INFO"])
if self.CP.flags & HyundaiFlags.CANFD_HDA2: if self.CP.flags & HyundaiFlags.CANFD_HDA2:
self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"])
@ -227,6 +257,8 @@ class CarState(CarStateBase):
("CF_Clu_AmpInfo", "CLU11"), ("CF_Clu_AmpInfo", "CLU11"),
("CF_Clu_AliveCnt1", "CLU11"), ("CF_Clu_AliveCnt1", "CLU11"),
("CF_Clu_VehicleSpeed", "CLU15"),
("ACCEnable", "TCS13"), ("ACCEnable", "TCS13"),
("ACC_REQ", "TCS13"), ("ACC_REQ", "TCS13"),
("DriverBraking", "TCS13"), ("DriverBraking", "TCS13"),
@ -251,6 +283,7 @@ class CarState(CarStateBase):
("TCS13", 50), ("TCS13", 50),
("TCS15", 10), ("TCS15", 10),
("CLU11", 50), ("CLU11", 50),
("CLU15", 5),
("ESP12", 100), ("ESP12", 100),
("CGW1", 10), ("CGW1", 10),
("CGW2", 5), ("CGW2", 5),
@ -276,12 +309,14 @@ class CarState(CarStateBase):
signals += [ signals += [
("FCA_CmdAct", "FCA11"), ("FCA_CmdAct", "FCA11"),
("CF_VSM_Warn", "FCA11"), ("CF_VSM_Warn", "FCA11"),
("CF_VSM_DecCmdAct", "FCA11"),
] ]
checks.append(("FCA11", 50)) checks.append(("FCA11", 50))
else: else:
signals += [ signals += [
("AEB_CmdAct", "SCC12"), ("AEB_CmdAct", "SCC12"),
("CF_VSM_Warn", "SCC12"), ("CF_VSM_Warn", "SCC12"),
("CF_VSM_DecCmdAct", "SCC12"),
] ]
if CP.enableBsm: if CP.enableBsm:
@ -309,7 +344,6 @@ class CarState(CarStateBase):
if CP.carFingerprint in FEATURES["use_cluster_gears"]: if CP.carFingerprint in FEATURES["use_cluster_gears"]:
signals.append(("CF_Clu_Gear", "CLU15")) signals.append(("CF_Clu_Gear", "CLU15"))
checks.append(("CLU15", 5))
elif CP.carFingerprint in FEATURES["use_tcu_gears"]: elif CP.carFingerprint in FEATURES["use_tcu_gears"]:
signals.append(("CUR_GR", "TCU12")) signals.append(("CUR_GR", "TCU12"))
checks.append(("TCU12", 100)) checks.append(("TCU12", 100))
@ -366,12 +400,14 @@ class CarState(CarStateBase):
signals += [ signals += [
("FCA_CmdAct", "FCA11"), ("FCA_CmdAct", "FCA11"),
("CF_VSM_Warn", "FCA11"), ("CF_VSM_Warn", "FCA11"),
("CF_VSM_DecCmdAct", "FCA11"),
] ]
checks.append(("FCA11", 50)) checks.append(("FCA11", 50))
else: else:
signals += [ signals += [
("AEB_CmdAct", "SCC12"), ("AEB_CmdAct", "SCC12"),
("CF_VSM_Warn", "SCC12"), ("CF_VSM_Warn", "SCC12"),
("CF_VSM_DecCmdAct", "SCC12"),
] ]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2) return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 2)
@ -380,21 +416,23 @@ class CarState(CarStateBase):
def get_can_parser_canfd(CP): def get_can_parser_canfd(CP):
cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS"
gear_msg = "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else "GEAR_SHIFTER"
signals = [ signals = [
("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_1", "WHEEL_SPEEDS"),
("WHEEL_SPEED_2", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"),
("WHEEL_SPEED_3", "WHEEL_SPEEDS"), ("WHEEL_SPEED_3", "WHEEL_SPEEDS"),
("WHEEL_SPEED_4", "WHEEL_SPEEDS"), ("WHEEL_SPEED_4", "WHEEL_SPEEDS"),
("GEAR", "GEAR_SHIFTER"), ("GEAR", gear_msg),
("BRAKE_PRESSED", "BRAKE"),
("STEERING_RATE", "STEERING_SENSORS"), ("STEERING_RATE", "STEERING_SENSORS"),
("STEERING_ANGLE", "STEERING_SENSORS"), ("STEERING_ANGLE", "STEERING_SENSORS"),
("STEERING_COL_TORQUE", "MDPS"), ("STEERING_COL_TORQUE", "MDPS"),
("STEERING_OUT_TORQUE", "MDPS"), ("STEERING_OUT_TORQUE", "MDPS"),
("LKA_FAULT", "MDPS"),
("DriverBraking", "TCS"),
("CRUISE_ACTIVE", "SCC1"),
("COUNTER", cruise_btn_msg), ("COUNTER", cruise_btn_msg),
("CRUISE_BUTTONS", cruise_btn_msg), ("CRUISE_BUTTONS", cruise_btn_msg),
("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg), ("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg),
@ -410,64 +448,83 @@ class CarState(CarStateBase):
checks = [ checks = [
("WHEEL_SPEEDS", 100), ("WHEEL_SPEEDS", 100),
("GEAR_SHIFTER", 100), (gear_msg, 100),
("BRAKE", 100),
("STEERING_SENSORS", 100), ("STEERING_SENSORS", 100),
("MDPS", 100), ("MDPS", 100),
("SCC1", 50), ("TCS", 50),
(cruise_btn_msg, 50), (cruise_btn_msg, 50),
("CLUSTER_INFO", 4), ("CLUSTER_INFO", 4),
("BLINKERS", 4), ("BLINKERS", 4),
("DOORS_SEATBELTS", 4), ("DOORS_SEATBELTS", 4),
] ]
if CP.flags & HyundaiFlags.CANFD_HDA2: if CP.enableBsm:
signals += [
("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"),
("FR_INDICATOR", "BLINDSPOTS_REAR_CORNERS"),
]
checks += [
("BLINDSPOTS_REAR_CORNERS", 20),
]
if not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and not CP.openpilotLongitudinalControl:
signals += [
("ACCMode", "SCC_CONTROL"),
("VSetDis", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "SCC_CONTROL"),
]
checks += [
("SCC_CONTROL", 50),
]
if CP.carFingerprint in EV_CAR:
signals += [ signals += [
("ACCELERATOR_PEDAL", "ACCELERATOR"), ("ACCELERATOR_PEDAL", "ACCELERATOR"),
("GEAR", "ACCELERATOR"),
("SET_SPEED", "CRUISE_INFO"),
("CRUISE_STANDSTILL", "CRUISE_INFO"),
] ]
checks += [ checks += [
("CRUISE_INFO", 50),
("ACCELERATOR", 100), ("ACCELERATOR", 100),
] ]
else: elif CP.carFingerprint in HYBRID_CAR:
signals += [ signals += [
("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"), ("ACCELERATOR_PEDAL", "ACCELERATOR_ALT"),
] ]
checks += [ checks += [
("ACCELERATOR_ALT", 100), ("ACCELERATOR_ALT", 100),
] ]
else:
signals += [
("ACCELERATOR_PEDAL_PRESSED", "ACCELERATOR_BRAKE_ALT"),
]
checks += [
("ACCELERATOR_BRAKE_ALT", 100),
]
bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus) return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus)
@staticmethod @staticmethod
def get_cam_can_parser_canfd(CP): def get_cam_can_parser_canfd(CP):
signals = []
checks = []
if CP.flags & HyundaiFlags.CANFD_HDA2: if CP.flags & HyundaiFlags.CANFD_HDA2:
signals = [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] signals += [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)]
checks = [("CAM_0x2a4", 20)] checks += [("CAM_0x2a4", 20)]
else: elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC:
signals = [ signals += [
("COUNTER", "CRUISE_INFO"), ("COUNTER", "SCC_CONTROL"),
("NEW_SIGNAL_1", "CRUISE_INFO"), ("NEW_SIGNAL_1", "SCC_CONTROL"),
("CRUISE_MAIN", "CRUISE_INFO"), ("MainMode_ACC", "SCC_CONTROL"),
("CRUISE_STATUS", "CRUISE_INFO"), ("ACCMode", "SCC_CONTROL"),
("CRUISE_INACTIVE", "CRUISE_INFO"), ("CRUISE_INACTIVE", "SCC_CONTROL"),
("NEW_SIGNAL_2", "CRUISE_INFO"), ("ZEROS_9", "SCC_CONTROL"),
("CRUISE_STANDSTILL", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "SCC_CONTROL"),
("NEW_SIGNAL_3", "CRUISE_INFO"), ("ZEROS_5", "SCC_CONTROL"),
("BYTE11", "CRUISE_INFO"), ("DISTANCE_SETTING", "SCC_CONTROL"),
("SET_SPEED", "CRUISE_INFO"), ("VSetDis", "SCC_CONTROL"),
("NEW_SIGNAL_4", "CRUISE_INFO"),
] ]
signals += [(f"BYTE{i}", "CRUISE_INFO") for i in range(3, 7)] checks += [
signals += [(f"BYTE{i}", "CRUISE_INFO") for i in range(13, 31)] ("SCC_CONTROL", 50),
checks = [
("CRUISE_INFO", 50),
] ]
return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6) return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6)

@ -4,7 +4,7 @@ from selfdrive.car.hyundai.values import CAR, CHECKSUM, CAMERA_SCC_CAR
hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf) hyundai_checksum = crcmod.mkCrcFun(0x11D, initCrc=0xFD, rev=False, xorOut=0xdf)
def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
lkas11, sys_warning, sys_state, enabled, torque_fault, lkas11, sys_warning, sys_state, enabled,
left_lane, right_lane, left_lane, right_lane,
left_lane_depart, right_lane_depart): left_lane_depart, right_lane_depart):
values = lkas11 values = lkas11
@ -14,12 +14,14 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
values["CF_Lkas_LdwsRHWarning"] = right_lane_depart values["CF_Lkas_LdwsRHWarning"] = right_lane_depart
values["CR_Lkas_StrToqReq"] = apply_steer values["CR_Lkas_StrToqReq"] = apply_steer
values["CF_Lkas_ActToi"] = steer_req values["CF_Lkas_ActToi"] = steer_req
values["CF_Lkas_ToiFlt"] = torque_fault # seems to allow actuation on CR_Lkas_StrToqReq
values["CF_Lkas_MsgCount"] = frame % 0x10 values["CF_Lkas_MsgCount"] = frame % 0x10
if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE,
CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020,
CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022,
CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022,
CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022):
values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1)
values["CF_Lkas_LdwsOpt_USM"] = 2 values["CF_Lkas_LdwsOpt_USM"] = 2
@ -38,7 +40,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req,
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
# Likely cars lacking the ability to show individual lane lines in the dash # Likely cars lacking the ability to show individual lane lines in the dash
elif car_fingerprint in (CAR.KIA_OPTIMA,): elif car_fingerprint in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL):
# SysWarning 4 = keep hands on wheel + beep # SysWarning 4 = keep hands on wheel + beep
values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0 values["CF_Lkas_SysWarning"] = 4 if sys_warning else 0
@ -94,7 +96,7 @@ def create_lfahda_mfc(packer, enabled, hda_set_speed=0):
} }
return packer.make_can_msg("LFAHDA_MFC", 0, values) return packer.make_can_msg("LFAHDA_MFC", 0, values)
def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, gas_pressed): def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, set_speed, stopping, long_override):
commands = [] commands = []
scc11_values = { scc11_values = {
@ -111,7 +113,7 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s
commands.append(packer.make_can_msg("SCC11", 0, scc11_values)) commands.append(packer.make_can_msg("SCC11", 0, scc11_values))
scc12_values = { scc12_values = {
"ACCMode": 2 if enabled and gas_pressed else 1 if enabled else 0, "ACCMode": 2 if enabled and long_override else 1 if enabled else 0,
"StopReq": 1 if stopping else 0, "StopReq": 1 if stopping else 0,
"aReqRaw": accel, "aReqRaw": accel,
"aReqValue": accel, # stock ramps up and down respecting jerk limit until it reaches aReqRaw "aReqValue": accel, # stock ramps up and down respecting jerk limit until it reaches aReqRaw
@ -127,23 +129,21 @@ def create_acc_commands(packer, enabled, accel, upper_jerk, idx, lead_visible, s
"ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values "ComfortBandLower": 0.0, # stock usually is 0 but sometimes uses higher values
"JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values "JerkUpperLimit": upper_jerk, # stock usually is 1.0 but sometimes uses higher values
"JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values "JerkLowerLimit": 5.0, # stock usually is 0.5 but sometimes uses higher values
"ACCMode": 2 if enabled and gas_pressed else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage "ACCMode": 2 if enabled and long_override else 1 if enabled else 4, # stock will always be 4 instead of 0 after first disengage
"ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead "ObjGap": 2 if lead_visible else 0, # 5: >30, m, 4: 25-30 m, 3: 20-25 m, 2: < 20 m, 0: no lead
} }
commands.append(packer.make_can_msg("SCC14", 0, scc14_values)) commands.append(packer.make_can_msg("SCC14", 0, scc14_values))
# note that some vehicles most likely have an alternate checksum/counter definition
# https://github.com/commaai/opendbc/commit/9ddcdb22c4929baf310295e832668e6e7fcfa602
fca11_values = { fca11_values = {
# seems to count 2,1,0,3,2,1,0,3,2,1,0,3,2,1,0,repeat... "CR_FCA_Alive": idx % 0xF,
# (where first value is aligned to Supplemental_Counter == 0)
# test: [(idx % 0xF, -((idx % 0xF) + 2) % 4) for idx in range(0x14)]
"CR_FCA_Alive": ((-((idx % 0xF) + 2) % 4) << 2) + 1,
"Supplemental_Counter": idx % 0xF,
"PAINT1_Status": 1, "PAINT1_Status": 1,
"FCA_DrvSetStatus": 1, "FCA_DrvSetStatus": 1,
"FCA_Status": 1, # AEB disabled "FCA_Status": 1, # AEB disabled
} }
fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2] fca11_dat = packer.make_can_msg("FCA11", 0, fca11_values)[2]
fca11_values["CR_FCA_ChkSum"] = 0x10 - sum(sum(divmod(i, 16)) for i in fca11_dat) % 0x10 fca11_values["CR_FCA_ChkSum"] = hyundai_checksum(fca11_dat[:7])
commands.append(packer.make_can_msg("FCA11", 0, fca11_values)) commands.append(packer.make_can_msg("FCA11", 0, fca11_values))
return commands return commands

@ -1,7 +1,18 @@
from common.numpy_fast import clip
from selfdrive.car.hyundai.values import HyundaiFlags from selfdrive.car.hyundai.values import HyundaiFlags
def create_lkas(packer, CP, enabled, lat_active, apply_steer): def get_e_can_bus(CP):
# On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars
# have a different harness than the HDA1 and non-HDA variants in order to split
# a different bus, since the steering is done by different ECUs.
return 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4
def create_steering_messages(packer, CP, enabled, lat_active, apply_steer):
ret = []
values = { values = {
"LKA_MODE": 2, "LKA_MODE": 2,
"LKA_ICON": 2 if enabled else 1, "LKA_ICON": 2 if enabled else 1,
@ -14,8 +25,14 @@ def create_lkas(packer, CP, enabled, lat_active, apply_steer):
"NEW_SIGNAL_2": 0, "NEW_SIGNAL_2": 0,
} }
msg = "LKAS" if CP.flags & HyundaiFlags.CANFD_HDA2 else "LFA" if CP.flags & HyundaiFlags.CANFD_HDA2:
return packer.make_can_msg(msg, 4, values) if CP.openpilotLongitudinalControl:
ret.append(packer.make_can_msg("LFA", 5, values))
ret.append(packer.make_can_msg("LKAS", 4, values))
else:
ret.append(packer.make_can_msg("LFA", 4, values))
return ret
def create_cam_0x2a4(packer, camera_values): def create_cam_0x2a4(packer, camera_values):
camera_values.update({ camera_values.update({
@ -23,24 +40,111 @@ def create_cam_0x2a4(packer, camera_values):
}) })
return packer.make_can_msg("CAM_0x2a4", 4, camera_values) return packer.make_can_msg("CAM_0x2a4", 4, camera_values)
def create_buttons(packer, cnt, btn): def create_buttons(packer, CP, cnt, btn):
values = { values = {
"COUNTER": cnt, "COUNTER": cnt,
"SET_ME_1": 1, "SET_ME_1": 1,
"CRUISE_BUTTONS": btn, "CRUISE_BUTTONS": btn,
} }
return packer.make_can_msg("CRUISE_BUTTONS", 5, values)
def create_cruise_info(packer, cruise_info_copy, cancel): bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 6
return packer.make_can_msg("CRUISE_BUTTONS", bus, values)
def create_acc_cancel(packer, CP, cruise_info_copy):
values = cruise_info_copy values = cruise_info_copy
if cancel: values.update({
values["CRUISE_STATUS"] = 0 "ACCMode": 4,
values["CRUISE_INACTIVE"] = 1 })
return packer.make_can_msg("CRUISE_INFO", 4, values) return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)
def create_lfahda_cluster(packer, enabled): def create_lfahda_cluster(packer, CP, enabled):
values = { values = {
"HDA_ICON": 1 if enabled else 0, "HDA_ICON": 1 if enabled else 0,
"LFA_ICON": 2 if enabled else 0, "LFA_ICON": 2 if enabled else 0,
} }
return packer.make_can_msg("LFAHDA_CLUSTER", 4, values) return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values)
def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed):
jerk = 5
jn = jerk / 50
if not enabled or gas_override:
a_val, a_raw = 0, 0
else:
a_raw = accel
a_val = clip(accel, accel_last - jn, accel_last + jn)
if stopping:
a_raw = 0
values = {
"ACCMode": 0 if not enabled else (2 if gas_override else 1),
"MainMode_ACC": 1,
"StopReq": 1 if stopping else 0,
"aReqValue": a_val,
"aReqRaw": a_raw,
"VSetDis": set_speed,
"JerkLowerLimit": jerk if enabled else 1,
"ACC_ObjDist": 1,
"ObjValid": 0,
"OBJ_STATUS": 2,
"SET_ME_2": 0x4,
"SET_ME_3": 0x3,
"SET_ME_TMP_64": 0x64,
"NEW_SIGNAL_10": 4,
"DISTANCE_SETTING": 4,
}
return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values)
def create_adrv_messages(packer, frame):
# messages needed to car happy after disabling
# the ADAS Driving ECU to do longitudinal control
ret = []
values = {
}
ret.append(packer.make_can_msg("ADRV_0x51", 4, values))
if frame % 2 == 0:
values = {
'AEB_SETTING': 0x1, # show AEB disabled icon
'SET_ME_2': 0x2,
'SET_ME_FF': 0xff,
'SET_ME_FC': 0xfc,
'SET_ME_9': 0x9,
}
ret.append(packer.make_can_msg("ADRV_0x160", 5, values))
if frame % 5 == 0:
values = {
'SET_ME_1C': 0x1c,
'SET_ME_FF': 0xff,
'SET_ME_TMP_F': 0xf,
'SET_ME_TMP_F_2': 0xf,
}
ret.append(packer.make_can_msg("ADRV_0x1ea", 5, values))
values = {
'SET_ME_E1': 0xe1,
'SET_ME_3A': 0x3a,
}
ret.append(packer.make_can_msg("ADRV_0x200", 5, values))
if frame % 20 == 0:
values = {
'SET_ME_15': 0x15,
}
ret.append(packer.make_can_msg("ADRV_0x345", 5, values))
if frame % 100 == 0:
values = {
'SET_ME_22': 0x22,
'SET_ME_41': 0x41,
}
ret.append(packer.make_can_msg("ADRV_0x1da", 5, values))
return ret

@ -2,12 +2,13 @@
from cereal import car from cereal import car
from panda import Panda from panda import Panda
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams
from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR
from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
from selfdrive.car.disable_ecu import disable_ecu from selfdrive.car.disable_ecu import disable_ecu
Ecu = car.CarParams.Ecu
ButtonType = car.CarState.ButtonEvent.Type ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL) ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL)
@ -27,165 +28,118 @@ class CarInterface(CarInterfaceBase):
ret.carName = "hyundai" ret.carName = "hyundai"
ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None
# WARNING: disabling radar also disables AEB (and we show the same warning on the instrument cluster as if you manually disabled AEB)
ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR | CANFD_CAR)
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
ret.pcmCruise = not ret.openpilotLongitudinalControl
# These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars have been put into dashcam only due to both a lack of users and test coverage.
# These cars likely still work fine. Once a user confirms each car works and a test route is # These cars likely still work fine. Once a user confirms each car works and a test route is
# added to selfdrive/car/tests/routes.py, we can remove it from this list. # added to selfdrive/car/tests/routes.py, we can remove it from this list.
ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, }
if candidate in CANFD_CAR:
# detect HDA2 with ADAS Driving ECU
if Ecu.adas in [fw.ecu for fw in car_fw]:
ret.flags |= HyundaiFlags.CANFD_HDA2.value
else:
# non-HDA2
if 0x1cf not in fingerprint[4]:
ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value
# ICE cars do not have 0x130; GEARS message on 0x40 instead
if 0x130 not in fingerprint[4]:
ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value
if candidate not in CANFD_RADAR_SCC_CAR:
ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value
ret.steerActuatorDelay = 0.1 # Default delay ret.steerActuatorDelay = 0.1 # Default delay
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
tire_stiffness_factor = 1. tire_stiffness_factor = 1.
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
ret.stoppingControl = True
ret.startingState = True
ret.vEgoStarting = 0.1
ret.startAccel = 2.0
ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0]
ret.longitudinalActuatorDelayLowerBound = 0.5 # s
ret.longitudinalActuatorDelayUpperBound = 0.5 # s
if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022):
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.766 ret.wheelbase = 2.766
# Values from optimizer # Values from optimizer
ret.steerRatio = 16.55 # 13.8 is spec end-to-end ret.steerRatio = 16.55 # 13.8 is spec end-to-end
tire_stiffness_factor = 0.82 tire_stiffness_factor = 0.82
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]]
elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID): elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID):
ret.mass = 1513. + STD_CARGO_KG ret.mass = 1513. + STD_CARGO_KG
ret.wheelbase = 2.84 ret.wheelbase = 2.84
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.SONATA_LF: elif candidate == CAR.SONATA_LF:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 4497. * CV.LB_TO_KG ret.mass = 4497. * CV.LB_TO_KG
ret.wheelbase = 2.804 ret.wheelbase = 2.804
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.PALISADE: elif candidate == CAR.PALISADE:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1999. + STD_CARGO_KG ret.mass = 1999. + STD_CARGO_KG
ret.wheelbase = 2.90 ret.wheelbase = 2.90
ret.steerRatio = 15.6 * 1.15 ret.steerRatio = 15.6 * 1.15
tire_stiffness_factor = 0.63 tire_stiffness_factor = 0.63
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.ELANTRA:
elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30):
ret.lateralTuning.pid.kf = 0.00006
ret.mass = 1275. + STD_CARGO_KG ret.mass = 1275. + STD_CARGO_KG
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535 ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535
tire_stiffness_factor = 0.385 # stiffnessFactor settled on 1.0081302973865127 tire_stiffness_factor = 0.385 # stiffnessFactor settled on 1.0081302973865127
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
ret.minSteerSpeed = 32 * CV.MPH_TO_MS ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.ELANTRA_2021: elif candidate == CAR.ELANTRA_2021:
ret.mass = (2800. * CV.LB_TO_KG) + STD_CARGO_KG ret.mass = (2800. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72 ret.wheelbase = 2.72
ret.steerRatio = 12.9 ret.steerRatio = 12.9
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.ELANTRA_HEV_2021: elif candidate == CAR.ELANTRA_HEV_2021:
ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG
ret.wheelbase = 2.72 ret.wheelbase = 2.72
ret.steerRatio = 12.9 ret.steerRatio = 12.9
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.HYUNDAI_GENESIS: elif candidate == CAR.HYUNDAI_GENESIS:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01 ret.wheelbase = 3.01
ret.steerRatio = 16.5 ret.steerRatio = 16.5
ret.lateralTuning.init('indi')
ret.lateralTuning.indi.innerLoopGainBP = [0.]
ret.lateralTuning.indi.innerLoopGainV = [3.5]
ret.lateralTuning.indi.outerLoopGainBP = [0.]
ret.lateralTuning.indi.outerLoopGainV = [2.0]
ret.lateralTuning.indi.timeConstantBP = [0.]
ret.lateralTuning.indi.timeConstantV = [1.4]
ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
ret.lateralTuning.indi.actuatorEffectivenessV = [2.3]
ret.minSteerSpeed = 60 * CV.KPH_TO_MS ret.minSteerSpeed = 60 * CV.KPH_TO_MS
elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022):
ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) + STD_CARGO_KG ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) + STD_CARGO_KG
ret.wheelbase = 2.6 ret.wheelbase = 2.6
ret.steerRatio = 13.42 # Spec ret.steerRatio = 13.42 # Spec
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022):
ret.lateralTuning.pid.kf = 0.00006
ret.mass = 1490. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx ret.mass = 1490. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 13.73 # Spec ret.steerRatio = 13.73 # Spec
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022):
ret.minSteerSpeed = 32 * CV.MPH_TO_MS ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.IONIQ_PHEV_2019: elif candidate == CAR.IONIQ_PHEV_2019:
ret.mass = 1550. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/us/en/vehicles/2019-ioniq-plug-in-hybrid/compare-specs ret.mass = 1550. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/us/en/vehicles/2019-ioniq-plug-in-hybrid/compare-specs
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 13.73 ret.steerRatio = 13.73
ret.lateralTuning.init('indi')
ret.lateralTuning.indi.innerLoopGainBP = [0.]
ret.lateralTuning.indi.innerLoopGainV = [2.5]
ret.lateralTuning.indi.outerLoopGainBP = [0.]
ret.lateralTuning.indi.outerLoopGainV = [3.5]
ret.lateralTuning.indi.timeConstantBP = [0.]
ret.lateralTuning.indi.timeConstantV = [1.4]
ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
ret.lateralTuning.indi.actuatorEffectivenessV = [1.8]
ret.minSteerSpeed = 32 * CV.MPH_TO_MS ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.VELOSTER: elif candidate == CAR.VELOSTER:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3558. * CV.LB_TO_KG ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80 ret.wheelbase = 2.80
ret.steerRatio = 13.75 * 1.15 ret.steerRatio = 13.75 * 1.15
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.TUCSON: elif candidate == CAR.TUCSON:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3520. * CV.LB_TO_KG ret.mass = 3520. * CV.LB_TO_KG
ret.wheelbase = 2.67 ret.wheelbase = 2.67
ret.steerRatio = 14.00 * 1.15 ret.steerRatio = 14.00 * 1.15
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.TUCSON_HYBRID_4TH_GEN: elif candidate == CAR.TUCSON_HYBRID_4TH_GEN:
ret.mass = 1680. + STD_CARGO_KG # average of all 3 trims ret.mass = 1680. + STD_CARGO_KG # average of all 3 trims
ret.wheelbase = 2.756 ret.wheelbase = 2.756
ret.steerRatio = 16. ret.steerRatio = 16.
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SANTA_CRUZ_1ST_GEN:
ret.mass = 1870. + STD_CARGO_KG # weight from Limited trim - the only supported trim
ret.wheelbase = 3.000
ret.steerRatio = 14.2 # steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf
# Kia # Kia
elif candidate == CAR.KIA_SORENTO: elif candidate == CAR.KIA_SORENTO:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1985. + STD_CARGO_KG ret.mass = 1985. + STD_CARGO_KG
ret.wheelbase = 2.78 ret.wheelbase = 2.78
ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021): elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021):
ret.lateralTuning.pid.kf = 0.00006
ret.mass = 1737. + STD_CARGO_KG ret.mass = 1737. + STD_CARGO_KG
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
if candidate == CAR.KIA_NIRO_PHEV: if candidate == CAR.KIA_NIRO_PHEV:
ret.minSteerSpeed = 32 * CV.MPH_TO_MS ret.minSteerSpeed = 32 * CV.MPH_TO_MS
elif candidate == CAR.KIA_SELTOS: elif candidate == CAR.KIA_SELTOS:
@ -193,137 +147,128 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.63 ret.wheelbase = 2.63
ret.steerRatio = 14.56 ret.steerRatio = 14.56
tire_stiffness_factor = 1 tire_stiffness_factor = 1
ret.lateralTuning.init('indi') elif candidate == CAR.KIA_SPORTAGE_5TH_GEN:
ret.lateralTuning.indi.innerLoopGainBP = [0.] ret.mass = 1700. + STD_CARGO_KG # weight from SX and above trims, average of FWD and AWD versions
ret.lateralTuning.indi.innerLoopGainV = [4.] ret.wheelbase = 2.756
ret.lateralTuning.indi.outerLoopGainBP = [0.] ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications
ret.lateralTuning.indi.outerLoopGainV = [3.] elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H):
ret.lateralTuning.indi.timeConstantBP = [0.]
ret.lateralTuning.indi.timeConstantV = [1.4]
ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
ret.lateralTuning.indi.actuatorEffectivenessV = [1.8]
elif candidate in (CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_H):
ret.mass = 3558. * CV.LB_TO_KG ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80 ret.wheelbase = 2.80
ret.steerRatio = 13.75 ret.steerRatio = 13.75
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate == CAR.KIA_OPTIMA_G4:
elif candidate == CAR.KIA_STINGER: ret.minSteerSpeed = 32 * CV.MPH_TO_MS
ret.lateralTuning.pid.kf = 0.00005 elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022):
ret.mass = 1825. + STD_CARGO_KG ret.mass = 1825. + STD_CARGO_KG
ret.wheelbase = 2.78 ret.wheelbase = 2.78
ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_FORTE: elif candidate == CAR.KIA_FORTE:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3558. * CV.LB_TO_KG ret.mass = 3558. * CV.LB_TO_KG
ret.wheelbase = 2.80 ret.wheelbase = 2.80
ret.steerRatio = 13.75 ret.steerRatio = 13.75
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_CEED: elif candidate == CAR.KIA_CEED:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 1450. + STD_CARGO_KG ret.mass = 1450. + STD_CARGO_KG
ret.wheelbase = 2.65 ret.wheelbase = 2.65
ret.steerRatio = 13.75 ret.steerRatio = 13.75
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_K5_2021: elif candidate == CAR.KIA_K5_2021:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 3228. * CV.LB_TO_KG ret.mass = 3228. * CV.LB_TO_KG
ret.wheelbase = 2.85 ret.wheelbase = 2.85
ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims) ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims)
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_EV6: elif candidate == CAR.KIA_EV6:
ret.mass = 2055 + STD_CARGO_KG ret.mass = 2055 + STD_CARGO_KG
ret.wheelbase = 2.9 ret.wheelbase = 2.9
ret.steerRatio = 16. ret.steerRatio = 16.
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
elif candidate == CAR.IONIQ_5: elif candidate == CAR.IONIQ_5:
ret.mass = 2012 + STD_CARGO_KG ret.mass = 2012 + STD_CARGO_KG
ret.wheelbase = 3.0 ret.wheelbase = 3.0
ret.steerRatio = 16. ret.steerRatio = 16.
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN:
ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only
ret.wheelbase = 2.756
ret.steerRatio = 13.6
# Genesis # Genesis
elif candidate == CAR.GENESIS_G70: elif candidate == CAR.GENESIS_G70:
ret.lateralTuning.init('indi')
ret.lateralTuning.indi.innerLoopGainBP = [0.]
ret.lateralTuning.indi.innerLoopGainV = [2.5]
ret.lateralTuning.indi.outerLoopGainBP = [0.]
ret.lateralTuning.indi.outerLoopGainV = [3.5]
ret.lateralTuning.indi.timeConstantBP = [0.]
ret.lateralTuning.indi.timeConstantV = [1.4]
ret.lateralTuning.indi.actuatorEffectivenessBP = [0.]
ret.lateralTuning.indi.actuatorEffectivenessV = [1.8]
ret.steerActuatorDelay = 0.1 ret.steerActuatorDelay = 0.1
ret.mass = 1640.0 + STD_CARGO_KG ret.mass = 1640.0 + STD_CARGO_KG
ret.wheelbase = 2.84 ret.wheelbase = 2.84
ret.steerRatio = 13.56 ret.steerRatio = 13.56
elif candidate == CAR.GENESIS_G70_2020: elif candidate == CAR.GENESIS_G70_2020:
ret.lateralTuning.pid.kf = 0.
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.112], [0.004]]
ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG
ret.wheelbase = 2.83 ret.wheelbase = 2.83
ret.steerRatio = 12.9 ret.steerRatio = 12.9
elif candidate == CAR.GENESIS_GV70_1ST_GEN:
ret.mass = 1950. + STD_CARGO_KG
ret.wheelbase = 2.87
ret.steerRatio = 14.6
elif candidate == CAR.GENESIS_G80: elif candidate == CAR.GENESIS_G80:
ret.lateralTuning.pid.kf = 0.00005
ret.mass = 2060. + STD_CARGO_KG ret.mass = 2060. + STD_CARGO_KG
ret.wheelbase = 3.01 ret.wheelbase = 3.01
ret.steerRatio = 16.5 ret.steerRatio = 16.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]]
elif candidate == CAR.GENESIS_G90: elif candidate == CAR.GENESIS_G90:
ret.mass = 2200 ret.mass = 2200
ret.wheelbase = 3.15 ret.wheelbase = 3.15
ret.steerRatio = 12.069 ret.steerRatio = 12.069
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]]
# panda safety config # *** longitudinal control ***
if candidate in CANFD_CAR:
ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0]
ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in CANFD_RADAR_SCC_CAR
else:
ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0]
ret.experimentalLongitudinalAvailable = candidate not in (LEGACY_SAFETY_MODE_CAR | CAMERA_SCC_CAR)
ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable
ret.pcmCruise = not ret.openpilotLongitudinalControl
ret.stoppingControl = True
ret.startingState = True
ret.vEgoStarting = 0.1
ret.startAccel = 2.0
ret.longitudinalActuatorDelayLowerBound = 0.5
ret.longitudinalActuatorDelayUpperBound = 0.5
# *** feature detection ***
if candidate in CANFD_CAR:
bus = 5 if ret.flags & HyundaiFlags.CANFD_HDA2 else 4
ret.enableBsm = 0x1e5 in fingerprint[bus]
else:
ret.enableBsm = 0x58b in fingerprint[0]
# *** panda safety config ***
if candidate in CANFD_CAR: if candidate in CANFD_CAR:
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput), ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput),
get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)] get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd)]
# detect HDA2 with LKAS message if ret.flags & HyundaiFlags.CANFD_HDA2:
if 0x50 in fingerprint[6]:
ret.flags |= HyundaiFlags.CANFD_HDA2.value
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2
else: if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS:
# non-HDA2 ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS
if 0x1cf not in fingerprint[4]: if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC:
ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS
else: else:
ret.enableBsm = 0x58b in fingerprint[0]
if candidate in LEGACY_SAFETY_MODE_CAR: if candidate in LEGACY_SAFETY_MODE_CAR:
# these cars require a special panda safety mode due to missing counters and checksums in the messages # these cars require a special panda safety mode due to missing counters and checksums in the messages
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)] ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)]
else: else:
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)]
# set appropriate safety param for gas signal
if candidate in HYBRID_CAR:
ret.safetyConfigs[0].safetyParam = 2
elif candidate in EV_CAR:
ret.safetyConfigs[0].safetyParam = 1
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_LONG
if candidate in CAMERA_SCC_CAR: if candidate in CAMERA_SCC_CAR:
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC
if ret.openpilotLongitudinalControl:
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG
if candidate in HYBRID_CAR:
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_HYBRID_GAS
elif candidate in EV_CAR:
ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS
ret.centerToFront = ret.wheelbase * 0.4 ret.centerToFront = ret.wheelbase * 0.4
# TODO: get actual value, for now starting with reasonable value for # TODO: get actual value, for now starting with reasonable value for
@ -334,13 +279,15 @@ class CarInterface(CarInterfaceBase):
# mass and CG position, so all cars will have approximately similar dyn behaviors # mass and CG position, so all cars will have approximately similar dyn behaviors
ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront,
tire_stiffness_factor=tire_stiffness_factor) tire_stiffness_factor=tire_stiffness_factor)
return ret return ret
@staticmethod @staticmethod
def init(CP, logcan, sendcan): def init(CP, logcan, sendcan):
if CP.openpilotLongitudinalControl: if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value):
disable_ecu(logcan, sendcan, addr=0x7d0, com_cont_req=b'\x28\x83\x01') addr, bus = 0x7d0, 0
if CP.flags & HyundaiFlags.CANFD_HDA2.value:
addr, bus = 0x730, 5
disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01')
def _update(self, c): def _update(self, c):
ret = self.CS.update(self.cp, self.cp_cam) ret = self.CS.update(self.cp, self.cp_cam)

@ -0,0 +1,29 @@
#!/usr/bin/env python3
import unittest
from cereal import car
from selfdrive.car.car_helpers import get_interface_attr
from selfdrive.car.fw_versions import FW_QUERY_CONFIGS
from selfdrive.car.hyundai.values import CANFD_CAR
Ecu = car.CarParams.Ecu
ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()}
VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True)
class TestHyundaiFingerprint(unittest.TestCase):
def test_auxiliary_request_ecu_whitelist(self):
# Asserts only auxiliary Ecus can exist in database for CAN-FD cars
config = FW_QUERY_CONFIGS['hyundai']
whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus if r.bus > 3}
for car_model in CANFD_CAR:
ecus = {fw[0] for fw in VERSIONS['hyundai'][car_model].keys()}
ecus_not_in_whitelist = ecus - whitelisted_ecus
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_in_whitelist])
self.assertEqual(len(ecus_not_in_whitelist), 0, f'{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}')
if __name__ == "__main__":
unittest.main()

@ -32,12 +32,14 @@ class CarControllerParams:
self.STEER_DRIVER_ALLOWANCE = 250 self.STEER_DRIVER_ALLOWANCE = 250
self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_MULTIPLIER = 2
self.STEER_THRESHOLD = 250 self.STEER_THRESHOLD = 250
self.STEER_DELTA_UP = 2
self.STEER_DELTA_DOWN = 3
# To determine the limit for your car, find the maximum value that the stock LKAS will request. # To determine the limit for your car, find the maximum value that the stock LKAS will request.
# If the max stock LKAS request is <384, add your car to this list. # If the max stock LKAS request is <384, add your car to this list.
elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.IONIQ,
CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV,
CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO):
self.STEER_MAX = 255 self.STEER_MAX = 255
# Default for most HKG # Default for most HKG
@ -48,6 +50,8 @@ class CarControllerParams:
class HyundaiFlags(IntFlag): class HyundaiFlags(IntFlag):
CANFD_HDA2 = 1 CANFD_HDA2 = 1
CANFD_ALT_BUTTONS = 2 CANFD_ALT_BUTTONS = 2
CANFD_ALT_GEARS = 4
CANFD_CAMERA_SCC = 8
class CAR: class CAR:
@ -55,7 +59,6 @@ class CAR:
ELANTRA = "HYUNDAI ELANTRA 2017" ELANTRA = "HYUNDAI ELANTRA 2017"
ELANTRA_2021 = "HYUNDAI ELANTRA 2021" ELANTRA_2021 = "HYUNDAI ELANTRA 2021"
ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021"
ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT"
HYUNDAI_GENESIS = "HYUNDAI GENESIS 2015-2016" HYUNDAI_GENESIS = "HYUNDAI GENESIS 2015-2016"
IONIQ = "HYUNDAI IONIQ HYBRID 2017-2019" IONIQ = "HYUNDAI IONIQ HYBRID 2017-2019"
IONIQ_HEV_2022 = "HYUNDAI IONIQ HYBRID 2020-2022" IONIQ_HEV_2022 = "HYUNDAI IONIQ HYBRID 2020-2022"
@ -79,6 +82,7 @@ class CAR:
SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021"
IONIQ_5 = "HYUNDAI IONIQ 5 2022" IONIQ_5 = "HYUNDAI IONIQ 5 2022"
TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN"
SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN"
# Kia # Kia
KIA_FORTE = "KIA FORTE E 2018 & GT 2021" KIA_FORTE = "KIA FORTE E 2018 & GT 2021"
@ -86,97 +90,112 @@ class CAR:
KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_EV = "KIA NIRO EV 2020"
KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019"
KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021"
KIA_OPTIMA = "KIA OPTIMA SX 2019 & 2016" KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN"
KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT"
KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019"
KIA_SELTOS = "KIA SELTOS 2021" KIA_SELTOS = "KIA SELTOS 2021"
KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN"
KIA_SORENTO = "KIA SORENTO GT LINE 2018" KIA_SORENTO = "KIA SORENTO GT LINE 2018"
KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN"
KIA_STINGER = "KIA STINGER GT2 2018" KIA_STINGER = "KIA STINGER GT2 2018"
KIA_STINGER_2022 = "KIA STINGER 2022"
KIA_CEED = "KIA CEED INTRO ED 2019" KIA_CEED = "KIA CEED INTRO ED 2019"
KIA_EV6 = "KIA EV6 2022" KIA_EV6 = "KIA EV6 2022"
# Genesis # Genesis
GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70 = "GENESIS G70 2018"
GENESIS_G70_2020 = "GENESIS G70 2020" GENESIS_G70_2020 = "GENESIS G70 2020"
GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN"
GENESIS_G80 = "GENESIS G80 2017" GENESIS_G80 = "GENESIS G80 2017"
GENESIS_G90 = "GENESIS G90 2017" GENESIS_G90 = "GENESIS G90 2017"
@dataclass @dataclass
class HyundaiCarInfo(CarInfo): class HyundaiCarInfo(CarInfo):
# TODO: we can probably remove LKAS. LKAS is standard on many package: str = "Smart Cruise Control (SCC)"
# HKG and for others, it's likely packaged together with SCC
package: str = "Smart Cruise Control (SCC) & LKAS"
CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
CAR.ELANTRA: HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), CAR.ELANTRA: [
HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b),
HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e),
HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e),
],
CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-22", "Smart Cruise Control (SCC)", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k),
CAR.ELANTRA_GT_I30: None, # dashcamOnly and same platform as CAR.ELANTRA CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages
CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j),
CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c),
CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", "Smart Cruise Control (SCC) & LFA", harness=Harness.hyundai_h), CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness
CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c), CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c),
CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", harness=Harness.hyundai_h), CAR.IONIQ_EV_2020: HyundaiCarInfo("Hyundai Ioniq Electric 2020", "All", harness=Harness.hyundai_h),
CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", harness=Harness.hyundai_c), CAR.IONIQ_PHEV_2019: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2019", harness=Harness.hyundai_c),
CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-21", "Smart Cruise Control (SCC)", harness=Harness.hyundai_h), CAR.IONIQ_PHEV: HyundaiCarInfo("Hyundai Ioniq Plug-in Hybrid 2020-21", "All", harness=Harness.hyundai_h),
CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", "Smart Cruise Control (SCC)", harness=Harness.hyundai_b), CAR.KONA: HyundaiCarInfo("Hyundai Kona 2020", harness=Harness.hyundai_b),
CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", harness=Harness.hyundai_g), CAR.KONA_EV: HyundaiCarInfo("Hyundai Kona Electric 2018-21", harness=Harness.hyundai_g),
CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", "Smart Cruise Control (SCC)", harness=Harness.hyundai_o), CAR.KONA_EV_2022: HyundaiCarInfo("Hyundai Kona Electric 2022", harness=Harness.hyundai_o),
CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", harness=Harness.hyundai_i), CAR.KONA_HEV: HyundaiCarInfo("Hyundai Kona Hybrid 2020", video_link="https://youtu.be/0dwpAHiZgFo", harness=Harness.hyundai_i), # TODO: check packages
CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", harness=Harness.hyundai_d), CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", harness=Harness.hyundai_d),
CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", video_link="https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l), CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", "https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l),
CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l),
CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l),
CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", video_link="https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a),
CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e),
CAR.TUCSON: [ CAR.TUCSON: [
HyundaiCarInfo("Hyundai Tucson 2021", "Smart Cruise Control (SCC)", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l),
HyundaiCarInfo("Hyundai Tucson Diesel 2019", "Smart Cruise Control (SCC)", harness=Harness.hyundai_l), HyundaiCarInfo("Hyundai Tucson Diesel 2019", harness=Harness.hyundai_l),
], ],
CAR.PALISADE: [ CAR.PALISADE: [
HyundaiCarInfo("Hyundai Palisade 2020-22", "All", video_link="https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), HyundaiCarInfo("Hyundai Palisade 2020-22", "All", "https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h),
HyundaiCarInfo("Kia Telluride 2020", "All", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Telluride 2020-22", "All", harness=Harness.hyundai_h),
], ],
CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", "Smart Cruise Control (SCC)", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e),
CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a),
CAR.IONIQ_5: HyundaiCarInfo("Hyundai Ioniq 5 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), CAR.IONIQ_5: [
HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23" , "Highway Driving Assist", harness=Harness.hyundai_k),
HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q),
],
CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n),
CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n),
# Kia # Kia
CAR.KIA_FORTE: [ CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g),
HyundaiCarInfo("Kia Forte 2018", harness=Harness.hyundai_b), CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a),
HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g),
],
CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a),
CAR.KIA_NIRO_EV: [ CAR.KIA_NIRO_EV: [
HyundaiCarInfo("Kia Niro Electric 2019", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
HyundaiCarInfo("Kia Niro Electric 2020", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f),
HyundaiCarInfo("Kia Niro Electric 2021", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c),
HyundaiCarInfo("Kia Niro Electric 2022", "All", video_link="https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h),
], ],
CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c),
CAR.KIA_NIRO_HEV_2021: [ CAR.KIA_NIRO_HEV_2021: [
HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify
HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h),
], ],
CAR.KIA_OPTIMA: [ CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018
HyundaiCarInfo("Kia Optima 2017", min_steer_speed=32. * CV.MPH_TO_MS, harness=Harness.hyundai_b), CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g),
HyundaiCarInfo("Kia Optima 2019", harness=Harness.hyundai_g), CAR.KIA_OPTIMA_H: [
HyundaiCarInfo("Kia Optima Hybrid 2017", "Advanced Smart Cruise Control"), # TODO: may support adjacent years
HyundaiCarInfo("Kia Optima Hybrid 2019"),
], ],
CAR.KIA_OPTIMA_H: HyundaiCarInfo("Kia Optima Hybrid 2017, 2019"), # TODO: info may be incorrect CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a),
CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", "Smart Cruise Control (SCC)", harness=Harness.hyundai_a), CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", "Smart Cruise Control (SCC)", harness=Harness.hyundai_n),
CAR.KIA_SORENTO: [ CAR.KIA_SORENTO: [
HyundaiCarInfo("Kia Sorento 2018", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c),
HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e),
], ],
CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n),
CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c),
CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k),
CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e),
CAR.KIA_EV6: HyundaiCarInfo("Kia EV6 2022", "Highway Driving Assist II", harness=Harness.hyundai_p), CAR.KIA_EV6: [
HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l),
HyundaiCarInfo("Kia EV6 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_p)
],
# Genesis # Genesis
CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f),
CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f),
CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l),
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
} }
@ -192,15 +211,6 @@ FINGERPRINTS = {
CAR.ELANTRA: [{ CAR.ELANTRA: [{
66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
}], }],
CAR.ELANTRA_GT_I30: [{
66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1193: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1952: 8, 1960: 8, 1988: 8, 2000: 8, 2001: 8, 2005: 8, 2008: 8, 2009: 8, 2013: 8, 2017: 8, 2025: 8
},
{
66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1419: 8, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8
},
{
66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1960: 8, 1990: 8, 1998: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8
}],
CAR.HYUNDAI_GENESIS: [{ CAR.HYUNDAI_GENESIS: [{
67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4
}, },
@ -231,9 +241,6 @@ FINGERPRINTS = {
CAR.SONATA_LF: [ CAR.SONATA_LF: [
{66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8}, {66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1397: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2014: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8},
], ],
CAR.KIA_OPTIMA: [{
64: 8, 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 447: 8, 512: 6, 544: 8, 558: 8, 593: 8, 608: 8, 640: 8, 688: 5, 790: 8, 809: 8, 832: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 6, 909: 8, 912: 7, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1186: 2, 1191: 2, 1253: 8, 1254: 8, 1255: 8, 1265: 4, 1268: 8, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1492: 8, 1530: 8, 1532: 5, 1792: 8, 1872: 8, 1937: 8, 1953: 8, 1968: 8, 1988: 8, 1996: 8, 2000: 8, 2001: 8, 2004: 8, 2008: 8, 2009: 8, 2012: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8, 1371: 8, 1397: 8, 1961: 8
}],
CAR.KIA_SORENTO: [{ CAR.KIA_SORENTO: [{
67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1 67: 8, 68: 8, 127: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 8, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 8, 897: 8, 902: 8, 903: 8, 916: 8, 1040: 8, 1042: 8, 1056: 8, 1057: 8, 1064: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1265: 4, 1280: 1, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1342: 6, 1345: 8, 1348: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1384: 8, 1407: 8, 1411: 8, 1419: 8, 1425: 2, 1427: 6, 1444: 8, 1456: 4, 1470: 8, 1489: 1
}], }],
@ -288,15 +295,36 @@ HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x4
FW_QUERY_CONFIG = FwQueryConfig( FW_QUERY_CONFIG = FwQueryConfig(
requests=[ requests=[
# TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD
# CAN queries (OBD-II port)
Request( Request(
[HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE], [HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera],
), ),
Request( Request(
[HYUNDAI_VERSION_REQUEST_MULTI], [HYUNDAI_VERSION_REQUEST_MULTI],
[HYUNDAI_VERSION_RESPONSE], [HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar],
),
# CAN-FD queries (camera)
Request(
[HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar],
bus=4,
),
Request(
[HYUNDAI_VERSION_REQUEST_LONG],
[HYUNDAI_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar],
bus=5,
), ),
], ],
extra_ecus=[
(Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms
(Ecu.cornerRadar, 0x7b7, None),
],
) )
FW_VERSIONS = { FW_VERSIONS = {
@ -445,6 +473,7 @@ FW_VERSIONS = {
b'\xf1\x82DNBWN5TMDCXXXG2E', b'\xf1\x82DNBWN5TMDCXXXG2E',
b'\xf1\x82DNCVN5GMCCXXXF0A', b'\xf1\x82DNCVN5GMCCXXXF0A',
b'\xf1\x82DNCVN5GMCCXXXG2B', b'\xf1\x82DNCVN5GMCCXXXG2B',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A',
b'\xf1\x87391162M003', b'\xf1\x87391162M003',
b'\xf1\x87391162M013', b'\xf1\x87391162M013',
@ -469,6 +498,7 @@ FW_VERSIONS = {
b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101',
b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101',
b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100',
b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422',
@ -489,6 +519,7 @@ FW_VERSIONS = {
b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92',
b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:',
b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE',
b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5',
b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v',
@ -736,6 +767,7 @@ FW_VERSIONS = {
b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81640E0051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00',
b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00',
b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104',
@ -758,10 +790,31 @@ FW_VERSIONS = {
b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2', b'\xf1\x87VDKLJ18675252DK6\x89vhgwwwwveVU\x88w\x87w\x99vgf\x97vXfgw_\xff\xc2\xfb\xf1\x89E25\x00\x00\x00\x00\x00\x00\x00\xf1\x82TCK0T33NB2',
b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94', b'\xf1\x87WAJTE17552812CH4vfFffvfVeT5DwvvVVdFeegeg\x88\x88o\xff\x1a]\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00TCK2T20NB1\x19\xd2\x00\x94',
b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S',
b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5',
b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0',
],
},
CAR.KIA_STINGER_2022: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ',
],
(Ecu.engine, 0x7e0, None): [
b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503',
],
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6',
], ],
}, },
CAR.PALISADE: { CAR.PALISADE: {
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ',
b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ',
b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ',
b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ',
@ -775,6 +828,7 @@ FW_VERSIONS = {
b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360',
b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330',
b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330',
b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350',
b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330',
b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360',
b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360',
@ -789,6 +843,7 @@ FW_VERSIONS = {
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103',
b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103',
b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103',
b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104',
b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13',
@ -805,6 +860,7 @@ FW_VERSIONS = {
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28',
b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08',
b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6',
b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1',
b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8',
@ -1029,6 +1085,7 @@ FW_VERSIONS = {
b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010',
b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010',
b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010',
b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010',
# TODO: these return from the MULTI request, above return from LONG # TODO: these return from the MULTI request, above return from LONG
b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01', b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01',
b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04', b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04',
@ -1038,10 +1095,12 @@ FW_VERSIONS = {
b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802',
b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802',
b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904',
b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904',
], ],
(Ecu.eps, 0x7D4, None): [ (Ecu.eps, 0x7D4, None): [
b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102',
b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102',
b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102',
], ],
(Ecu.fwdRadar, 0x7D0, None): [ (Ecu.fwdRadar, 0x7D0, None): [
b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00',
@ -1143,27 +1202,59 @@ FW_VERSIONS = {
b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b', b'\xf1\x87954A22D200\xf1\x81T01950A1 \xf1\000T0190XBL T01950A1 DSP2T16X4X950NS8\r\xfe\x9c\x8b',
], ],
}, },
CAR.KIA_OPTIMA: { CAR.KIA_OPTIMA_G4: {
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ', b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4100 ',
], ],
(Ecu.abs, 0x7d1, None): [ (Ecu.abs, 0x7d1, None): [
b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180', b'\xf1\x00JF ESC \x0f 16 \x16\x06\x17 58920-D5080',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x89F1JF600AISEIU702\xf1\x82F1JF600AISEIU702', b'\xf1\x00JFWGN LDWS AT USA LHD 1.00 1.02 95895-D4100 G21',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6J0051\x00\x00\xf1\x006T6J0_C2\x00\x006T6J0051\x00\x00TJF0T20NSB\x00\x00\x00\x00',
],
},
CAR.KIA_OPTIMA_G4_FL: {
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JF__ SCC F-CUP 1.00 1.00 96400-D4110 ',
],
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00JF ESC \x0b 11 \x18\x030 58920-D5180',
b"\xf1\x00JF ESC \t 11 \x18\x03' 58920-D5260",
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32', b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5001 h32',
b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.02 95895-D5000 h31', b'\xf1\x00JFA LKAS AT USA LHD 1.00 1.00 95895-D5100 h32',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW',
b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.',
b'\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01',
b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00',
b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW', b'\xf1\x816U2V8051\x00\x00\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DJF0T16NL0\t\xd2GW',
b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.', b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\xca3\xeb.',
b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DJF0T16NL2\x9eA\x80\x01',
b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00', b'\xf1\x816U2VA051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DJF0T16NL1\x00\x00\x00\x00',
b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4',
],
},
CAR.ELANTRA: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54',
],
(Ecu.transmission, 0x7e1, None): [
b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104',
],
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ',
], ],
}, },
CAR.ELANTRA_2021: { CAR.ELANTRA_2021: {
@ -1177,6 +1268,7 @@ FW_VERSIONS = {
b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106',
b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106',
b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819',
@ -1189,6 +1281,7 @@ FW_VERSIONS = {
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800',
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800',
b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800',
b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -1201,32 +1294,35 @@ FW_VERSIONS = {
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x82CNCWD0AMFCXCSFFA', b'\xf1\x82CNCWD0AMFCXCSFFA',
b'\xf1\x82CNCWD0AMFCXCSFFB', b'\xf1\x81HM6M2_0a0_FF0',
b'\xf1\x82CNCVD0AMFCXCSFFB', b'\xf1\x82CNCVD0AMFCXCSFFB',
b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80',
], ],
}, },
CAR.ELANTRA_HEV_2021: { CAR.ELANTRA_HEV_2021: {
(Ecu.fwdCamera, 0x7c4, None) : [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930', b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.05 99210-AA000 210930',
b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\000CN7HMFC AT USA LHD 1.00 1.03 99210-AA000 200819',
b'\xf1\x00CN7HMFC AT USA LHD 1.00 1.07 99210-AA000 220426',
], ],
(Ecu.fwdRadar, 0x7d0, None) : [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\000CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ', b'\xf1\x8799110BY000\xf1\x00CNhe SCC FHCUP 1.00 1.01 99110-BY000 ',
], ],
(Ecu.eps, 0x7d4, None) :[ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00CN7 MDPS C 1.00 1.03 56310BY0500 4CNHC103',
b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103', b'\xf1\x8756310/BY050\xf1\x00CN7 MDPS C 1.00 1.03 56310/BY050 4CNHC103',
b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102', b'\xf1\x8756310/BY050\xf1\000CN7 MDPS C 1.00 1.02 56310/BY050 4CNHC102',
], ],
(Ecu.transmission, 0x7e1, None) :[ (Ecu.transmission, 0x7e1, None): [
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000', b'\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\000\000\000\000',
b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa', b'\xf1\x816U3K3051\000\000\xf1\0006U3L0_C2\000\0006U3K3051\000\000HCN0G16NS0\xb9?A\xaa',
b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00', b'\xf1\x816U3K3051\x00\x00\xf1\x006U3L0_C2\x00\x006U3K3051\x00\x00HCN0G16NS0\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None) : [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00',
] ]
}, },
CAR.KONA_HEV: { CAR.KONA_HEV: {
@ -1298,31 +1394,18 @@ FW_VERSIONS = {
], ],
}, },
CAR.KIA_EV6: { CAR.KIA_EV6: {
(Ecu.abs, 0x7d1, None): [
b'\xf1\x8758520CV100\xf1\x00CV IEB \x02 101!\x10\x18 58520-CV100',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00CV1 MDPS R 1.00 1.04 57700-CV000 1B30',
],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328',
b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027',
b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328',
], ],
}, },
CAR.IONIQ_5: { CAR.IONIQ_5: {
(Ecu.abs, 0x7d1, None): [
b'\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010',
b'\xf1\x8758520GI010\xf1\x00NE1 IEB \x07 106!\x11) 58520-GI010',
b'\xf1\x00NE1 IEB \x08 104!\x04\x05 58520-GI000',
b'\xf1\x8758520GI000\xf1\x00NE1 IEB \x08 104!\x04\x05 58520-GI000',
],
(Ecu.eps, 0x7d4, None): [
b'\xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106',
b'\xf1\x8757700GI000 \xf1\x00NE MDPS R 1.00 1.06 57700GI000 4NEDR106',
],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ',
@ -1330,21 +1413,50 @@ FW_VERSIONS = {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206',
b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813',
b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614',
], ],
}, },
CAR.TUCSON_HYBRID_4TH_GEN: { CAR.TUCSON_HYBRID_4TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q',
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NX4 MDPS C 1.00 1.01 56300-P0100 2228', b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ',
], ],
(Ecu.engine, 0x7e0, None): [ },
b'\xf1\x87391312MND0', CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00PSBG2441 G19_Rev\x00\x00\x00SNX4T16XXHS01NS2lS\xdfa', b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ',
b'\xf1\x8795441-3D220\x00\xf1\x81G19_Rev\x00\x00\x00\xf1\x00PSBG2441 G19_Rev\x00\x00\x00SNX4T16XXHS01NS2lS\xdfa', ],
},
CAR.SANTA_CRUZ_1ST_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ',
],
},
CAR.KIA_SPORTAGE_5TH_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662',
b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ',
b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ',
],
},
CAR.GENESIS_GV70_1ST_GEN: {
(Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204',
],
(Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ',
], ],
}, },
} }
@ -1356,24 +1468,27 @@ CHECKSUM = {
FEATURES = { FEATURES = {
# which message has the gear # which message has the gear
"use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, "use_cluster_gears": {CAR.ELANTRA, CAR.KONA},
"use_tcu_gears": {CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON},
"use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022},
# these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12
"use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022}, "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.TUCSON, CAR.KONA_EV_2022, CAR.KIA_STINGER_2022},
} }
CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN} CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN}
# The radar does SCC on these cars when HDA I, rather than the camera
CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, }
# The camera does SCC on these cars, rather than the radar # The camera does SCC on these cars, rather than the radar
CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } CAMERA_SCC_CAR = {CAR.KONA_EV_2022, }
HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019} # these cars use a different gas signal HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN} # these cars use a different gas signal
EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022} EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5}
# these cars require a special panda safety mode due to missing counters and checksums in the messages # these cars require a special panda safety mode due to missing counters and checksums in the messages
LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022}
# If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points.
# If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py
@ -1381,44 +1496,49 @@ DBC = {
CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None),
CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None),
CAR.GENESIS_G70: dbc_dict('hyundai_kia_generic', None), CAR.GENESIS_G70: dbc_dict('hyundai_kia_generic', None),
CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.GENESIS_G80: dbc_dict('hyundai_kia_generic', None), CAR.GENESIS_G80: dbc_dict('hyundai_kia_generic', None),
CAR.GENESIS_G90: dbc_dict('hyundai_kia_generic', None), CAR.GENESIS_G90: dbc_dict('hyundai_kia_generic', None),
CAR.HYUNDAI_GENESIS: dbc_dict('hyundai_kia_generic', None), CAR.HYUNDAI_GENESIS: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_PHEV_2019: dbc_dict('hyundai_kia_generic', None), CAR.IONIQ_PHEV_2019: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_PHEV: dbc_dict('hyundai_kia_generic', None), CAR.IONIQ_PHEV: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_EV_2020: dbc_dict('hyundai_kia_generic', None), CAR.IONIQ_EV_2020: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_EV_LTD: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.IONIQ_EV_LTD: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.IONIQ: dbc_dict('hyundai_kia_generic', None), CAR.IONIQ: dbc_dict('hyundai_kia_generic', None),
CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None), CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None), CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_OPTIMA: dbc_dict('hyundai_kia_generic', None), CAR.KIA_OPTIMA_G4: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_OPTIMA_G4_FL: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_OPTIMA_H: dbc_dict('hyundai_kia_generic', None), CAR.KIA_OPTIMA_H: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format
CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KONA: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None),
CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None),
CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_HEV: dbc_dict('hyundai_kia_generic', None),
CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None),
CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None),
CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format
CAR.TUCSON: dbc_dict('hyundai_kia_generic', None), CAR.TUCSON: dbc_dict('hyundai_kia_generic', None),
CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.VELOSTER: dbc_dict('hyundai_kia_generic', None), CAR.VELOSTER: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None),
CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), CAR.KIA_EV6: dbc_dict('hyundai_canfd', None),
CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'),
CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None),
CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None),
CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None),
} }

@ -2,24 +2,28 @@ import yaml
import os import os
import time import time
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from typing import Any, Dict, Optional, Tuple, List from typing import Any, Dict, Optional, Tuple, List, Callable
from cereal import car from cereal import car
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.conversions import Conversions as CV from common.conversions import Conversions as CV
from common.kalman.simple_kalman import KF1D from common.kalman.simple_kalman import KF1D
from common.numpy_fast import interp
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.car import create_button_enable_events, gen_empty_fingerprint from selfdrive.car import apply_hysteresis, gen_empty_fingerprint
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_deadzone
from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.events import Events
from selfdrive.controls.lib.vehicle_model import VehicleModel from selfdrive.controls.lib.vehicle_model import VehicleModel
ButtonType = car.CarState.ButtonEvent.Type
GearShifter = car.CarState.GearShifter GearShifter = car.CarState.GearShifter
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float]
MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS
ACCEL_MAX = 2.0 ACCEL_MAX = 2.0
ACCEL_MIN = -3.5 ACCEL_MIN = -3.5
FRICTION_THRESHOLD = 0.3
TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml')
TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml')
@ -101,6 +105,20 @@ class CarInterfaceBase(ABC):
def get_steer_feedforward_function(self): def get_steer_feedforward_function(self):
return self.get_steer_feedforward_default return self.get_steer_feedforward_default
@staticmethod
def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation):
# The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction)
friction_interp = interp(
apply_deadzone(lateral_accel_error, lateral_accel_deadzone),
[-FRICTION_THRESHOLD, FRICTION_THRESHOLD],
[-torque_params.friction, torque_params.friction]
)
friction = friction_interp if friction_compensation else 0.0
return (lateral_accel_value / torque_params.latAccelFactor) + friction
def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType:
return self.torque_from_lateral_accel_linear
# returns a set of default params to avoid repetition in car specific params # returns a set of default params to avoid repetition in car specific params
@staticmethod @staticmethod
def get_std_params(candidate, fingerprint): def get_std_params(candidate, fingerprint):
@ -144,10 +162,12 @@ class CarInterfaceBase(ABC):
tune.init('torque') tune.init('torque')
tune.torque.useSteeringAngle = use_steering_angle tune.torque.useSteeringAngle = use_steering_angle
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR'] tune.torque.kp = 1.0
tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR'] tune.torque.kf = 1.0
tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR'] tune.torque.ki = 0.1
tune.torque.friction = params['FRICTION'] tune.torque.friction = params['FRICTION']
tune.torque.latAccelFactor = params['LAT_ACCEL_FACTOR']
tune.torque.latAccelOffset = 0.0
tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg tune.torque.steeringAngleDeadzoneDeg = steering_angle_deadzone_deg
@abstractmethod @abstractmethod
@ -171,6 +191,12 @@ class CarInterfaceBase(ABC):
else: else:
self.v_ego_cluster_seen = True self.v_ego_cluster_seen = True
# Many cars apply hysteresis to the ego dash speed
if self.CS is not None:
ret.vEgoCluster = apply_hysteresis(ret.vEgoCluster, self.CS.out.vEgoCluster, self.CS.cluster_speed_hyst_gap)
if abs(ret.vEgo) < self.CS.cluster_min_speed:
ret.vEgoCluster = 0.0
if ret.cruiseState.speedCluster == 0: if ret.cruiseState.speedCluster == 0:
ret.cruiseState.speedCluster = ret.cruiseState.speed ret.cruiseState.speedCluster = ret.cruiseState.speed
@ -185,7 +211,8 @@ class CarInterfaceBase(ABC):
def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]:
pass pass
def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True): def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True,
enable_buttons=(ButtonType.accelCruise, ButtonType.decelCruise)):
events = Events() events = Events()
if cs_out.doorOpen: if cs_out.doorOpen:
@ -215,9 +242,17 @@ class CarInterfaceBase(ABC):
events.add(EventName.parkBrake) events.add(EventName.parkBrake)
if cs_out.accFaulted: if cs_out.accFaulted:
events.add(EventName.accFaulted) events.add(EventName.accFaulted)
if cs_out.steeringPressed:
events.add(EventName.steerOverride)
# Handle button presses # Handle button presses
events.events.extend(create_button_enable_events(cs_out.buttonEvents, pcm_cruise=self.CP.pcmCruise)) for b in cs_out.buttonEvents:
# Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port)
if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed):
events.add(EventName.buttonEnable)
# Disable on rising and falling edge of cancel for both stock and OP long
if b.type == ButtonType.cancel:
events.add(EventName.buttonCancel)
# Handle permanent and temporary steering faults # Handle permanent and temporary steering faults
self.steering_unpressed = 0 if cs_out.steeringPressed else self.steering_unpressed + 1 self.steering_unpressed = 0 if cs_out.steeringPressed else self.steering_unpressed + 1
@ -270,6 +305,8 @@ class CarStateBase(ABC):
self.right_blinker_cnt = 0 self.right_blinker_cnt = 0
self.left_blinker_prev = False self.left_blinker_prev = False
self.right_blinker_prev = False self.right_blinker_prev = False
self.cluster_speed_hyst_gap = 0.0
self.cluster_min_speed = 0.0 # min speed before dropping to 0
# Q = np.matrix([[0.0, 0.0], [0.0, 100.0]]) # Q = np.matrix([[0.0, 0.0], [0.0, 100.0]])
# R = 0.3 # R = 0.3

@ -1,7 +1,6 @@
import time import time
from collections import defaultdict from collections import defaultdict
from functools import partial from functools import partial
from typing import Optional
import cereal.messaging as messaging import cereal.messaging as messaging
from system.swaglog import cloudlog from system.swaglog import cloudlog
@ -10,24 +9,21 @@ from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_a
class IsoTpParallelQuery: class IsoTpParallelQuery:
def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addr=False, debug=False, response_pending_timeout=10): def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addrs=None, debug=False, response_pending_timeout=10):
self.sendcan = sendcan self.sendcan = sendcan
self.logcan = logcan self.logcan = logcan
self.bus = bus self.bus = bus
self.request = request self.request = request
self.response = response self.response = response
self.functional_addrs = functional_addrs or []
self.debug = debug self.debug = debug
self.functional_addr = functional_addr
self.response_pending_timeout = response_pending_timeout self.response_pending_timeout = response_pending_timeout
self.real_addrs = [] real_addrs = [a if isinstance(a, tuple) else (a, None) for a in addrs]
for a in addrs: for tx_addr, _ in real_addrs:
if isinstance(a, tuple): assert tx_addr not in FUNCTIONAL_ADDRS, f"Functional address should be defined in functional_addrs: {hex(tx_addr)}"
self.real_addrs.append(a)
else:
self.real_addrs.append((a, None))
self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in self.real_addrs} self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs}
self.msg_buffer = defaultdict(list) self.msg_buffer = defaultdict(list)
def rx(self): def rx(self):
@ -36,13 +32,8 @@ class IsoTpParallelQuery:
for packet in can_packets: for packet in can_packets:
for msg in packet.can: for msg in packet.can:
if msg.src == self.bus: if msg.src == self.bus and msg.address in self.msg_addrs.values():
if self.functional_addr: self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
if (0x7E8 <= msg.address <= 0x7EF) or (0x18DAF100 <= msg.address <= 0x18DAF1FF):
fn_addr = next(a for a in FUNCTIONAL_ADDRS if msg.address - a <= 32)
self.msg_buffer[fn_addr].append((msg.address, msg.busTime, msg.dat, msg.src))
elif msg.address in self.msg_addrs.values():
self.msg_buffer[msg.address].append((msg.address, msg.busTime, msg.dat, msg.src))
def _can_tx(self, tx_addr, dat, bus): def _can_tx(self, tx_addr, dat, bus):
"""Helper function to send single message""" """Helper function to send single message"""
@ -72,6 +63,16 @@ class IsoTpParallelQuery:
messaging.drain_sock(self.logcan) messaging.drain_sock(self.logcan)
self.msg_buffer = defaultdict(list) self.msg_buffer = defaultdict(list)
def _create_isotp_msg(self, tx_addr, sub_addr, rx_addr):
can_client = CanClient(self._can_tx, partial(self._can_rx, rx_addr, sub_addr=sub_addr), tx_addr, rx_addr,
self.bus, sub_addr=sub_addr, debug=self.debug)
max_len = 8 if sub_addr is None else 7
# uses iso-tp frame separation time of 10 ms
# TODO: use single_frame_mode so ECUs can send as fast as they want,
# as well as reduces chances we process messages from previous queries
return IsoTpMessage(can_client, timeout=0, separation_time=0.01, debug=self.debug, max_len=max_len)
def get_data(self, timeout, total_timeout=60.): def get_data(self, timeout, total_timeout=60.):
self._drain_rx() self._drain_rx()
@ -80,22 +81,19 @@ class IsoTpParallelQuery:
request_counter = {} request_counter = {}
request_done = {} request_done = {}
for tx_addr, rx_addr in self.msg_addrs.items(): for tx_addr, rx_addr in self.msg_addrs.items():
# rx_addr not set when using functional tx addr msgs[tx_addr] = self._create_isotp_msg(*tx_addr, rx_addr)
id_addr = rx_addr or tx_addr[0]
sub_addr = tx_addr[1]
can_client = CanClient(self._can_tx, partial(self._can_rx, id_addr, sub_addr=sub_addr), tx_addr[0], rx_addr,
self.bus, sub_addr=sub_addr, debug=self.debug)
max_len = 8 if sub_addr is None else 7
msg = IsoTpMessage(can_client, timeout=0, max_len=max_len, debug=self.debug)
msg.send(self.request[0])
msgs[tx_addr] = msg
request_counter[tx_addr] = 0 request_counter[tx_addr] = 0
request_done[tx_addr] = False request_done[tx_addr] = False
# Send first request to functional addrs, subsequent responses are handled on physical addrs
if len(self.functional_addrs):
for addr in self.functional_addrs:
self._create_isotp_msg(addr, None, -1).send(self.request[0])
# If querying functional addrs, set up physical IsoTpMessages to send consecutive frames
for msg in msgs.values():
msg.send(self.request[0], setup_only=len(self.functional_addrs) > 0)
results = {} results = {}
start_time = time.monotonic() start_time = time.monotonic()
response_timeouts = {tx_addr: start_time + timeout for tx_addr in self.msg_addrs} response_timeouts = {tx_addr: start_time + timeout for tx_addr in self.msg_addrs}
@ -107,12 +105,15 @@ class IsoTpParallelQuery:
for tx_addr, msg in msgs.items(): for tx_addr, msg in msgs.items():
try: try:
dat: Optional[bytes] = msg.recv() dat, updated = msg.recv()
except Exception: except Exception:
cloudlog.exception("Error processing UDS response") cloudlog.exception(f"Error processing UDS response: {tx_addr}")
request_done[tx_addr] = True request_done[tx_addr] = True
continue continue
if updated:
response_timeouts[tx_addr] = time.monotonic() + timeout
if not dat: if not dat:
continue continue
@ -121,12 +122,11 @@ class IsoTpParallelQuery:
response_valid = dat[:len(expected_response)] == expected_response response_valid = dat[:len(expected_response)] == expected_response
if response_valid: if response_valid:
response_timeouts[tx_addr] = time.monotonic() + timeout
if counter + 1 < len(self.request): if counter + 1 < len(self.request):
msg.send(self.request[counter + 1]) msg.send(self.request[counter + 1])
request_counter[tx_addr] += 1 request_counter[tx_addr] += 1
else: else:
results[(tx_addr, msg._can_client.rx_addr)] = dat[len(expected_response):] results[tx_addr] = dat[len(expected_response):]
request_done[tx_addr] = True request_done[tx_addr] = True
else: else:
error_code = dat[2] if len(dat) > 2 else -1 error_code = dat[2] if len(dat) > 2 else -1

@ -10,10 +10,6 @@ EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@staticmethod
def compute_gb(accel, speed):
return float(accel) / 4.0
@staticmethod @staticmethod
def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False):
ret = CarInterfaceBase.get_std_params(candidate, fingerprint) ret = CarInterfaceBase.get_std_params(candidate, fingerprint)

@ -47,7 +47,7 @@ CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = {
CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"), CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"),
CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"), CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"),
CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22", video_link="https://youtu.be/dA3duO4a0O4"), CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22", video_link="https://youtu.be/dA3duO4a0O4"),
CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022"), CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-23"),
} }
@ -93,6 +93,7 @@ FW_VERSIONS = {
], ],
(Ecu.fwdCamera, 0x706, None): [ (Ecu.fwdCamera, 0x706, None): [
b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -102,13 +103,15 @@ FW_VERSIONS = {
}, },
CAR.CX5: { CAR.CX5: {
(Ecu.eps, 0x730, None): [ (Ecu.eps, 0x730, None): [
b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -150,6 +153,7 @@ FW_VERSIONS = {
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PA66-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',

@ -20,18 +20,13 @@ class CarInterface(CarInterfaceBase):
cloudlog.debug("Using Mock Car Interface") cloudlog.debug("Using Mock Car Interface")
self.sensor = messaging.sub_sock('sensorEvents') self.sm = messaging.SubMaster(['gyroscope', 'gpsLocation', 'gpsLocationExternal'])
self.gps = messaging.sub_sock('gpsLocationExternal')
self.speed = 0. self.speed = 0.
self.prev_speed = 0. self.prev_speed = 0.
self.yaw_rate = 0. self.yaw_rate = 0.
self.yaw_rate_meas = 0. self.yaw_rate_meas = 0.
@staticmethod
def compute_gb(accel, speed):
return accel
@staticmethod @staticmethod
def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False):
ret = CarInterfaceBase.get_std_params(candidate, fingerprint) ret = CarInterfaceBase.get_std_params(candidate, fingerprint)
@ -49,17 +44,16 @@ class CarInterface(CarInterfaceBase):
# returns a car.CarState # returns a car.CarState
def _update(self, c): def _update(self, c):
self.sm.update(0)
# get basic data from phone and gps since CAN isn't connected # get basic data from phone and gps since CAN isn't connected
sensors = messaging.recv_sock(self.sensor) if self.sm.updated['gyroscope']:
if sensors is not None: self.yaw_rate_meas = -self.sm['gyroscope'].gyroUncalibrated.v[0]
for sensor in sensors.sensorEvents:
if sensor.type == 4: # gyro gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation'
self.yaw_rate_meas = -sensor.gyro.v[0] if self.sm.updated[gps_sock]:
gps = messaging.recv_sock(self.gps)
if gps is not None:
self.prev_speed = self.speed self.prev_speed = self.speed
self.speed = gps.gpsLocationExternal.speed self.speed = self.sm[gps_sock].speed
# create message # create message
ret = car.CarState.new_message() ret = car.CarState.new_message()

@ -44,7 +44,7 @@ class CarState(CarStateBase):
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.01 ret.standstill = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] == 0.0 and cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] == 0.0
if self.CP.carFingerprint == CAR.ALTIMA: if self.CP.carFingerprint == CAR.ALTIMA:
ret.cruiseState.enabled = bool(cp.vl["CRUISE_STATE"]["CRUISE_ENABLED"]) ret.cruiseState.enabled = bool(cp.vl["CRUISE_STATE"]["CRUISE_ENABLED"])

@ -32,9 +32,8 @@ class CarState(CarStateBase):
cp_wheels.vl["Wheel_Speeds"]["RR"], cp_wheels.vl["Wheel_Speeds"]["RR"],
) )
ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4.
# Kalman filter, even though Subaru raw wheel speed is heaviliy filtered by default
ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw)
ret.standstill = ret.vEgoRaw < 0.01 ret.standstill = ret.vEgoRaw == 0
# continuous blinker signals for assisted lane change # continuous blinker signals for assisted lane change
ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"], ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["Dashlights"]["LEFT_BLINKER"],
@ -59,9 +58,6 @@ class CarState(CarStateBase):
ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0 ret.cruiseState.available = cp_cruise.vl["CruiseControl"]["Cruise_On"] != 0
ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS ret.cruiseState.speed = cp_cam.vl["ES_DashStatus"]["Cruise_Set_Speed"] * CV.KPH_TO_MS
if self.car_fingerprint not in PREGLOBAL_CARS:
ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3
if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \ if (self.car_fingerprint in PREGLOBAL_CARS and cp.vl["Dash_State2"]["UNITS"] == 1) or \
(self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1): (self.car_fingerprint not in PREGLOBAL_CARS and cp.vl["Dashlights"]["UNITS"] == 1):
ret.cruiseState.speed *= CV.MPH_TO_KPH ret.cruiseState.speed *= CV.MPH_TO_KPH
@ -79,6 +75,8 @@ class CarState(CarStateBase):
else: else:
ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.steerFaultTemporary = cp.vl["Steering_Torque"]["Steer_Warning"] == 1
ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1
ret.cruiseState.standstill = cp_cam.vl["ES_DashStatus"]["Cruise_State"] == 3
ret.stockFcw = cp_cam.vl["ES_LKAS_State"]["LKAS_Alert"] == 2
self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"])
cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam cp_es_distance = cp_body if self.car_fingerprint in GLOBAL_GEN2 else cp_cam

@ -104,6 +104,7 @@ FW_VERSIONS = {
b'\000\000e~\037@ \'', b'\000\000e~\037@ \'',
b'\x00\x00e@\x1f@ $', b'\x00\x00e@\x1f@ $',
b'\x00\x00d\xb9\x00\x00\x00\x00', b'\x00\x00d\xb9\x00\x00\x00\x00',
b'\x00\x00e@\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xbb,\xa0t\a', b'\xbb,\xa0t\a',
@ -111,33 +112,38 @@ FW_VERSIONS = {
b'\xf1\x82\xbb,\xa0t\a', b'\xf1\x82\xbb,\xa0t\a',
b'\xf1\x82\xd9,\xa0@\a', b'\xf1\x82\xd9,\xa0@\a',
b'\xf1\x82\xd1,\xa0q\x07', b'\xf1\x82\xd1,\xa0q\x07',
b'\xd1,\xa0q\x07',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\x00\xfe\xf7\x00\x00', b'\x00\xfe\xf7\x00\x00',
b'\001\xfe\xf9\000\000', b'\001\xfe\xf9\000\000',
b'\x01\xfe\xf7\x00\x00', b'\x01\xfe\xf7\x00\x00',
b'\x01\xfe\xfa\x00\x00',
], ],
}, },
CAR.LEGACY: { CAR.LEGACY: {
(Ecu.abs, 0x7b0, None): [ (Ecu.abs, 0x7b0, None): [
b'\xa1\\ x04\x01', b'\xa1\\ x04\x01',
b'\xa1 \x03\x03' b'\xa1 \x03\x03',
b'\xa1 \x02\x01',
], ],
(Ecu.eps, 0x746, None): [ (Ecu.eps, 0x746, None): [
b'\x9b\xc0\x11\x00', b'\x9b\xc0\x11\x00',
b'\x9b\xc0\x11\x02' b'\x9b\xc0\x11\x02',
], ],
(Ecu.fwdCamera, 0x787, None): [ (Ecu.fwdCamera, 0x787, None): [
b'\x00\x00e\x80\x00\x1f@ \x19\x00', b'\x00\x00e\x80\x00\x1f@ \x19\x00',
b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00' b'\x00\x00e\x9a\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xde\"a0\x07', b'\xde\"a0\x07',
b'\xe2"aq\x07' b'\xe2"aq\x07',
b'\xde,\xa0@\x07',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xa5\xf6\x05@\x00', b'\xa5\xf6\x05@\x00',
b'\xa7\xf6\x04@\x00' b'\xa7\xf6\x04@\x00',
b'\xa5\xfe\xc7@\x00',
], ],
}, },
CAR.IMPREZA: { CAR.IMPREZA: {
@ -190,6 +196,7 @@ FW_VERSIONS = {
b'\xaa!dt\a', b'\xaa!dt\a',
b'\xc5!ar\a', b'\xc5!ar\a',
b'\xbe!as\a', b'\xbe!as\a',
b'\xc5!as\x07',
b'\xc5!ds\a', b'\xc5!ds\a',
b'\xc5!`s\a', b'\xc5!`s\a',
b'\xaa!au\a', b'\xaa!au\a',
@ -454,6 +461,7 @@ FW_VERSIONS = {
b'\xa1 \x08\x02', b'\xa1 \x08\x02',
b'\xa1 \x06\x02', b'\xa1 \x06\x02',
b'\xa1 \x08\x00', b'\xa1 \x08\x00',
b'\xa1 "\t\x00',
], ],
(Ecu.eps, 0x746, None): [ (Ecu.eps, 0x746, None): [
b'\x9b\xc0\x10\x00', b'\x9b\xc0\x10\x00',
@ -475,6 +483,7 @@ FW_VERSIONS = {
b'\xe2"`p\x07', b'\xe2"`p\x07',
b'\xf1\x82\xe2,\xa0@\x07', b'\xf1\x82\xe2,\xa0@\x07',
b'\xbc"`q\x07', b'\xbc"`q\x07',
b'\xe3,\xa0@\x07',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xa5\xfe\xf7@\x00', b'\xa5\xfe\xf7@\x00',

@ -1,9 +1,12 @@
from collections import namedtuple from collections import namedtuple
from typing import Dict, List, Union from typing import Dict, List, Union
from cereal import car
from selfdrive.car import dbc_dict from selfdrive.car import dbc_dict
from selfdrive.car.docs_definitions import CarInfo from selfdrive.car.docs_definitions import CarInfo
from cereal import car from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries
Ecu = car.CarParams.Ecu
Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values'])
AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points'])
@ -20,11 +23,6 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
} }
FINGERPRINTS = { FINGERPRINTS = {
CAR.AP2_MODELS: [
{
1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 277: 6, 280: 6, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 518: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 538: 8, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 576: 3, 577: 8, 582: 5, 583: 8, 584: 4, 585: 8, 590: 8, 601: 8, 606: 8, 608: 1, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 692: 8, 693: 8, 695: 8, 696: 8, 697: 8, 699: 8, 700: 8, 701: 8, 702: 8, 703: 8, 704: 8, 708: 8, 709: 8, 710: 8, 711: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 811: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 845: 8, 846: 5, 848: 8, 852: 8, 853: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 876: 8, 877: 8, 879: 8, 880: 8, 882: 8, 884: 8, 888: 8, 893: 8, 894: 8, 901: 6, 904: 3, 905: 8, 906: 8, 908: 2, 909: 8, 910: 8, 912: 8, 920: 8, 921: 8, 925: 4, 926: 6, 936: 8, 941: 8, 949: 8, 952: 8, 953: 6, 968: 8, 969: 6, 970: 8, 971: 8, 977: 8, 984: 8, 987: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1007: 8, 1008: 8, 1010: 6, 1014: 1, 1015: 8, 1016: 8, 1017: 8, 1018: 8, 1020: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1049: 8, 1061: 8, 1064: 8, 1065: 8, 1070: 8, 1080: 8, 1081: 8, 1097: 8, 1113: 8, 1129: 8, 1145: 8, 1160: 4, 1177: 8, 1281: 8, 1328: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1353: 8, 1368: 8, 1412: 8, 1436: 8, 1476: 8, 1481: 8, 1497: 8, 1513: 8, 1519: 8, 1601: 8, 1605: 8, 1617: 8, 1621: 8, 1625: 8, 1665: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1824: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2015: 8, 2043: 5, 2045: 4
},
],
CAR.AP1_MODELS: [ CAR.AP1_MODELS: [
{ {
1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4 1: 8, 3: 8, 14: 8, 21: 4, 69: 8, 109: 4, 257: 3, 264: 8, 267: 5, 277: 6, 280: 6, 283: 5, 293: 4, 296: 4, 309: 5, 325: 8, 328: 5, 336: 8, 341: 8, 360: 7, 373: 8, 389: 8, 415: 8, 513: 5, 516: 8, 520: 4, 522: 8, 524: 8, 526: 8, 532: 3, 536: 8, 537: 3, 542: 8, 551: 5, 552: 2, 556: 8, 558: 8, 568: 8, 569: 8, 574: 8, 577: 8, 582: 5, 584: 4, 585: 8, 590: 8, 606: 8, 622: 8, 627: 6, 638: 8, 641: 8, 643: 8, 660: 5, 693: 8, 696: 8, 697: 8, 712: 8, 728: 8, 744: 8, 760: 8, 772: 8, 775: 8, 776: 8, 777: 8, 778: 8, 782: 8, 788: 8, 791: 8, 792: 8, 796: 2, 797: 8, 798: 6, 799: 8, 804: 8, 805: 8, 807: 8, 808: 1, 809: 8, 812: 8, 813: 8, 814: 5, 815: 8, 820: 8, 823: 8, 824: 8, 829: 8, 830: 5, 836: 8, 840: 8, 841: 8, 845: 8, 846: 5, 852: 8, 856: 4, 857: 6, 861: 8, 862: 5, 872: 8, 873: 8, 877: 8, 878: 8, 879: 8, 880: 8, 884: 8, 888: 8, 889: 8, 893: 8, 896: 8, 901: 6, 904: 3, 905: 8, 908: 2, 909: 8, 920: 8, 921: 8, 925: 4, 936: 8, 937: 8, 941: 8, 949: 8, 952: 8, 953: 6, 957: 8, 968: 8, 973: 8, 984: 8, 987: 8, 989: 8, 990: 8, 1000: 8, 1001: 8, 1006: 8, 1016: 8, 1026: 8, 1028: 8, 1029: 8, 1030: 8, 1032: 1, 1033: 1, 1034: 8, 1048: 1, 1064: 8, 1070: 8, 1080: 8, 1160: 4, 1281: 8, 1329: 8, 1332: 8, 1335: 8, 1337: 8, 1368: 8, 1412: 8, 1436: 8, 1465: 8, 1476: 8, 1497: 8, 1524: 8, 1527: 8, 1601: 8, 1605: 8, 1611: 8, 1614: 8, 1617: 8, 1621: 8, 1627: 8, 1630: 8, 1800: 4, 1804: 8, 1812: 8, 1815: 8, 1816: 8, 1828: 8, 1831: 8, 1832: 8, 1840: 8, 1848: 8, 1864: 8, 1880: 8, 1892: 8, 1896: 8, 1912: 8, 1960: 8, 1992: 8, 2008: 3, 2043: 5, 2045: 4
@ -37,6 +35,42 @@ DBC = {
CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'), CAR.AP1_MODELS: dbc_dict('tesla_powertrain', 'tesla_radar', chassis_dbc='tesla_can'),
} }
FW_QUERY_CONFIG = FwQueryConfig(
requests=[
Request(
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
whitelist_ecus=[Ecu.eps],
rx_offset=0x08,
bus=0,
),
Request(
[StdQueries.TESTER_PRESENT_REQUEST, StdQueries.UDS_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.UDS_VERSION_RESPONSE],
whitelist_ecus=[Ecu.adas, Ecu.electricBrakeBooster, Ecu.fwdRadar],
rx_offset=0x10,
bus=0,
),
]
)
FW_VERSIONS = {
CAR.AP2_MODELS: {
(Ecu.adas, 0x649, None): [
b'\x01\x00\x8b\x07\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11',
],
(Ecu.electricBrakeBooster, 0x64d, None): [
b'1037123-00-A',
],
(Ecu.fwdRadar, 0x671, None): [
b'\x01\x00W\x00\x00\x00\x07\x00\x00\x00\x00\x08\x01\x00\x00\x00\x07\xff\xfe',
],
(Ecu.eps, 0x730, None): [
b'\x10#\x01',
],
},
}
class CANBUS: class CANBUS:
# Lateral harness # Lateral harness
chassis = 0 chassis = 0
@ -65,8 +99,8 @@ BUTTONS = [
Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]), Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]),
Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]), Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]),
Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]), Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]),
Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]),
Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]),
] ]
class CarControllerParams: class CarControllerParams:

@ -21,9 +21,11 @@ non_tested_cars = [
GM.CADILLAC_ATS, GM.CADILLAC_ATS,
GM.HOLDEN_ASTRA, GM.HOLDEN_ASTRA,
GM.MALIBU, GM.MALIBU,
HYUNDAI.ELANTRA_GT_I30, GM.EQUINOX,
GM.BOLT_EV,
HYUNDAI.GENESIS_G90, HYUNDAI.GENESIS_G90,
HYUNDAI.KIA_OPTIMA_H, HYUNDAI.KIA_OPTIMA_H,
HONDA.ODYSSEY_CHN,
] ]
CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,)) CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,))
@ -48,7 +50,7 @@ routes = [
CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL), CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL),
CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV),
CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT),
CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV), CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1),
CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO), CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO),
CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G), CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G),
@ -60,7 +62,6 @@ routes = [
CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED), CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED),
CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV), CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV),
CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX), CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX),
CarTestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN),
CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T
CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T
CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs
@ -80,21 +81,25 @@ routes = [
CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS),
CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70),
CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN),
CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80),
CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN),
CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE),
CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022),
CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022),
CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1), CarTestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022, segment=1),
CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), CarTestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED),
CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA), CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4),
CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0),
CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS),
CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN),
CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA),
CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF),
CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON),
CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN),
CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO),
CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE),
CarTestRoute("22de8111a8c5463c|2022-07-29--13-34-49", HYUNDAI.IONIQ_5), CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2
CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019),
CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV),
CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020),
@ -106,14 +111,18 @@ routes = [
CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11),
CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV),
CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER),
CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022),
CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER),
CarTestRoute("d824e27e8c60172c|2022-05-19--16-15-28", HYUNDAI.KIA_EV6), CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2
CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1
CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021),
CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV),
CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV),
CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021), CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021),
CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA), # 2019 Elantra GT
CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021),
CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021), CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021),
CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID), CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID),
@ -167,11 +176,13 @@ routes = [
CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1),
CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1), CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1),
CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC
CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal
CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7), CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7),
CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8), CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8),
CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS), CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS),
CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6), CarTestRoute("0cd0b7f7e31a3853|2021-11-03--19-30-22", VOLKSWAGEN.POLO_MK6),
CarTestRoute("064d1816e448f8eb|2022-09-29--15-32-34", VOLKSWAGEN.SHARAN_MK2),
CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1), CarTestRoute("7d82b2f3a9115f1f|2021-10-21--15-39-42", VOLKSWAGEN.TAOS_MK1),
CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1), CarTestRoute("2744c89a8dda9a51|2021-07-24--21-28-06", VOLKSWAGEN.TCROSS_MK1),
CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2), CarTestRoute("2cef8a0b898f331a|2021-03-25--20-13-57", VOLKSWAGEN.TIGUAN_MK2),
@ -194,7 +205,7 @@ routes = [
CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), CarTestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER),
CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), CarTestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA),
CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), CarTestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020),
CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=3), CarTestRoute("1bbe6bf2d62f58a8|2022-07-14--17-11-43", SUBARU.OUTBACK, segment=10),
CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3), CarTestRoute("c56e69bbc74b8fad|2022-08-18--09-43-51", SUBARU.LEGACY, segment=3),
# Pre-global, dashcam # Pre-global, dashcam
CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), CarTestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL),

@ -34,15 +34,18 @@ class TestCarInterfaces(unittest.TestCase):
self.assertGreater(car_params.maxLateralAccel, 0) self.assertGreater(car_params.maxLateralAccel, 0)
if car_params.steerControlType != car.CarParams.SteerControlType.angle: if car_params.steerControlType != car.CarParams.SteerControlType.angle:
tuning = car_params.lateralTuning.which() tune = car_params.lateralTuning
if tuning == 'pid': if tune.which() == 'pid':
self.assertTrue(len(car_params.lateralTuning.pid.kpV)) self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0)
elif tuning == 'torque': self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP))
kf = car_params.lateralTuning.torque.kf self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP))
self.assertTrue(not math.isnan(kf) and kf > 0)
self.assertTrue(not math.isnan(car_params.lateralTuning.torque.friction)) elif tune.which() == 'torque':
elif tuning == 'indi': self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0)
self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV)) self.assertTrue(not math.isnan(tune.torque.friction))
elif tune.which() == 'indi':
self.assertTrue(len(tune.indi.outerLoopGainV))
# Run car interface # Run car interface
CC = car.CarControl.new_message() CC = car.CarControl.new_message()

@ -1,4 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from collections import defaultdict
import re import re
import unittest import unittest
@ -9,8 +10,9 @@ from selfdrive.car.honda.values import CAR as HONDA
class TestCarDocs(unittest.TestCase): class TestCarDocs(unittest.TestCase):
def setUp(self): @classmethod
self.all_cars = get_all_car_info() def setUpClass(cls):
cls.all_cars = get_all_car_info()
def test_generator(self): def test_generator(self):
generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
@ -20,6 +22,15 @@ class TestCarDocs(unittest.TestCase):
self.assertEqual(generated_cars_md, current_cars_md, self.assertEqual(generated_cars_md, current_cars_md,
"Run selfdrive/car/docs.py to update the compatibility documentation") "Run selfdrive/car/docs.py to update the compatibility documentation")
def test_duplicate_years(self):
make_model_years = defaultdict(list)
for car in self.all_cars:
with self.subTest(car_info_name=car.name):
make_model = (car.make, car.model)
for year in car.year_list:
self.assertNotIn(year, make_model_years[make_model], f"{car.name}: Duplicate model year")
make_model_years[make_model].append(year)
def test_missing_car_info(self): def test_missing_car_info(self):
all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys() all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys()
for platform in sorted(interfaces.keys()): for platform in sorted(interfaces.keys()):
@ -34,9 +45,10 @@ class TestCarDocs(unittest.TestCase):
if car.car_name == "hyundai": if car.car_name == "hyundai":
self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`") self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
self.assertNotIn("hev", tokens, "Use `Hybrid`") self.assertNotIn("hev", tokens, "Use `Hybrid`")
self.assertNotIn("ev", tokens, "Use `Electric`")
if "plug-in hybrid" in car.model.lower(): if "plug-in hybrid" in car.model.lower():
self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization") self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
if car.make != "Kia":
self.assertNotIn("ev", tokens, "Use `Electric`")
elif car.car_name == "toyota": elif car.car_name == "toyota":
if "rav4" in tokens: if "rav4" in tokens:
self.assertIn("RAV4", car.model, "Use correct capitalization") self.assertIn("RAV4", car.model, "Use correct capitalization")

@ -1,6 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import random import random
import unittest import unittest
from collections import defaultdict
from parameterized import parameterized from parameterized import parameterized
from cereal import car from cereal import car
@ -44,6 +45,24 @@ class TestFwFingerprint(unittest.TestCase):
duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1} duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1}
self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}") self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}")
def test_all_addrs_map_to_one_ecu(self):
for brand, cars in VERSIONS.items():
addr_to_ecu = defaultdict(set)
for ecus in cars.values():
for ecu_type, addr, sub_addr in ecus.keys():
addr_to_ecu[(addr, sub_addr)].add(ecu_type)
ecus_for_addr = addr_to_ecu[(addr, sub_addr)]
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr])
self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})")
def test_data_collection_ecus(self):
# Asserts no extra ECUs are in the fingerprinting database
for brand, config in FW_QUERY_CONFIGS.items():
for car_model, ecus in VERSIONS[brand].items():
bad_ecus = set(ecus).intersection(config.extra_ecus)
with self.subTest(car_model=car_model):
self.assertFalse(len(bad_ecus), f'{car_model}: Fingerprints contain ECUs added for data collection: {bad_ecus}')
def test_blacklisted_ecus(self): def test_blacklisted_ecus(self):
blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu
for car_model, ecus in FW_VERSIONS.items(): for car_model, ecus in FW_VERSIONS.items():
@ -73,10 +92,11 @@ class TestFwFingerprint(unittest.TestCase):
def test_fw_request_ecu_whitelist(self): def test_fw_request_ecu_whitelist(self):
for brand, config in FW_QUERY_CONFIGS.items(): for brand, config in FW_QUERY_CONFIGS.items():
with self.subTest(brand=brand): with self.subTest(brand=brand):
whitelisted_ecus = set([ecu for r in config.requests for ecu in r.whitelist_ecus]) whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus}
brand_ecus = set([fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw]) brand_ecus = {fw[0] for car_fw in VERSIONS[brand].values() for fw in car_fw}
brand_ecus |= {ecu[0] for ecu in config.extra_ecus}
# each ecu in brand's fw versions needs to be whitelisted at least once # each ecu in brand's fw versions + extra ecus needs to be whitelisted at least once
ecus_not_whitelisted = brand_ecus - whitelisted_ecus ecus_not_whitelisted = brand_ecus - whitelisted_ecus
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted]) ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted])

@ -9,7 +9,7 @@ from parameterized import parameterized_class
from cereal import log, car from cereal import log, car
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.boardd.boardd import can_capnp_to_can_list, can_list_to_can_capnp from selfdrive.boardd.boardd import can_capnp_to_can_list
from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.fingerprints import all_known_cars
from selfdrive.car.car_helpers import interfaces from selfdrive.car.car_helpers import interfaces
from selfdrive.car.gm.values import CAR as GM from selfdrive.car.gm.values import CAR as GM
@ -109,6 +109,10 @@ class TestCarModelBase(unittest.TestCase):
assert cls.CP assert cls.CP
assert cls.CP.carFingerprint == cls.car_model assert cls.CP.carFingerprint == cls.car_model
@classmethod
def tearDownClass(cls):
del cls.can_msgs
def setUp(self): def setUp(self):
self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) self.CI = self.CarInterface(self.CP, self.CarController, self.CarState)
assert self.CI assert self.CI
@ -210,18 +214,15 @@ class TestCarModelBase(unittest.TestCase):
# warm up pass, as initial states may be different # warm up pass, as initial states may be different
for can in self.can_msgs[:300]: for can in self.can_msgs[:300]:
self.CI.update(CC, (can.as_builder().to_bytes(), ))
for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): for msg in can_capnp_to_can_list(can.can, src_filter=range(64)):
to_send = package_can_msg(msg) to_send = package_can_msg(msg)
self.safety.safety_rx_hook(to_send) self.safety.safety_rx_hook(to_send)
self.CI.update(CC, (can_list_to_can_capnp([msg, ]), ))
if not self.CP.pcmCruise:
self.safety.set_controls_allowed(0)
controls_allowed_prev = False controls_allowed_prev = False
CS_prev = car.CarState.new_message() CS_prev = car.CarState.new_message()
checks = defaultdict(lambda: 0) checks = defaultdict(lambda: 0)
for can in self.can_msgs: for idx, can in enumerate(self.can_msgs):
CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) CS = self.CI.update(CC, (can.as_builder().to_bytes(), ))
for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): for msg in can_capnp_to_can_list(can.can, src_filter=range(64)):
msg = list(msg) msg = list(msg)
@ -230,11 +231,18 @@ class TestCarModelBase(unittest.TestCase):
ret = self.safety.safety_rx_hook(to_send) ret = self.safety.safety_rx_hook(to_send)
self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}")
# Skip first frame so CS_prev is properly initialized
if idx == 0:
CS_prev = CS
# Button may be left pressed in warm up period
if not self.CP.pcmCruise:
self.safety.set_controls_allowed(0)
continue
# TODO: check rest of panda's carstate (steering, ACC main on, etc.) # TODO: check rest of panda's carstate (steering, ACC main on, etc.)
checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev()
checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available if self.CP.carName not in ("hyundai", "volkswagen", "body"):
if self.CP.carName in ("honda", "toyota"):
# TODO: fix standstill mismatches for other makes # TODO: fix standstill mismatches for other makes
checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving()
@ -244,6 +252,7 @@ class TestCarModelBase(unittest.TestCase):
if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05: if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05:
brake_pressed = False brake_pressed = False
checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev()
checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev()
if self.CP.pcmCruise: if self.CP.pcmCruise:
# On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state.

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

Loading…
Cancel
Save