Merge branch 'master' into pq-upstream

pull/24768/head
Jason Young 3 years ago
commit d17c8aa525
  1. 2
      .github/workflows/prebuilt.yaml
  2. 2
      .github/workflows/release.yaml
  3. 75
      .github/workflows/selfdrive_tests.yaml
  4. 2
      .pre-commit-config.yaml
  5. 10
      README.md
  6. 3
      RELEASES.md
  7. 2
      cereal
  8. 5
      common/modeldata.h
  9. 2
      common/version.h
  10. 100
      docs/CARS.md
  11. 2
      docs/c_docs.rst
  12. 2
      laika_repo
  13. 2
      panda
  14. 21
      release/files_common
  15. BIN
      selfdrive/assets/navigation/direction_turn_left_inactive.png
  16. BIN
      selfdrive/assets/navigation/direction_turn_right_inactive.png
  17. BIN
      selfdrive/assets/navigation/direction_turn_straight_inactive.png
  18. 8
      selfdrive/car/car_helpers.py
  19. 53
      selfdrive/car/chrysler/carcontroller.py
  20. 4
      selfdrive/car/chrysler/chryslercan.py
  21. 8
      selfdrive/car/chrysler/interface.py
  22. 18
      selfdrive/car/chrysler/values.py
  23. 14
      selfdrive/car/disable_ecu.py
  24. 30
      selfdrive/car/fw_versions.py
  25. 23
      selfdrive/car/honda/values.py
  26. 4
      selfdrive/car/hyundai/carcontroller.py
  27. 11
      selfdrive/car/hyundai/carstate.py
  28. 8
      selfdrive/car/hyundai/hda2can.py
  29. 5
      selfdrive/car/hyundai/interface.py
  30. 10
      selfdrive/car/hyundai/values.py
  31. 4
      selfdrive/car/interfaces.py
  32. 2
      selfdrive/car/isotp_parallel_query.py
  33. 6
      selfdrive/car/mazda/values.py
  34. 6
      selfdrive/car/subaru/values.py
  35. 2
      selfdrive/car/torque_data/override.yaml
  36. 2
      selfdrive/car/toyota/carcontroller.py
  37. 28
      selfdrive/car/toyota/values.py
  38. 10
      selfdrive/car/vin.py
  39. 50
      selfdrive/car/volkswagen/values.py
  40. 28
      selfdrive/controls/controlsd.py
  41. 40
      selfdrive/debug/disable_ecu.py
  42. 18
      selfdrive/debug/dump_car_info.py
  43. 90
      selfdrive/debug/print_docs_diff.py
  44. 33
      selfdrive/locationd/laikad.py
  45. 14
      selfdrive/locationd/test/test_laikad.py
  46. 22
      selfdrive/test/process_replay/compare_logs.py
  47. 13
      selfdrive/test/process_replay/process_replay.py
  48. 2
      selfdrive/test/process_replay/ref_commit
  49. 2
      selfdrive/test/process_replay/regen.py
  50. 29
      selfdrive/test/process_replay/test_processes.py
  51. 4
      selfdrive/ui/qt/maps/map.cc
  52. 2
      selfdrive/ui/qt/maps/map_settings.cc
  53. 8
      selfdrive/ui/qt/offroad/networking.cc
  54. 4
      selfdrive/ui/qt/offroad/settings.cc
  55. 2
      selfdrive/ui/qt/widgets/input.cc
  56. 13
      selfdrive/ui/qt/widgets/prime.cc
  57. 39
      selfdrive/ui/translations/README.md
  58. 4
      selfdrive/ui/translations/main_ja.ts
  59. BIN
      selfdrive/ui/translations/main_ko.qm
  60. 177
      selfdrive/ui/translations/main_ko.ts
  61. BIN
      selfdrive/ui/translations/main_zh-CHS.qm
  62. 95
      selfdrive/ui/translations/main_zh-CHS.ts
  63. BIN
      selfdrive/ui/translations/main_zh-CHT.qm
  64. 102
      selfdrive/ui/translations/main_zh-CHT.ts
  65. 10
      selfdrive/ui/update_translations.py
  66. 2
      system/camerad/SConscript
  67. 54
      system/camerad/cameras/camera_common.cc
  68. 16
      system/camerad/cameras/camera_common.h
  69. 26
      system/camerad/cameras/camera_qcom2.cc
  70. 2
      system/camerad/cameras/camera_qcom2.h
  71. 36
      system/camerad/transforms/rgb_to_yuv.cc
  72. 14
      system/camerad/transforms/rgb_to_yuv.h
  73. 191
      system/camerad/transforms/rgb_to_yuv_test.cc
  74. 12
      system/hardware/tici/agnos.py
  75. 74
      system/hardware/tici/tests/compare_casync_manifest.py
  76. 22
      tools/replay/logreader.cc
  77. 2
      tools/replay/logreader.h
  78. 4
      tools/replay/route.cc
  79. 1
      tools/replay/tests/test_replay.cc
  80. 1
      tools/sim/start_carla.sh

@ -25,7 +25,7 @@ jobs:
IMAGE_NAME: openpilot-prebuilt IMAGE_NAME: openpilot-prebuilt
steps: steps:
- name: Wait for green check mark - name: Wait for green check mark
uses: commaai/wait-on-check-action@f16fc3bb6cd4886520b4e9328db1d42104d5cadc uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd
with: with:
ref: master ref: master
wait-interval: 30 wait-interval: 30

@ -12,7 +12,7 @@ jobs:
if: github.repository == 'commaai/openpilot' if: github.repository == 'commaai/openpilot'
steps: steps:
- name: Wait for green check mark - name: Wait for green check mark
uses: commaai/wait-on-check-action@f16fc3bb6cd4886520b4e9328db1d42104d5cadc uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd
with: with:
ref: master ref: master
wait-interval: 30 wait-interval: 30

@ -17,12 +17,12 @@ env:
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true 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 . 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 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 $BASE_IMAGE /bin/sh -c RUN: 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 $BASE_IMAGE /bin/sh -c
BUILD_CL: | BUILD_CL: |
docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true docker pull $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest || true
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 . 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 $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
@ -58,6 +58,9 @@ jobs:
run: | run: |
TARGET_DIR=$STRIPPED_DIR release/build_devel.sh TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
cp Dockerfile.openpilot_base $STRIPPED_DIR cp Dockerfile.openpilot_base $STRIPPED_DIR
cp .pre-commit-config.yaml $STRIPPED_DIR
cp .pylintrc $STRIPPED_DIR
cp mypy.ini $STRIPPED_DIR
- name: Build Docker image - name: Build Docker image
run: | run: |
eval "$BUILD" eval "$BUILD"
@ -66,6 +69,10 @@ jobs:
run: | run: |
cd $STRIPPED_DIR cd $STRIPPED_DIR
${{ env.RUN }} "CI=1 python selfdrive/manager/build.py && \ ${{ env.RUN }} "CI=1 python selfdrive/manager/build.py && \
pre-commit run --all && \
rm .pre-commit-config.yaml && \
rm .pylintrc && \
rm mypy.ini && \
release/check-dirty.sh && \ release/check-dirty.sh && \
python -m unittest discover selfdrive/car" python -m unittest discover selfdrive/car"
@ -510,3 +517,67 @@ jobs:
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/openpilot-docs:latest docker push $DOCKER_REGISTRY/openpilot-docs:latest
car_docs_diff:
name: comment on PR with car docs diff
runs-on: ubuntu-20.04
timeout-minutes: 50
if: github.event_name == 'pull_request'
steps:
- uses: actions/checkout@v3
with:
submodules: true
ref: ${{ github.event.pull_request.base.ref }}
- name: Cache scons
id: scons-cache
# TODO: Change the version to the released version when https://github.com/actions/cache/pull/489 (or 571) is merged.
uses: actions/cache@03e00da99d75a2204924908e1cca7902cafce66b
env:
CACHE_SKIP_SAVE: true
with:
path: /tmp/scons_cache
key: scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}-
restore-keys: |
scons-${{ hashFiles('.github/workflows/selfdrive_tests.yaml') }}-
scons-
- name: Build Docker image
run: eval "$BUILD"
- name: Get base car info
run: |
${{ env.RUN }} "scons -j$(nproc) && python selfdrive/debug/dump_car_info.py --path /tmp/openpilot_cache/base_car_info"
sudo chown -R $USER:$USER ${{ github.workspace }}
- uses: actions/checkout@v3
with:
submodules: true
- name: Save car docs diff
id: save_diff
run: |
${{ env.RUN }} "scons -j$(nproc)"
output=$(${{ env.RUN }} "python selfdrive/debug/print_docs_diff.py --path /tmp/openpilot_cache/base_car_info")
output="${output//$'\n'/'%0A'}"
echo "::set-output name=diff::$output"
- name: Find comment
if: ${{ env.AZURE_TOKEN != '' }}
uses: peter-evans/find-comment@1769778a0c5bd330272d749d12c036d65e70d39d
id: fc
with:
issue-number: ${{ github.event.pull_request.number }}
body-includes: This PR makes changes to
- name: Update comment
if: ${{ steps.save_diff.outputs.diff != '' && env.AZURE_TOKEN != '' }}
uses: peter-evans/create-or-update-comment@b95e16d2859ad843a14218d1028da5b2c4cbc4b4
with:
comment-id: ${{ steps.fc.outputs.comment-id }}
issue-number: ${{ github.event.pull_request.number }}
body: "${{ steps.save_diff.outputs.diff }}"
edit-mode: replace
- name: Delete comment
if: ${{ steps.fc.outputs.comment-id != '' && steps.save_diff.outputs.diff == '' && env.AZURE_TOKEN != '' }}
uses: actions/github-script@v6
with:
script: |
github.rest.issues.deleteComment({
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: ${{ steps.fc.outputs.comment-id }}
})

@ -54,7 +54,7 @@ repos:
entry: cppcheck entry: cppcheck
language: system language: system
types: [c++] types: [c++]
exclude: '^(third_party/)|(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)/|(installer/)' exclude: '^(third_party/)|(pyextra/)|(cereal/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)/|(installer/)'
args: args:
- --error-exitcode=1 - --error-exitcode=1
- --language=c++ - --language=c++

@ -105,23 +105,25 @@ Directory Structure
├── third_party # External libraries ├── third_party # External libraries
├── pyextra # Extra python packages ├── pyextra # Extra python packages
└── system # Generic services └── system # Generic services
├── camerad # Driver to capture images from the camera sensors
├── clocksd # Broadcasts current time
├── hardware # Hardware abstraction classes
├── logcatd # systemd journal as a service ├── logcatd # systemd journal as a service
└── proclogd # Logs information from /proc └── proclogd # Logs information from /proc
└── selfdrive # Code needed to drive the car └── selfdrive # Code needed to drive the car
├── assets # Fonts, images, and sounds for UI ├── assets # Fonts, images, and sounds for UI
├── athena # Allows communication with the app ├── athena # Allows communication with the app
├── boardd # Daemon to talk to the board ├── boardd # Daemon to talk to the board
├── camerad # Driver to capture images from the camera sensors
├── car # Car specific code to read states and control actuators ├── car # Car specific code to read states and control actuators
├── common # Shared C/C++ code for the daemons
├── controls # Planning and controls ├── controls # Planning and controls
├── debug # Tools to help you debug and do car ports ├── debug # Tools to help you debug and do car ports
├── locationd # Precise localization and vehicle parameter estimation ├── locationd # Precise localization and vehicle parameter estimation
├── loggerd # Logger and uploader of car data ├── loggerd # Logger and uploader of car data
├── manager # Deamon that starts/stops all other daemons as needed
├── modeld # Driving and monitoring model runners ├── modeld # Driving and monitoring model runners
├── proclogd # Logs information from proc ├── monitoring # Daemon to determine driver attention
├── sensord # IMU interface code
├── navd # Turn-by-turn navigation ├── navd # Turn-by-turn navigation
├── sensord # IMU interface code
├── test # Unit tests, system tests, and a car simulator ├── test # Unit tests, system tests, and a car simulator
└── ui # The UI └── ui # The UI

@ -1,3 +1,6 @@
Version 0.8.16 (2022-XX-XX)
========================
Version 0.8.15 (2022-07-20) Version 0.8.15 (2022-07-20)
======================== ========================
* New driving model * New driving model

@ -1 +1 @@
Subproject commit cda60ec9652c05de4ccfcad1fae7936e708434a3 Subproject commit a4c1afa3bfcbba989c128ec9b5092f6c91f4da22

@ -34,12 +34,13 @@ const mat3 ecam_intrinsic_matrix = (mat3){{567.0, 0.0, 1928.0 / 2,
0.0, 567.0, 1208.0 / 2, 0.0, 567.0, 1208.0 / 2,
0.0, 0.0, 1.0}}; 0.0, 0.0, 1.0}};
static inline mat3 get_model_yuv_transform(bool bayer = true) { static inline mat3 get_model_yuv_transform() {
float db_s = 1.0; float db_s = 1.0;
const mat3 transform = (mat3){{ const mat3 transform = (mat3){{
1.0, 0.0, 0.0, 1.0, 0.0, 0.0,
0.0, 1.0, 0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 1.0 0.0, 0.0, 1.0
}}; }};
return bayer ? transform_scale_buffer(transform, db_s) : transform; // Can this be removed since scale is 1?
return transform_scale_buffer(transform, db_s);
} }

@ -1 +1 @@
#define COMMA_VERSION "0.8.15" #define COMMA_VERSION "0.8.16"

@ -35,7 +35,7 @@ How We Rate The Cars
**All supported cars can move between the tiers as support changes.** **All supported cars can move between the tiers as support changes.**
# Gold - 31 cars # Gold - 30 cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| |Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained|
|---|---|---|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|
@ -49,18 +49,17 @@ How We Rate The Cars
|Kia|Niro Electric 2021|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Niro Electric 2021|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Niro Electric 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Niro Electric 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Telluride 2020|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Telluride 2020|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|ES 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|ES 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|ES Hybrid 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|ES Hybrid 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX 2020|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX 2020-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX Hybrid 2020|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX Hybrid 2020-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RX 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|RX 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|UX Hybrid 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|UX Hybrid 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Avalon 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Avalon Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry Hybrid 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry Hybrid 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Corolla 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla Cross 2020-21 (Non-US only)|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla Hatchback 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Corolla Hatchback 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla Hybrid 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Corolla Hybrid 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Highlander 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Highlander 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -71,7 +70,7 @@ How We Rate The Cars
|Toyota|RAV4 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 Hybrid 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 Hybrid 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
# Silver - 69 cars # Silver - 78 cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| |Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained|
|---|---|---|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|
@ -80,8 +79,8 @@ How We Rate The Cars
|Audi|RS3 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Audi|RS3 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|S3 2015-17|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Audi|S3 2015-17|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Chevrolet|Volt 2017-18[<sup>1</sup>](#footnotes)|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Chevrolet|Volt 2017-18[<sup>1</sup>](#footnotes)|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Genesis|G70 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Genesis|G70 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Genesis|G80 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Genesis|G80 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Elantra 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Elantra 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Elantra Hybrid 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Elantra Hybrid 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Ioniq Electric 2020|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Ioniq Electric 2020|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -105,7 +104,7 @@ How We Rate The Cars
|Kia|Seltos 2021|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Seltos 2021|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Sorento 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Sorento 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Sorento 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Sorento 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Stinger 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Stinger 2018-20|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|CT Hybrid 2017-18|LSS|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|CT Hybrid 2017-18|LSS|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|ES Hybrid 2017-18|LSS|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|ES Hybrid 2017-18|LSS|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -117,15 +116,17 @@ How We Rate The Cars
|Nissan|X-Trail 2017|ProPILOT|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Nissan|X-Trail 2017|ProPILOT|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|SEAT|Ateca 2018|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |SEAT|Ateca 2018|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|SEAT|Leon 2014-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |SEAT|Leon 2014-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Ascent 2019-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Ascent 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Crosstrek 2020-21|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Crosstrek 2020-21|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Forester 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Forester 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Impreza 2020-21|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Impreza 2020-22|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|XV 2020-21|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|XV 2020-21|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Alphard 2019-20|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Alphard 2019-20|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Alphard Hybrid 2021|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Alphard Hybrid 2021|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry Hybrid 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry Hybrid 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla Cross 2020-21 (Non-US only)|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla Cross Hybrid 2020-22 (Non-US only)|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Highlander 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Highlander 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Highlander Hybrid 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Highlander Hybrid 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Prius 2016-20|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Prius 2016-20|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -133,25 +134,32 @@ How We Rate The Cars
|Toyota|RAV4 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 Hybrid 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 Hybrid 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Atlas 2018-19, 2022[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Atlas 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|e-Golf 2014, 2018-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Atlas Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf 2015-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|e-Golf 2014-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf Alltrack 2017-18|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf 2015-20[<sup>8</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf GTE 2016|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf Alltrack 2015-19|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf GTI 2018-21|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf GTD 2015-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf R 2016-19|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf GTE 2015-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf SportsVan 2016|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf GTI 2015-21|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Golf SportWagen 2015|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf R 2015-19[<sup>8</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Passat 2015-19[<sup>6</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Golf SportsVan 2015-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Polo 2020|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Passat 2015-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Passat Alltrack 2015-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
# Bronze - 80 cars |Volkswagen|Passat GTE 2015-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Polo 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Polo GTI 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Teramont 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Teramont Cross Sport 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Teramont X 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
# Bronze - 83 cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| |Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained|
|---|---|---|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|
|Acura|ILX 2016-19|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|ILX 2016-19|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Acura|RDX 2016-18|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|RDX 2016-18|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Acura|RDX 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|RDX 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|Q2 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Audi|Q2 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|Q3 2020-21|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Audi|Q3 2020-21|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|ACC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>| |Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|ACC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|
@ -159,27 +167,27 @@ How We Rate The Cars
|Chrysler|Pacifica 2019-20|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Chrysler|Pacifica 2019-20|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Genesis|G90 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>| |Genesis|G90 2017-18|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|
|GMC|Acadia 2018[<sup>1</sup>](#footnotes)|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>| |GMC|Acadia 2018[<sup>1</sup>](#footnotes)|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|
|Honda|Accord 2018-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Accord 2018-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Accord Hybrid 2018-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Accord Hybrid 2018-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Civic 2016-18|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Civic 2016-18|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Civic 2019-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>[<sup>2</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Civic 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>[<sup>2</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Civic 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Civic 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Civic Hatchback 2017-21|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Civic Hatchback 2017-21|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Civic Hatchback 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Civic Hatchback 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|CR-V 2015-16|Touring|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|CR-V 2015-16|Touring|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|CR-V 2017-21|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|CR-V 2017-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|CR-V Hybrid 2017-19|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|CR-V Hybrid 2017-19|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|e 2020|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|e 2020|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Fit 2018-19|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Fit 2018-20|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Freed 2020|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Freed 2020|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|HR-V 2019-20|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|HR-V 2019-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Insight 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Insight 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Inspire 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Inspire 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Odyssey 2018-20|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Odyssey 2018-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Passport 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Passport 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Pilot 2016-21|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Pilot 2016-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Honda|Ridgeline 2017-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Honda|Ridgeline 2017-22|Honda Sensing|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Elantra 2017-19|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Elantra 2017-19|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Genesis 2015-16|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Genesis 2015-16|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -190,16 +198,16 @@ How We Rate The Cars
|Hyundai|Tucson 2021|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Tucson 2021|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Veloster 2019-20|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Veloster 2019-20|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Jeep|Grand Cherokee 2016-18|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Jeep|Grand Cherokee 2019-20|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Jeep|Grand Cherokee 2019-21|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Niro Plug-in Hybrid 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Niro Plug-in Hybrid 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Optima 2017|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Optima 2017|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|IS 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|IS 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RC 2020|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|RC 2017-2020|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RX 2016-18|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|RX 2016-18|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RX Hybrid 2016-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|RX Hybrid 2016-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Mazda|CX-5 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Mazda|CX-5 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Mazda|CX-9 2021|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Mazda|CX-9 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Ram|1500 2019-21|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Ram|1500 2019-22|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Crosstrek 2018-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Crosstrek 2018-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Impreza 2017-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|Impreza 2017-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|XV 2018-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Subaru|XV 2018-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -219,11 +227,14 @@ How We Rate The Cars
|Toyota|Prius v 2017|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Prius v 2017|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Sienna 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Sienna 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Arteon 2018, 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Arteon 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Arteon eHybrid 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Arteon R 2020-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|California 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|California 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Caravelle 2020[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Caravelle 2020[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Jetta 2018-21|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|CC 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Jetta GLI 2021|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Jetta 2018-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Jetta GLI 2021-22[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|T-Cross 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|T-Cross 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|T-Roc 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|T-Roc 2021[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Taos 2022[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Taos 2022[<sup>7</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -239,6 +250,7 @@ How We Rate The Cars
<sup>5</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br /> <sup>5</sup>Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform. <br />
<sup>6</sup>Refers only to the MQB-based European B8 Passat, not including the NMS Passat in the USA/China/Mideast markets. <br /> <sup>6</sup>Refers only to the MQB-based European B8 Passat, not including the NMS Passat in the USA/China/Mideast markets. <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). For the newer design, in the interim, choose "VW J533 Development" from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard. <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). For the newer design, in the interim, choose "VW J533 Development" from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard. <br />
<sup>8</sup>Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.) <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/).

@ -29,8 +29,6 @@ camerad
^^^^^^^ ^^^^^^^
.. autodoxygenindex:: .. autodoxygenindex::
:project: system_camerad_cameras :project: system_camerad_cameras
.. autodoxygenindex::
:project: system_camerad_transforms
.. autodoxygenindex:: .. autodoxygenindex::
:project: system_camerad_imgproc :project: system_camerad_imgproc

@ -1 +1 @@
Subproject commit 828612e1b8848ccf70072d5513c0b7977f1707da Subproject commit 3ce2628dfc8ddba1769c518f90275e3caca9e8c6

@ -1 +1 @@
Subproject commit 481b505c07895ace6af471a09df818afbc208931 Subproject commit d7a734f54c8cced94185864f5f04eda603946f6e

@ -57,6 +57,7 @@ common/api/__init__.py
release/* release/*
tools/__init__.py
tools/lib/* tools/lib/*
tools/joystick/* tools/joystick/*
tools/replay/*.cc tools/replay/*.cc
@ -128,7 +129,15 @@ system/clocksd/.gitignore
system/clocksd/SConscript system/clocksd/SConscript
system/clocksd/clocksd.cc system/clocksd/clocksd.cc
selfdrive/debug/*.py selfdrive/debug/can_printer.py
selfdrive/debug/check_freq.py
selfdrive/debug/dump.py
selfdrive/debug/filter_log_message.py
selfdrive/debug/get_fingerprint.py
selfdrive/debug/uiview.py
selfdrive/debug/hyundai_enable_radar_points.py
selfdrive/debug/vw_mqb_config.py
common/SConscript common/SConscript
common/version.h common/version.h
@ -187,6 +196,9 @@ selfdrive/controls/lib/lateral_mpc_lib/*
selfdrive/controls/lib/longitudinal_mpc_lib/* selfdrive/controls/lib/longitudinal_mpc_lib/*
selfdrive/hardware selfdrive/hardware
system/__init__.py
system/hardware/__init__.py system/hardware/__init__.py
system/hardware/base.h system/hardware/base.h
system/hardware/base.py system/hardware/base.py
@ -220,6 +232,7 @@ selfdrive/locationd/laikad_helpers.py
selfdrive/locationd/locationd.h selfdrive/locationd/locationd.h
selfdrive/locationd/locationd.cc selfdrive/locationd/locationd.cc
selfdrive/locationd/paramsd.py selfdrive/locationd/paramsd.py
selfdrive/locationd/models/__init__.py
selfdrive/locationd/models/.gitignore selfdrive/locationd/models/.gitignore
selfdrive/locationd/models/car_kf.py selfdrive/locationd/models/car_kf.py
selfdrive/locationd/models/gnss_kf.py selfdrive/locationd/models/gnss_kf.py
@ -311,11 +324,6 @@ system/camerad/cameras/camera_common.h
system/camerad/cameras/camera_common.cc system/camerad/cameras/camera_common.cc
system/camerad/cameras/sensor2_i2c.h system/camerad/cameras/sensor2_i2c.h
system/camerad/transforms/rgb_to_yuv.cc
system/camerad/transforms/rgb_to_yuv.h
system/camerad/transforms/rgb_to_yuv.cl
system/camerad/transforms/rgb_to_yuv_test.cc
system/camerad/imgproc/conv.cl system/camerad/imgproc/conv.cl
system/camerad/imgproc/pool.cl system/camerad/imgproc/pool.cl
system/camerad/imgproc/utils.cc system/camerad/imgproc/utils.cc
@ -330,6 +338,7 @@ selfdrive/manager/process.py
selfdrive/manager/test/__init__.py selfdrive/manager/test/__init__.py
selfdrive/manager/test/test_manager.py selfdrive/manager/test/test_manager.py
selfdrive/modeld/__init__.py
selfdrive/modeld/SConscript selfdrive/modeld/SConscript
selfdrive/modeld/modeld.cc selfdrive/modeld/modeld.cc
selfdrive/modeld/dmonitoringmodeld.cc selfdrive/modeld/dmonitoringmodeld.cc

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

@ -93,17 +93,17 @@ def fingerprint(logcan, sendcan):
if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN: if cached_params is not None and len(cached_params.carFw) > 0 and cached_params.carVin is not VIN_UNKNOWN:
cloudlog.warning("Using cached CarParams") cloudlog.warning("Using cached CarParams")
vin = cached_params.carVin vin, vin_rx_addr = cached_params.carVin, 0
car_fw = list(cached_params.carFw) car_fw = list(cached_params.carFw)
else: else:
cloudlog.warning("Getting VIN & FW versions") cloudlog.warning("Getting VIN & FW versions")
_, 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)
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_UNKNOWN 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(), []
if len(vin) != 17: if len(vin) != 17:
@ -166,7 +166,7 @@ def fingerprint(logcan, sendcan):
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,
fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), 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

@ -1,7 +1,8 @@
from opendbc.can.packer import CANPacker from opendbc.can.packer import CANPacker
from common.realtime import DT_CTRL
from selfdrive.car import apply_toyota_steer_torque_limits from selfdrive.car import apply_toyota_steer_torque_limits
from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons
from selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams from selfdrive.car.chrysler.values import CAR, RAM_CARS, CarControllerParams
class CarController: class CarController:
@ -13,23 +14,45 @@ class CarController:
self.hud_count = 0 self.hud_count = 0
self.last_lkas_falling_edge = 0 self.last_lkas_falling_edge = 0
self.lkas_active_prev = False self.lkas_control_bit_prev = False
self.last_button_frame = 0
self.packer = CANPacker(dbc_name) self.packer = CANPacker(dbc_name)
self.params = CarControllerParams(CP)
def update(self, CC, CS, low_speed_alert): def update(self, CC, CS):
can_sends = [] can_sends = []
# TODO: can we make this more sane? why is it different for all the cars?
lkas_control_bit = self.lkas_control_bit_prev
if CS.out.vEgo > self.CP.minSteerSpeed:
lkas_control_bit = True
elif self.CP.carFingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019):
if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0):
lkas_control_bit = False
elif self.CP.carFingerprint in RAM_CARS:
if CS.out.vEgo < (self.CP.minSteerSpeed - 0.5):
lkas_control_bit = False
# EPS faults if LKAS re-enables too quickly # EPS faults if LKAS re-enables too quickly
lkas_active = CC.latActive and not low_speed_alert and (self.frame - self.last_lkas_falling_edge > 200) lkas_control_bit = lkas_control_bit and (self.frame - self.last_lkas_falling_edge > 200)
lkas_active = CC.latActive and self.lkas_control_bit_prev
# *** control msgs *** # *** control msgs ***
das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0 # cruise buttons
if CC.cruiseControl.cancel: if (self.frame - self.last_button_frame)*DT_CTRL > 0.05:
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True)) das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0
elif CC.enabled and CS.out.cruiseState.standstill:
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True)) # ACC cancellation
if CC.cruiseControl.cancel:
self.last_button_frame = self.frame
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True))
# ACC resume from standstill
elif CC.cruiseControl.resume:
self.last_button_frame = self.frame
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True))
# HUD alerts # HUD alerts
if self.frame % 25 == 0: if self.frame % 25 == 0:
@ -40,22 +63,22 @@ class CarController:
# steering # steering
if self.frame % 2 == 0: if self.frame % 2 == 0:
# steer torque # steer torque
new_steer = int(round(CC.actuators.steer * CarControllerParams.STEER_MAX)) new_steer = int(round(CC.actuators.steer * self.params.STEER_MAX))
apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, CarControllerParams) apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, self.params)
if not lkas_active: if not lkas_active:
apply_steer = 0 apply_steer = 0
self.steer_rate_limited = new_steer != apply_steer self.steer_rate_limited = new_steer != apply_steer
self.apply_steer_last = apply_steer self.apply_steer_last = apply_steer
idx = self.frame // 2 idx = self.frame // 2
can_sends.append(create_lkas_command(self.packer, self.CP, int(apply_steer), lkas_active, idx)) can_sends.append(create_lkas_command(self.packer, self.CP, int(apply_steer), lkas_control_bit, idx))
self.frame += 1 self.frame += 1
if not lkas_active and self.lkas_active_prev: if not lkas_control_bit and self.lkas_control_bit_prev:
self.last_lkas_falling_edge = self.frame self.last_lkas_falling_edge = self.frame
self.lkas_active_prev = lkas_active self.lkas_control_bit_prev = lkas_control_bit
new_actuators = CC.actuators.copy() new_actuators = CC.actuators.copy()
new_actuators.steer = self.apply_steer_last / CarControllerParams.STEER_MAX new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX
return new_actuators, can_sends return new_actuators, can_sends

@ -52,12 +52,12 @@ def create_lkas_hud(packer, CP, lkas_active, hud_alert, hud_count, car_model, au
return packer.make_can_msg("DAS_6", 0, values) return packer.make_can_msg("DAS_6", 0, values)
def create_lkas_command(packer, CP, apply_steer, lat_active, frame): def create_lkas_command(packer, CP, apply_steer, lkas_control_bit, frame):
# LKAS_COMMAND Lane-keeping signal to turn the wheel # LKAS_COMMAND Lane-keeping signal to turn the wheel
enabled_val = 2 if CP.carFingerprint in RAM_CARS else 1 enabled_val = 2 if CP.carFingerprint in RAM_CARS else 1
values = { values = {
"STEERING_TORQUE": apply_steer, "STEERING_TORQUE": apply_steer,
"LKAS_CONTROL_BIT": enabled_val if lat_active else 0, "LKAS_CONTROL_BIT": enabled_val if lkas_control_bit else 0,
} }
return packer.make_can_msg("LKAS_COMMAND", 0, values, frame % 0x10) return packer.make_can_msg("LKAS_COMMAND", 0, values, frame % 0x10)

@ -46,15 +46,15 @@ class CarInterface(CarInterfaceBase):
# Ram # Ram
elif candidate == CAR.RAM_1500: elif candidate == CAR.RAM_1500:
ret.steerActuatorDelay = 0.2
ret.wheelbase = 3.88 ret.wheelbase = 3.88
ret.steerRatio = 16.3 ret.steerRatio = 16.3
ret.mass = 2493. + STD_CARGO_KG ret.mass = 2493. + STD_CARGO_KG
ret.maxLateralAccel = 2.4 ret.maxLateralAccel = 2.4
ret.minSteerSpeed = 14.5 ret.minSteerSpeed = 14.5
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)
ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.1], [0.02]]
ret.lateralTuning.pid.kf = 0.00003
else: else:
raise ValueError(f"Unsupported car: {candidate}") raise ValueError(f"Unsupported car: {candidate}")
@ -93,4 +93,4 @@ class CarInterface(CarInterfaceBase):
return ret return ret
def apply(self, c): def apply(self, c):
return self.CC.update(c, self.CS, self.low_speed_alert) return self.CC.update(c, self.CS)

@ -26,10 +26,16 @@ class CAR:
class CarControllerParams: class CarControllerParams:
STEER_MAX = 261 # higher than this faults the EPS on Chrysler/Jeep. Ram DT allows more def __init__(self, CP):
STEER_DELTA_UP = 3 self.STEER_MAX = 261 # higher than this faults the EPS on Chrysler/Jeep. Ram DT allows more
STEER_DELTA_DOWN = 3 self.STEER_ERROR_MAX = 80
STEER_ERROR_MAX = 80
if CP.carFingerprint in RAM_CARS:
self.STEER_DELTA_UP = 6
self.STEER_DELTA_DOWN = 6
else:
self.STEER_DELTA_UP = 3
self.STEER_DELTA_DOWN = 3
STEER_THRESHOLD = 120 STEER_THRESHOLD = 120
@ -47,8 +53,8 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"), CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"),
CAR.PACIFICA_2020: ChryslerCarInfo("Chrysler Pacifica 2019-20"), CAR.PACIFICA_2020: ChryslerCarInfo("Chrysler Pacifica 2019-20"),
CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"),
CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-20", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"),
CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-21"), CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-22"),
} }
# Unique CAN messages: # Unique CAN messages:

@ -6,6 +6,7 @@ EXT_DIAG_RESPONSE = b'\x50\x03'
COM_CONT_RESPONSE = b'' COM_CONT_RESPONSE = b''
def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False): def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01', timeout=0.1, retry=10, debug=False):
"""Silence an ECU by disabling sending and receiving messages using UDS 0x28. """Silence an ECU by disabling sending and receiving messages using UDS 0x28.
The ECU will stay silent as long as openpilot keeps sending Tester Present. The ECU will stay silent as long as openpilot keeps sending Tester Present.
@ -26,9 +27,22 @@ def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01'
cloudlog.warning("ecu disabled") cloudlog.warning("ecu disabled")
return True return True
except Exception: except Exception:
cloudlog.exception("ecu disable exception") cloudlog.exception("ecu disable exception")
print(f"ecu disable retry ({i+1}) ...") print(f"ecu disable retry ({i+1}) ...")
cloudlog.warning("ecu disable failed") cloudlog.warning("ecu disable failed")
return False return False
if __name__ == "__main__":
import time
import cereal.messaging as messaging
sendcan = messaging.pub_sock('sendcan')
logcan = messaging.sub_sock('can')
time.sleep(1)
# honda bosch radar disable
disabled = disable_ecu(logcan, sendcan, bus=1, addr=0x18DAB0F1, com_cont_req=b'\x28\x83\x03', timeout=0.5, debug=False)
print(f"disabled: {disabled}")

@ -99,6 +99,11 @@ CHRYSLER_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x
CHRYSLER_RX_OFFSET = -0x280 CHRYSLER_RX_OFFSET = -0x280
FORD_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \
p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
FORD_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \
p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_ECU_SOFTWARE_NUMBER)
@dataclass @dataclass
class Request: class Request:
@ -207,6 +212,13 @@ REQUESTS: List[Request] = [
[CHRYSLER_VERSION_REQUEST], [CHRYSLER_VERSION_REQUEST],
[CHRYSLER_VERSION_RESPONSE], [CHRYSLER_VERSION_RESPONSE],
), ),
# Ford
Request(
"ford",
[TESTER_PRESENT_REQUEST, FORD_VERSION_REQUEST],
[TESTER_PRESENT_RESPONSE, FORD_VERSION_RESPONSE],
bus=0,
),
] ]
@ -394,7 +406,7 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, debug=Fa
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, brand=brand, timeout=timeout, debug=debug, progress=progress) car_fw = get_fw_versions(logcan, sendcan, query_brand=brand, timeout=timeout, debug=debug, progress=progress)
all_car_fw.extend(car_fw) all_car_fw.extend(car_fw)
matches = match_fw_to_car_exact(build_fw_dict(car_fw)) matches = match_fw_to_car_exact(build_fw_dict(car_fw))
if len(matches) == 1: if len(matches) == 1:
@ -403,10 +415,10 @@ 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, 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, debug=False, progress=False):
versions = get_interface_attr('FW_VERSIONS', ignore_none=True) versions = get_interface_attr('FW_VERSIONS', ignore_none=True)
if brand is not None: if query_brand is not None:
versions = {brand: versions[brand]} versions = {query_brand: versions[query_brand]}
if extra is not None: if extra is not None:
versions.update(extra) versions.update(extra)
@ -434,7 +446,7 @@ def get_fw_versions(logcan, sendcan, brand=None, extra=None, timeout=0.1, debug=
addrs.insert(0, parallel_addrs) addrs.insert(0, parallel_addrs)
fw_versions = {} fw_versions = {}
requests = [r for r in REQUESTS if brand is None or r.brand == brand] requests = [r for r in REQUESTS if query_brand is None or r.brand == query_brand]
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 r in requests: for r in requests:
@ -444,7 +456,7 @@ def get_fw_versions(logcan, sendcan, brand=None, extra=None, timeout=0.1, debug=
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)
fw_versions.update({(r.brand, addr): (version, r) for addr, version in query.get_data(timeout).items()}) fw_versions.update({(r.brand, addr): (version, r) for (addr, _), version in query.get_data(timeout).items()})
except Exception: except Exception:
cloudlog.warning(f"FW query exception: {traceback.format_exc()}") cloudlog.warning(f"FW query exception: {traceback.format_exc()}")
@ -497,13 +509,13 @@ if __name__ == "__main__":
t = time.time() t = time.time()
print("Getting vin...") print("Getting vin...")
addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
print(f"VIN: {vin}") print(f'TX: {hex(addr)}, 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, brand=args.brand, extra=extra, debug=args.debug, progress=True) fw_vers = get_fw_versions(logcan, sendcan, query_brand=args.brand, extra=extra, debug=args.debug, progress=True)
_, candidates = match_fw_to_car(fw_vers) _, candidates = match_fw_to_car(fw_vers)
print() print()

@ -109,13 +109,13 @@ class HondaCarInfo(CarInfo):
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-21", "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", video_link="https://www.youtube.com/watch?v=mrUwlj3Mi58", 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, harness=Harness.bosch_a), HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a),
], ],
CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-21", "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, harness=Harness.bosch_a),
CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec), CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", harness=Harness.nidec),
CAR.CIVIC_BOSCH: [ CAR.CIVIC_BOSCH: [
HondaCarInfo("Honda Civic 2019-20", "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", 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 Hatchback 2017-21", harness=Harness.bosch_a), HondaCarInfo("Honda Civic Hatchback 2017-21", harness=Harness.bosch_a),
], ],
CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_BOSCH_DIESEL: None, # same platform
@ -125,20 +125,20 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
], ],
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, harness=Harness.nidec),
CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring", harness=Harness.nidec), CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring", harness=Harness.nidec),
CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-21", harness=Harness.bosch_a), CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", harness=Harness.bosch_a),
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", harness=Harness.bosch_a),
CAR.FIT: HondaCarInfo("Honda Fit 2018-19", harness=Harness.nidec), CAR.FIT: HondaCarInfo("Honda Fit 2018-20", harness=Harness.nidec),
CAR.FREED: HondaCarInfo("Honda Freed 2020", harness=Harness.nidec), CAR.FREED: HondaCarInfo("Honda Freed 2020", harness=Harness.nidec),
CAR.HRV: HondaCarInfo("Honda HR-V 2019-20", harness=Harness.nidec), CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", harness=Harness.nidec),
CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", min_steer_speed=0., harness=Harness.nidec), CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-22", min_steer_speed=0., harness=Harness.nidec),
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", harness=Harness.nidec),
CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-21", "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, harness=Harness.bosch_a),
CAR.PILOT: HondaCarInfo("Honda Pilot 2016-21", harness=Harness.nidec), CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", harness=Harness.nidec),
CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", harness=Harness.nidec), CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", harness=Harness.nidec),
CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", harness=Harness.nidec), CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", harness=Harness.nidec),
CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-21", "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, harness=Harness.bosch_a),
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, harness=Harness.bosch_a),
} }
@ -1406,6 +1406,7 @@ FW_VERSIONS = {
b'38897-T20-A020\x00\x00', b'38897-T20-A020\x00\x00',
b'38897-T20-A510\x00\x00', b'38897-T20-A510\x00\x00',
b'38897-T21-A010\x00\x00', b'38897-T21-A010\x00\x00',
b'38897-T20-A210\x00\x00',
], ],
(Ecu.srs, 0x18DA53F1, None): [ (Ecu.srs, 0x18DA53F1, None): [
b'77959-T20-A970\x00\x00', b'77959-T20-A970\x00\x00',
@ -1415,6 +1416,7 @@ FW_VERSIONS = {
b'78108-T21-A220\x00\x00', b'78108-T21-A220\x00\x00',
b'78108-T21-A620\x00\x00', b'78108-T21-A620\x00\x00',
b'78108-T23-A110\x00\x00', b'78108-T23-A110\x00\x00',
b'78108-T21-A230\x00\x00',
], ],
(Ecu.vsa, 0x18DA28F1, None): [ (Ecu.vsa, 0x18DA28F1, None): [
b'57114-T20-AB40\x00\x00', b'57114-T20-AB40\x00\x00',
@ -1429,6 +1431,7 @@ FW_VERSIONS = {
b'37805-64L-A540\x00\x00', b'37805-64L-A540\x00\x00',
b'37805-64S-A540\x00\x00', b'37805-64S-A540\x00\x00',
b'37805-64S-A720\x00\x00', b'37805-64S-A720\x00\x00',
b'37805-64A-A540\x00\x00',
], ],
}, },
} }

@ -82,12 +82,12 @@ class CarController:
if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: if (self.frame - self.last_button_frame) * DT_CTRL > 0.25:
if CC.cruiseControl.cancel: if CC.cruiseControl.cancel:
for _ in range(20): for _ in range(20):
can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, True, False)) can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL))
self.last_button_frame = self.frame self.last_button_frame = self.frame
# cruise standstill resume # cruise standstill resume
elif CC.cruiseControl.resume: elif CC.cruiseControl.resume:
can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, False, True)) can_sends.append(hda2can.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL))
self.last_button_frame = self.frame self.last_button_frame = self.frame
else: else:

@ -8,7 +8,7 @@ from opendbc.can.can_define import CANDefine
from selfdrive.car.hyundai.values import DBC, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.hyundai.values import DBC, FEATURES, HDA2_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams
from selfdrive.car.interfaces import CarStateBase from selfdrive.car.interfaces import CarStateBase
PREV_BUTTON_SAMPLES = 4 PREV_BUTTON_SAMPLES = 8
class CarState(CarStateBase): class CarState(CarStateBase):
@ -171,7 +171,10 @@ class CarState(CarStateBase):
speed_factor = CV.MPH_TO_MS if cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] == 1 else CV.KPH_TO_MS speed_factor = CV.MPH_TO_MS if cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] == 1 else CV.KPH_TO_MS
ret.cruiseState.speed = cp.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor ret.cruiseState.speed = cp.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor
self.buttons_counter = cp.vl["CRUISE_BUTTONS"]["_COUNTER"] self.cruise_buttons.extend(cp.vl_all["CRUISE_BUTTONS"]["CRUISE_BUTTONS"])
self.main_buttons.extend(cp.vl_all["CRUISE_BUTTONS"]["ADAPTIVE_CRUISE_MAIN_BTN"])
self.buttons_counter = cp.vl["CRUISE_BUTTONS"]["COUNTER"]
self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"])
return ret return ret
@ -362,7 +365,9 @@ class CarState(CarStateBase):
("CRUISE_ACTIVE", "SCC1"), ("CRUISE_ACTIVE", "SCC1"),
("SET_SPEED", "CRUISE_INFO"), ("SET_SPEED", "CRUISE_INFO"),
("CRUISE_STANDSTILL", "CRUISE_INFO"), ("CRUISE_STANDSTILL", "CRUISE_INFO"),
("_COUNTER", "CRUISE_BUTTONS"), ("COUNTER", "CRUISE_BUTTONS"),
("CRUISE_BUTTONS", "CRUISE_BUTTONS"),
("ADAPTIVE_CRUISE_MAIN_BTN", "CRUISE_BUTTONS"),
("DISTANCE_UNIT", "CLUSTER_INFO"), ("DISTANCE_UNIT", "CLUSTER_INFO"),

@ -18,11 +18,9 @@ def create_cam_0x2a4(packer, frame, camera_values):
}) })
return packer.make_can_msg("CAM_0x2a4", 4, camera_values, frame % 255) return packer.make_can_msg("CAM_0x2a4", 4, camera_values, frame % 255)
def create_buttons(packer, cnt, cancel, resume): def create_buttons(packer, cnt, btn):
values = { values = {
"_COUNTER": cnt % 0xf,
"SET_ME_1": 1, "SET_ME_1": 1,
"DISTANCE_BTN": 1 if resume else 0, "CRUISE_BUTTONS": btn,
"PAUSE_RESUME_BTN": 1 if cancel else 0,
} }
return packer.make_can_msg("CRUISE_BUTTONS", 5, values) return packer.make_can_msg("CRUISE_BUTTONS", 5, values, cnt % 0xf)

@ -2,7 +2,7 @@
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 CAR, DBC, HDA2_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams from selfdrive.car.hyundai.values import CAR, DBC, 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_enable_events, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config from selfdrive.car import STD_CARGO_KG, create_button_enable_events, 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
@ -321,8 +321,7 @@ class CarInterface(CarInterfaceBase):
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons # To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
# Main button also can trigger an engagement on these cars # Main button also can trigger an engagement on these cars
allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons) allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons)
allow_enable = allow_enable or self.CP.carFingerprint in HDA2_CAR events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable)
events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable or True)
if self.CS.brake_error: if self.CS.brake_error:
events.add(EventName.brakeUnavailable) events.add(EventName.brakeUnavailable)

@ -151,15 +151,15 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
HyundaiCarInfo("Kia Sorento 2018", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2018", video_link="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_STINGER: HyundaiCarInfo("Kia Stinger 2018", 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_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", "All", harness=Harness.hyundai_p), CAR.KIA_EV6: HyundaiCarInfo("Kia EV6 2022", "All", harness=Harness.hyundai_p),
# Genesis # Genesis
CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018", "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_G80: HyundaiCarInfo("Genesis G80 2018", "All", harness=Harness.hyundai_h), CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2018", "All", harness=Harness.hyundai_c), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
} }
class Buttons: class Buttons:
@ -167,7 +167,7 @@ class Buttons:
RES_ACCEL = 1 RES_ACCEL = 1
SET_DECEL = 2 SET_DECEL = 2
GAP_DIST = 3 GAP_DIST = 3
CANCEL = 4 CANCEL = 4 # on newer models, this is a pause/resume button
FINGERPRINTS = { FINGERPRINTS = {
CAR.ELANTRA: [{ CAR.ELANTRA: [{

@ -135,11 +135,11 @@ class CarInterfaceBase(ABC):
return ret return ret
@staticmethod @staticmethod
def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0): def configure_torque_tune(candidate, tune, steering_angle_deadzone_deg=0.0, use_steering_angle=True):
params = get_torque_params(candidate) params = get_torque_params(candidate)
tune.init('torque') tune.init('torque')
tune.torque.useSteeringAngle = True tune.torque.useSteeringAngle = use_steering_angle
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR'] tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR']
tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR'] tune.torque.kf = 1.0 / params['LAT_ACCEL_FACTOR']
tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR'] tune.torque.ki = 0.1 / params['LAT_ACCEL_FACTOR']

@ -126,7 +126,7 @@ class IsoTpParallelQuery:
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] = dat[len(expected_response):] results[(tx_addr, msg._can_client.rx_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

@ -40,7 +40,7 @@ CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = {
CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-17"), CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-17"),
CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017"), CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017"),
CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017"), CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017"),
CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021"), CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22"),
CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022"), CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022"),
} }
@ -65,6 +65,7 @@ FW_VERSIONS = {
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2H-188K2-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SH54-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
@ -266,6 +267,7 @@ FW_VERSIONS = {
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -278,9 +280,11 @@ FW_VERSIONS = {
b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-P\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',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PXM6-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
} }
} }

@ -41,18 +41,18 @@ class SubaruCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = {
CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-20", "All"), CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"),
CAR.IMPREZA: [ CAR.IMPREZA: [
SubaruCarInfo("Subaru Impreza 2017-19"), SubaruCarInfo("Subaru Impreza 2017-19"),
SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"),
], ],
CAR.IMPREZA_2020: [ CAR.IMPREZA_2020: [
SubaruCarInfo("Subaru Impreza 2020-21"), SubaruCarInfo("Subaru Impreza 2020-22"),
SubaruCarInfo("Subaru Crosstrek 2020-21"), SubaruCarInfo("Subaru Crosstrek 2020-21"),
SubaruCarInfo("Subaru XV 2020-21"), SubaruCarInfo("Subaru XV 2020-21"),
], ],
CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-22", "All"),
CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"), CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"),
CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"), CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),

@ -21,7 +21,7 @@ COMMA BODY: [.nan, 1000, .nan]
# Totally new cars # Totally new cars
KIA EV6 2022: [3.5, 2.5, 0.0] KIA EV6 2022: [3.5, 2.5, 0.0]
RAM 1500 5TH GEN: [2.0, 2.0, 0.05] RAM 1500 5TH GEN: [2.0, 2.0, 0.0]
# Dashcam or fallback configured as ideal car # Dashcam or fallback configured as ideal car
mock: [10.0, 10, 0.0] mock: [10.0, 10, 0.0]

@ -126,7 +126,7 @@ class CarController:
# forcing the pcm to disengage causes a bad fault sound so play a good sound instead # forcing the pcm to disengage causes a bad fault sound so play a good sound instead
send_ui = True send_ui = True
if self.frame % 100 == 0 or send_ui: if (self.frame % 100 == 0 or send_ui) and (self.CP.carFingerprint != CAR.PRIUS_V):
can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible,
hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneVisible, hud_control.leftLaneDepart,
hud_control.rightLaneDepart, CC.enabled)) hud_control.rightLaneDepart, CC.enabled))

@ -119,12 +119,13 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19", footnotes=[Footnote.DSU]), CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19", footnotes=[Footnote.DSU]),
CAR.COROLLA_TSS2: [ CAR.COROLLA_TSS2: [
ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
ToyotaCarInfo("Toyota Corolla Cross 2020-21 (Non-US only)"), ToyotaCarInfo("Toyota Corolla Cross 2020-21 (Non-US only)", min_enable_speed=7.5),
ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"),
], ],
CAR.COROLLAH_TSS2: [ CAR.COROLLAH_TSS2: [
ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"),
ToyotaCarInfo("Lexus UX Hybrid 2019-21"), ToyotaCarInfo("Toyota Corolla Cross Hybrid 2020-22 (Non-US only)", min_enable_speed=7.5),
ToyotaCarInfo("Lexus UX Hybrid 2019-22"),
], ],
CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo", footnotes=[Footnote.DSU]), CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo", footnotes=[Footnote.DSU]),
CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"),
@ -151,14 +152,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
# Lexus # Lexus
CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]), CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]),
CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]), CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "LSS", footnotes=[Footnote.DSU]),
CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-21"), CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"),
CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"),
CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"),
CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19", footnotes=[Footnote.DSU]), CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19", footnotes=[Footnote.DSU]),
CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19", footnotes=[Footnote.DSU]), CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19", footnotes=[Footnote.DSU]),
CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020"), CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"),
CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020"), CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"),
CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2020"), CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-2020"),
CAR.LEXUS_RX: ToyotaCarInfo("Lexus RX 2016-18", footnotes=[Footnote.DSU]), CAR.LEXUS_RX: ToyotaCarInfo("Lexus RX 2016-18", footnotes=[Footnote.DSU]),
CAR.LEXUS_RXH: ToyotaCarInfo("Lexus RX Hybrid 2016-19", footnotes=[Footnote.DSU]), CAR.LEXUS_RXH: ToyotaCarInfo("Lexus RX Hybrid 2016-19", footnotes=[Footnote.DSU]),
CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"),
@ -788,6 +789,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [ (Ecu.eps, 0x7a1, None): [
b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B12361\x00\x00\x00\x00\x00\x00',
b'8965B12451\x00\x00\x00\x00\x00\x00', b'8965B12451\x00\x00\x00\x00\x00\x00',
b'8965B16011\x00\x00\x00\x00\x00\x00',
b'8965B76012\x00\x00\x00\x00\x00\x00', b'8965B76012\x00\x00\x00\x00\x00\x00',
b'8965B76050\x00\x00\x00\x00\x00\x00', b'8965B76050\x00\x00\x00\x00\x00\x00',
b'\x018965B12350\x00\x00\x00\x00\x00\x00', b'\x018965B12350\x00\x00\x00\x00\x00\x00',
@ -808,15 +810,16 @@ FW_VERSIONS = {
b'F152612800\x00\x00\x00\x00\x00\x00', b'F152612800\x00\x00\x00\x00\x00\x00',
b'F152612820\x00\x00\x00\x00\x00\x00', b'F152612820\x00\x00\x00\x00\x00\x00',
b'F152612840\x00\x00\x00\x00\x00\x00', b'F152612840\x00\x00\x00\x00\x00\x00',
b'F152612842\x00\x00\x00\x00\x00\x00',
b'F152612890\x00\x00\x00\x00\x00\x00', b'F152612890\x00\x00\x00\x00\x00\x00',
b'F152612A00\x00\x00\x00\x00\x00\x00', b'F152612A00\x00\x00\x00\x00\x00\x00',
b'F152612A10\x00\x00\x00\x00\x00\x00', b'F152612A10\x00\x00\x00\x00\x00\x00',
b'F152612D00\x00\x00\x00\x00\x00\x00',
b'F152616011\x00\x00\x00\x00\x00\x00',
b'F152642540\x00\x00\x00\x00\x00\x00', b'F152642540\x00\x00\x00\x00\x00\x00',
b'F152676293\x00\x00\x00\x00\x00\x00', b'F152676293\x00\x00\x00\x00\x00\x00',
b'F152676303\x00\x00\x00\x00\x00\x00', b'F152676303\x00\x00\x00\x00\x00\x00',
b'F152676304\x00\x00\x00\x00\x00\x00', b'F152676304\x00\x00\x00\x00\x00\x00',
b'F152612D00\x00\x00\x00\x00\x00\x00',
b'F152612842\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F3301100\x00\x00\x00\x00', b'\x018821F3301100\x00\x00\x00\x00',
@ -832,6 +835,7 @@ FW_VERSIONS = {
b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F1202000\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00',
b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00',
b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00", b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00",
b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00',
b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
@ -1302,6 +1306,7 @@ FW_VERSIONS = {
b'\x01896634AA0000\x00\x00\x00\x00', b'\x01896634AA0000\x00\x00\x00\x00',
b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634AA1000\x00\x00\x00\x00',
b'\x01896634A88000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00',
b'\x01896634A89000\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R01100\x00\x00\x00\x00', b'\x018821F0R01100\x00\x00\x00\x00',
@ -1381,11 +1386,12 @@ FW_VERSIONS = {
b'8965B42172\x00\x00\x00\x00\x00\x00', b'8965B42172\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.engine, 0x700, None): [ (Ecu.engine, 0x700, None): [
b'\x01896634A62000\x00\x00\x00\x00',
b'\x01896634A08000\x00\x00\x00\x00',
b'\x01896634A61000\x00\x00\x00\x00',
b'\x01896634A02001\x00\x00\x00\x00', b'\x01896634A02001\x00\x00\x00\x00',
b'\x01896634A03000\x00\x00\x00\x00', b'\x01896634A03000\x00\x00\x00\x00',
b'\x01896634A08000\x00\x00\x00\x00',
b'\x01896634A61000\x00\x00\x00\x00',
b'\x01896634A62000\x00\x00\x00\x00',
b'\x01896634A63000\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x750, 0xf): [ (Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R01100\x00\x00\x00\x00', b'\x018821F0R01100\x00\x00\x00\x00',

@ -22,18 +22,18 @@ def get_vin(logcan, sendcan, bus, timeout=0.1, retry=5, debug=False):
for request, response in ((UDS_VIN_REQUEST, UDS_VIN_RESPONSE), (OBD_VIN_REQUEST, OBD_VIN_RESPONSE)): for request, response in ((UDS_VIN_REQUEST, UDS_VIN_RESPONSE), (OBD_VIN_REQUEST, OBD_VIN_RESPONSE)):
try: try:
query = IsoTpParallelQuery(sendcan, logcan, bus, FUNCTIONAL_ADDRS, [request, ], [response, ], functional_addr=True, debug=debug) query = IsoTpParallelQuery(sendcan, logcan, bus, FUNCTIONAL_ADDRS, [request, ], [response, ], functional_addr=True, debug=debug)
for addr, vin in query.get_data(timeout).items(): for (addr, rx_addr), vin in query.get_data(timeout).items():
# Honda Bosch response starts with a length, trim to correct length # Honda Bosch response starts with a length, trim to correct length
if vin.startswith(b'\x11'): if vin.startswith(b'\x11'):
vin = vin[1:18] vin = vin[1:18]
return addr[0], vin.decode() return addr[0], rx_addr, vin.decode()
print(f"vin query retry ({i+1}) ...") print(f"vin query retry ({i+1}) ...")
except Exception: except Exception:
cloudlog.warning(f"VIN query exception: {traceback.format_exc()}") cloudlog.warning(f"VIN query exception: {traceback.format_exc()}")
return 0, VIN_UNKNOWN return 0, 0, VIN_UNKNOWN
if __name__ == "__main__": if __name__ == "__main__":
@ -41,5 +41,5 @@ if __name__ == "__main__":
sendcan = messaging.pub_sock('sendcan') sendcan = messaging.pub_sock('sendcan')
logcan = messaging.sub_sock('can') logcan = messaging.sub_sock('can')
time.sleep(1) time.sleep(1)
addr, vin = get_vin(logcan, sendcan, 1, debug=False) addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, debug=False)
print(hex(addr), vin) print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}')

@ -37,6 +37,7 @@ class CANBUS:
pt = 0 pt = 0
cam = 2 cam = 2
class DBC_FILES: class DBC_FILES:
mqb = "vw_mqb_2010" # Used for all cars with MQB-style CAN messaging mqb = "vw_mqb_2010" # Used for all cars with MQB-style CAN messaging
pq = "vw_golf_mk4" # Used for all cars with PQ-style (legacy) CAN messaging pq = "vw_golf_mk4" # Used for all cars with PQ-style (legacy) CAN messaging
@ -124,6 +125,9 @@ class Footnote(Enum):
"(older design) or light brown (newer design). For the newer design, in the interim, choose \"VW J533 Development\" " + "(older design) or light brown (newer design). For the newer design, in the interim, choose \"VW J533 Development\" " +
"from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.", "from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.",
Column.MODEL) Column.MODEL)
VW_VARIANT = CarFootnote(
"Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)",
Column.MODEL)
@dataclass @dataclass
@ -133,25 +137,43 @@ class VWCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.ARTEON_MK1: VWCarInfo("Volkswagen Arteon 2018, 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.ARTEON_MK1: [
CAR.ATLAS_MK1: VWCarInfo("Volkswagen Atlas 2018-19, 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
],
CAR.ATLAS_MK1: [
VWCarInfo("Volkswagen Atlas 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
],
CAR.GOLF_MK7: [ CAR.GOLF_MK7: [
VWCarInfo("Volkswagen e-Golf 2014, 2018-20"), VWCarInfo("Volkswagen e-Golf 2014-20"),
VWCarInfo("Volkswagen Golf 2015-20"), VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf Alltrack 2017-18"), VWCarInfo("Volkswagen Golf Alltrack 2015-19"),
VWCarInfo("Volkswagen Golf GTE 2016"), VWCarInfo("Volkswagen Golf GTD 2015-20"),
VWCarInfo("Volkswagen Golf GTI 2018-21"), VWCarInfo("Volkswagen Golf GTE 2015-20"),
VWCarInfo("Volkswagen Golf R 2016-19"), VWCarInfo("Volkswagen Golf GTI 2015-21"),
VWCarInfo("Volkswagen Golf SportsVan 2016"), VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf SportWagen 2015"), VWCarInfo("Volkswagen Golf SportsVan 2015-20"),
], ],
CAR.JETTA_MK7: [ CAR.JETTA_MK7: [
VWCarInfo("Volkswagen Jetta 2018-21"), VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Jetta GLI 2021"), VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
],
CAR.PASSAT_MK8: [
VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.PASSAT, Footnote.VW_VARIANT], harness=Harness.j533),
VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533),
], ],
CAR.PASSAT_MK8: VWCarInfo("Volkswagen Passat 2015-19", footnotes=[Footnote.PASSAT]),
CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017, 2021"), CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017, 2021"),
CAR.POLO_MK6: VWCarInfo("Volkswagen Polo 2020"), CAR.POLO_MK6: [
VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
],
CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),

@ -96,7 +96,11 @@ class Controls:
self.sm = sm self.sm = sm
if self.sm is None: if self.sm is None:
ignore = ['driverCameraState', 'managerState'] if SIMULATION else None ignore = []
if SIMULATION:
ignore += ['driverCameraState', 'managerState']
if params.get_bool('WideCameraOnly'):
ignore += ['roadCameraState']
self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration',
'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState'] + self.camera_packets + joystick_packet, 'managerState', 'liveParameters', 'radarState'] + self.camera_packets + joystick_packet,
@ -224,12 +228,8 @@ class Controls:
if not self.CP.notCar: if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events) self.events.add_from_msg(self.sm['driverMonitoringState'].events)
# Handle car events. Ignore when CAN is invalid # Add car events, ignore if CAN isn't valid
if CS.canTimeout: if CS.canValid:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
else:
self.events.add_from_msg(CS.events) self.events.add_from_msg(CS.events)
# Create events for temperature, disk space, and memory # Create events for temperature, disk space, and memory
@ -309,14 +309,19 @@ class Controls:
self.events.add(EventName.cameraFrameRate) self.events.add(EventName.cameraFrameRate)
if self.rk.lagging: if self.rk.lagging:
self.events.add(EventName.controlsdLagging) self.events.add(EventName.controlsdLagging)
if len(self.sm['radarState'].radarErrors): if len(self.sm['radarState'].radarErrors) or not self.sm.all_checks(['radarState']):
self.events.add(EventName.radarFault) self.events.add(EventName.radarFault)
if not self.sm.valid['pandaStates']: if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError) self.events.add(EventName.usbError)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead # generic catch-all. ideally, a more specific event should be added above instead
no_system_errors = len(self.events) != num_events has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE))
if (not self.sm.all_checks() or self.can_rcv_error) and no_system_errors and CS.canValid and not CS.canTimeout: no_system_errors = (not has_disable_events) or (len(self.events) == num_events)
if (not self.sm.all_checks() or self.can_rcv_error) and no_system_errors:
if not self.sm.all_alive(): if not self.sm.all_alive():
self.events.add(EventName.commIssue) self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok(): elif not self.sm.all_freq_ok():
@ -406,7 +411,8 @@ class Controls:
if not self.initialized: if not self.initialized:
all_valid = CS.canValid and self.sm.all_checks() all_valid = CS.canValid and self.sm.all_checks()
if all_valid or self.sm.frame * DT_CTRL > 3.5 or SIMULATION: timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5)
if all_valid or timed_out or SIMULATION:
if not self.read_only: if not self.read_only:
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
self.initialized = True self.initialized = True

@ -1,40 +0,0 @@
#!/usr/bin/env python3
import traceback
import cereal.messaging as messaging
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
from system.swaglog import cloudlog
EXT_DIAG_REQUEST = b'\x10\x03'
EXT_DIAG_RESPONSE = b'\x50\x03'
COM_CONT_REQUEST = b'\x28\x83\x03'
COM_CONT_RESPONSE = b''
def disable_ecu(ecu_addr, logcan, sendcan, bus, timeout=0.5, retry=5, debug=False):
print(f"ecu disable {hex(ecu_addr)} ...")
for i in range(retry):
try:
# enter extended diagnostic session
query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [EXT_DIAG_REQUEST], [EXT_DIAG_RESPONSE], debug=debug)
for addr, dat in query.get_data(timeout).items(): # pylint: disable=unused-variable
print("ecu communication control disable tx/rx ...")
# communication control disable tx and rx
query = IsoTpParallelQuery(sendcan, logcan, bus, [ecu_addr], [COM_CONT_REQUEST], [COM_CONT_RESPONSE], debug=debug)
query.get_data(0)
return True
print(f"ecu disable retry ({i+1}) ...")
except Exception:
cloudlog.warning(f"ecu disable exception: {traceback.format_exc()}")
return False
if __name__ == "__main__":
import time
sendcan = messaging.pub_sock('sendcan')
logcan = messaging.sub_sock('can')
time.sleep(1)
# honda bosch radar disable
disabled = disable_ecu(0x18DAB0F1, logcan, sendcan, 1, debug=False)
print(f"disabled: {disabled}")

@ -0,0 +1,18 @@
#!/usr/bin/env python3
import argparse
import pickle
from selfdrive.car.docs import get_all_car_info
def dump_car_info(path):
with open(path, 'wb') as f:
pickle.dump(get_all_car_info(), f)
print(f'Dumping car info to {path}')
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True)
args = parser.parse_args()
dump_car_info(args.path)

@ -0,0 +1,90 @@
#!/usr/bin/env python3
import argparse
import pickle
from selfdrive.car.docs import get_all_car_info
from selfdrive.car.docs_definitions import Column
STAR_ICON = '<a href="##"><img valign="top" src="https://raw.githubusercontent.com/commaai/openpilot/master/docs/assets/icon-star-{}.svg" width="22" /></a>'
COLUMNS = "|" + "|".join([column.value for column in Column]) + "|"
COLUMN_HEADER = "|---|---|---|:---:|:---:|:---:|:---:|:---:|"
ARROW_SYMBOL = ""
def load_base_car_info(path):
with open(path, "rb") as f:
return pickle.load(f)
def get_star_diff(base_car, new_car):
return [column for column, value in base_car.row.items() if value != new_car.row[column]]
def format_row(builder):
return "|" + "|".join(builder) + "|"
def print_car_info_diff(path):
base_car_info = {f"{i.make} {i.model}": i for i in load_base_car_info(path)}
new_car_info = {f"{i.make} {i.model}": i for i in get_all_car_info()}
tier_changes = []
star_changes = []
removals = []
additions = []
# Changes (tier + stars)
for base_car_model, base_car in base_car_info.items():
if base_car_model not in new_car_info:
continue
new_car = new_car_info[base_car_model]
# Tier changes
if base_car.tier != new_car.tier:
tier_changes.append(f"- Tier for {base_car.make} {base_car.model} changed! ({base_car.tier.name.title()} {ARROW_SYMBOL} {new_car.tier.name.title()})")
# Star changes
diff = get_star_diff(base_car, new_car)
if not len(diff):
continue
row_builder = []
for column in list(Column):
if column not in diff:
row_builder.append(new_car.get_column(column, STAR_ICON, "{}"))
else:
row_builder.append(base_car.get_column(column, STAR_ICON, "{}") + ARROW_SYMBOL + new_car.get_column(column, STAR_ICON, "{}"))
star_changes.append(format_row(row_builder))
# Removals
for model in set(base_car_info) - set(new_car_info):
car_info = base_car_info[model]
removals.append(format_row([car_info.get_column(column, STAR_ICON, "{}") for column in Column]))
# Additions
for model in set(new_car_info) - set(base_car_info):
car_info = new_car_info[model]
additions.append(format_row([car_info.get_column(column, STAR_ICON, "{}") for column in Column]))
# Print diff
if len(star_changes) or len(tier_changes) or len(removals) or len(additions):
markdown_builder = ["### ⚠ This PR makes changes to [CARS.md](../blob/master/docs/CARS.md) ⚠"]
for title, category in (("## 🏅 Tier Changes", tier_changes), ("## 🔀 Star Changes", star_changes), ("## ❌ Removed", removals), ("## ➕ Added", additions)):
if len(category):
markdown_builder.append(title)
if "Tier" not in title:
markdown_builder.append(COLUMNS)
markdown_builder.append(COLUMN_HEADER)
markdown_builder.extend(category)
print("\n".join(markdown_builder))
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--path", required=True)
args = parser.parse_args()
print_car_info_diff(args.path)

@ -15,6 +15,7 @@ from cereal import log, messaging
from common.params import Params, put_nonblocking from common.params import Params, put_nonblocking
from laika import AstroDog from laika import AstroDog
from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.constants import SECS_IN_HR, SECS_IN_MIN
from laika.downloader import DownloadFailed
from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem
from laika.gps_time import GPSTime from laika.gps_time import GPSTime
from laika.helpers import ConstellationId from laika.helpers import ConstellationId
@ -27,6 +28,7 @@ from system.swaglog import cloudlog
MAX_TIME_GAP = 10 MAX_TIME_GAP = 10
EPHEMERIS_CACHE = 'LaikadEphemeris' EPHEMERIS_CACHE = 'LaikadEphemeris'
DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache"
CACHE_VERSION = 0.1 CACHE_VERSION = 0.1
POS_FIX_RESIDUAL_THRESHOLD = 100.0 POS_FIX_RESIDUAL_THRESHOLD = 100.0
@ -42,7 +44,7 @@ class Laikad:
valid_ephem_types: Valid ephemeris types to be used by AstroDog valid_ephem_types: Valid ephemeris types to be used by AstroDog
save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. save_ephemeris: If true saves and loads nav and orbit ephemeris to cache.
""" """
self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True) self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER)
self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True) self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True)
self.auto_fetch_orbits = auto_fetch_orbits self.auto_fetch_orbits = auto_fetch_orbits
@ -77,8 +79,8 @@ class Laikad:
cloudlog.exception("Error parsing cache") cloudlog.exception("Error parsing cache")
timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan' timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan'
cloudlog.debug( cloudlog.debug(
f"Loaded nav and orbits cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " + f"Loaded nav ({sum([len(v) for v in cache['nav']])}) and orbits ({sum([len(v) for v in cache['orbits']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " +
f"Total: {sum([len(v) for v in cache['orbits']])} and {sum([len(v) for v in cache['nav']])}") f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.orbit_fetched_times._ranges]}")
def cache_ephemeris(self, t: GPSTime): def cache_ephemeris(self, t: GPSTime):
if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN): if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN):
@ -92,10 +94,15 @@ class Laikad:
if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2: if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2:
min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5 min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5
pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements) pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements)
if len(pos_fix) > 0 and np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD: if len(pos_fix) > 0:
self.last_pos_fix = pos_fix[:3]
self.last_pos_residual = pos_fix_residual
self.last_pos_fix_t = t self.last_pos_fix_t = t
residual_median = np.median(np.abs(pos_fix_residual))
if np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD:
cloudlog.debug(f"Pos fix is within threshold with median: {residual_median.round()}")
self.last_pos_fix = pos_fix[:3]
self.last_pos_residual = pos_fix_residual
else:
cloudlog.debug(f"Pos fix failed with median: {residual_median.round()}. All residuals: {np.round(pos_fix_residual)}")
return self.last_pos_fix return self.last_pos_fix
def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False): def process_ublox_msg(self, ublox_msg, ublox_mono_time: int, block=False):
@ -113,10 +120,11 @@ class Laikad:
new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7]
processed_measurements = process_measurements(new_meas, self.astro_dog) processed_measurements = process_measurements(new_meas, self.astro_dog)
est_pos = self.get_est_pos(t, processed_measurements) est_pos = self.get_est_pos(t, processed_measurements)
corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else [] corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else []
if ublox_mono_time % 10 == 0:
cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}")
self.update_localizer(est_pos, t, corrected_measurements) self.update_localizer(est_pos, t, corrected_measurements)
kf_valid = all(self.kf_valid(t)) kf_valid = all(self.kf_valid(t))
@ -183,7 +191,7 @@ class Laikad:
def fetch_orbits(self, t: GPSTime, block): def fetch_orbits(self, t: GPSTime, block):
# Download new orbits if 1 hour of orbits data left # Download new orbits if 1 hour of orbits data left
if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN): if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN):
astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir
ret = None ret = None
if block: # Used for testing purposes if block: # Used for testing purposes
@ -203,15 +211,17 @@ class Laikad:
self.cache_ephemeris(t=t) self.cache_ephemeris(t=t)
def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types): def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir):
astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types) astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir)
cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}") cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}")
start_time = time.monotonic() start_time = time.monotonic()
try: try:
astro_dog.get_orbit_data(t, only_predictions=True) astro_dog.get_orbit_data(t, only_predictions=True)
cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s")
cloudlog.debug(f"Downloaded orbits ({sum([len(v) for v in astro_dog.orbits])}): {list(astro_dog.orbits.keys())}" +
f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}")
return astro_dog.orbits, astro_dog.orbit_fetched_times, t return astro_dog.orbits, astro_dog.orbit_fetched_times, t
except (RuntimeError, ValueError, IOError) as e: except (DownloadFailed, RuntimeError, ValueError, IOError) as e:
cloudlog.warning(f"No orbit data found or parsing failure: {e}") cloudlog.warning(f"No orbit data found or parsing failure: {e}")
return None, None, t return None, None, t
@ -301,6 +311,7 @@ def main(sm=None, pm=None):
replay = "REPLAY" in os.environ replay = "REPLAY" in os.environ
use_internet = "LAIKAD_NO_INTERNET" not in os.environ use_internet = "LAIKAD_NO_INTERNET" not in os.environ
laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet) laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet)
while True: while True:
sm.update() sm.update()

@ -8,6 +8,7 @@ from unittest.mock import Mock, patch
from common.params import Params from common.params import Params
from laika.constants import SECS_IN_DAY from laika.constants import SECS_IN_DAY
from laika.downloader import DownloadFailed
from laika.ephemeris import EphemerisType, GPSEphemeris from laika.ephemeris import EphemerisType, GPSEphemeris
from laika.gps_time import GPSTime from laika.gps_time import GPSTime
from laika.helpers import ConstellationId, TimeRangeHolder from laika.helpers import ConstellationId, TimeRangeHolder
@ -51,6 +52,9 @@ def get_measurement_mock(gpstime, sat_ephemeris):
return meas return meas
GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12))
class TestLaikad(unittest.TestCase): class TestLaikad(unittest.TestCase):
@classmethod @classmethod
@ -109,7 +113,7 @@ class TestLaikad(unittest.TestCase):
data_mock = defaultdict(str) data_mock = defaultdict(str)
data_mock['sv_id'] = 1 data_mock['sv_id'] = 1
gpstime = GPSTime.from_datetime(datetime(2022, month=3, day=1)) gpstime = GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC
laikad = Laikad() laikad = Laikad()
laikad.fetch_orbits(gpstime, block=True) laikad.fetch_orbits(gpstime, block=True)
meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['R01'][0]) meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['R01'][0])
@ -165,7 +169,13 @@ class TestLaikad(unittest.TestCase):
@mock.patch('laika.downloader.download_and_cache_file') @mock.patch('laika.downloader.download_and_cache_file')
def test_laika_offline(self, downloader_mock): def test_laika_offline(self, downloader_mock):
downloader_mock.side_effect = IOError downloader_mock.side_effect = DownloadFailed("Mock download failed")
laikad = Laikad(auto_update=False)
laikad.fetch_orbits(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True)
@mock.patch('laika.downloader.download_and_cache_file')
def test_download_failed_russian_source(self, downloader_mock):
downloader_mock.side_effect = DownloadFailed
laikad = Laikad(auto_update=False) laikad = Laikad(auto_update=False)
correct_msgs = verify_messages(self.logs, laikad) correct_msgs = verify_messages(self.logs, laikad)
self.assertEqual(16, len(correct_msgs)) self.assertEqual(16, len(correct_msgs))

@ -46,11 +46,25 @@ def remove_ignored_fields(msg, ignore):
return msg.as_reader() return msg.as_reader()
def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None): def get_field_tolerance(diff_field, field_tolerances):
diff_field_str = diff_field[0]
for s in diff_field[1:]:
# loop until number in field
if not isinstance(s, str):
break
diff_field_str += '.'+s
if diff_field_str in field_tolerances:
return field_tolerances[diff_field_str]
def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=None, field_tolerances=None):
if ignore_fields is None: if ignore_fields is None:
ignore_fields = [] ignore_fields = []
if ignore_msgs is None: if ignore_msgs is None:
ignore_msgs = [] ignore_msgs = []
if field_tolerances is None:
field_tolerances = {}
default_tolerance = EPSILON if tolerance is None else tolerance
log1, log2 = (list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)) log1, log2 = (list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2))
@ -72,7 +86,6 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
msg1_dict = msg1.to_dict(verbose=True) msg1_dict = msg1.to_dict(verbose=True)
msg2_dict = msg2.to_dict(verbose=True) msg2_dict = msg2.to_dict(verbose=True)
tolerance = EPSILON if tolerance is None else tolerance
dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields) dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields)
# Dictdiffer only supports relative tolerance, we also want to check for absolute # Dictdiffer only supports relative tolerance, we also want to check for absolute
@ -80,10 +93,13 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
def outside_tolerance(diff): def outside_tolerance(diff):
try: try:
if diff[0] == "change": if diff[0] == "change":
field_tolerance = default_tolerance
if (tol := get_field_tolerance(diff[1], field_tolerances)) is not None:
field_tolerance = tol
a, b = diff[2] a, b = diff[2]
finite = math.isfinite(a) and math.isfinite(b) finite = math.isfinite(a) and math.isfinite(b)
if finite and isinstance(a, numbers.Number) and isinstance(b, numbers.Number): if finite and isinstance(a, numbers.Number) and isinstance(b, numbers.Number):
return abs(a - b) > max(tolerance, tolerance * max(abs(a), abs(b))) return abs(a - b) > max(field_tolerance, field_tolerance * max(abs(a), abs(b)))
except TypeError: except TypeError:
pass pass
return True return True

@ -14,6 +14,7 @@ from cereal import car, log
from cereal.services import service_list from cereal.services import service_list
from common.params import Params from common.params import Params
from common.timeout import Timeout from common.timeout import Timeout
from common.realtime import DT_CTRL
from panda.python import ALTERNATIVE_EXPERIENCE from panda.python import ALTERNATIVE_EXPERIENCE
from selfdrive.car.car_helpers import get_car, interfaces from selfdrive.car.car_helpers import get_car, interfaces
from selfdrive.test.process_replay.helpers import OpenpilotPrefix from selfdrive.test.process_replay.helpers import OpenpilotPrefix
@ -27,7 +28,7 @@ TIMEOUT = 15
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__)) PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/")
ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'init_callback', 'should_recv_callback', 'tolerance', 'fake_pubsubmaster', 'submaster_config', 'environ', 'subtest_name'], defaults=({}, {}, "")) ProcessConfig = namedtuple('ProcessConfig', ['proc_name', 'pub_sub', 'ignore', 'init_callback', 'should_recv_callback', 'tolerance', 'fake_pubsubmaster', 'submaster_config', 'environ', 'subtest_name', "field_tolerances"], defaults=({}, {}, "", {}))
def wait_for_event(evt): def wait_for_event(evt):
@ -548,11 +549,17 @@ def cpp_replay_process(cfg, lr, fingerprint=None):
def check_enabled(msgs): def check_enabled(msgs):
cur_enabled_count = 0
max_enabled_count = 0
for msg in msgs: for msg in msgs:
if msg.which() == "carParams": if msg.which() == "carParams":
if msg.carParams.notCar: if msg.carParams.notCar:
return True return True
elif msg.which() == "controlsState": elif msg.which() == "controlsState":
if msg.controlsState.active: if msg.controlsState.active:
return True cur_enabled_count += 1
return False else:
cur_enabled_count = 0
max_enabled_count = max(max_enabled_count, cur_enabled_count)
return max_enabled_count > int(10. / DT_CTRL)

@ -1 +1 @@
825acfae98543c915c18d3b19a9c5d2503e431a6 7c1168af0311d2fef67b82812cd863a0e97c030e

@ -263,7 +263,7 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False):
seg_path = os.path.join(outdir, segment) seg_path = os.path.join(outdir, segment)
# check to make sure openpilot is engaged in the route # check to make sure openpilot is engaged in the route
if not check_enabled(LogReader(os.path.join(seg_path, "rlog"))): if not check_enabled(LogReader(os.path.join(seg_path, "rlog"))):
raise Exception(f"Route never enabled: {segment}") raise Exception(f"Route did not engage for long enough: {segment}")
return seg_path return seg_path

@ -18,14 +18,14 @@ from tools.lib.logreader import LogReader
original_segments = [ original_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY
("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA
("HYUNDAI", "d824e27e8c60172c|2022-07-08--21-21-15--1"), # HYUNDAI.KIA_EV6 ("HYUNDAI", "d824e27e8c60172c|2022-07-08--21-21-15--0"), # HYUNDAI.KIA_EV6
("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI)
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR)
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC)
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH)
("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA
("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--2"), # CHRYSLER.RAM_1500 ("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--3"), # CHRYSLER.RAM_1500
("SUBARU", "4d70bc5e608678be|2021-01-15--17-02-04--5"), # SUBARU.IMPREZA ("SUBARU", "4d70bc5e608678be|2021-01-15--17-02-04--5"), # SUBARU.IMPREZA
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL
@ -38,17 +38,18 @@ original_segments = [
segments = [ segments = [
("BODY", "regen660D86654BA|2022-07-06--14-27-15--0"), ("BODY", "regen660D86654BA|2022-07-06--14-27-15--0"),
("HYUNDAI", "regen657E25856BB|2022-07-06--14-26-51--0"), ("HYUNDAI", "regen114E5FF24D8|2022-07-14--17-08-47--0"),
("HYUNDAI", "d824e27e8c60172c|2022-07-08--21-21-15--0"),
("TOYOTA", "regenBA97410FBEC|2022-07-06--14-26-49--0"), ("TOYOTA", "regenBA97410FBEC|2022-07-06--14-26-49--0"),
("TOYOTA2", "regenDEDB1D9C991|2022-07-06--14-54-08--0"), ("TOYOTA2", "regenDEDB1D9C991|2022-07-06--14-54-08--0"),
("TOYOTA3", "regenDDC1FE60734|2022-07-06--14-32-06--0"), ("TOYOTA3", "regenDDC1FE60734|2022-07-06--14-32-06--0"),
("HONDA", "regen17B09D158B8|2022-07-06--14-31-46--0"), ("HONDA", "regenE62960EEC38|2022-07-14--19-33-24--0"),
("HONDA2", "regen041739C3E9A|2022-07-06--15-08-02--0"), ("HONDA2", "regenC3EBD92F029|2022-07-14--19-29-47--0"),
("CHRYSLER", "regenBB2F9C1425C|2022-07-06--14-31-41--0"), ("CHRYSLER", "regen38346FB33D0|2022-07-14--18-05-26--0"),
("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--2"), ("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--3"),
("SUBARU", "regen732B69F33B1|2022-07-06--14-36-18--0"), ("SUBARU", "regen54A1E2BE5AA|2022-07-14--18-07-50--0"),
("GM", "regen01D09D915B5|2022-07-06--14-36-20--0"), ("GM", "regen01D09D915B5|2022-07-06--14-36-20--0"),
("NISSAN", "regenEA6FB2773F5|2022-07-06--14-58-23--0"), ("NISSAN", "regenCA0B0DC946E|2022-07-14--18-10-17--0"),
("VOLKSWAGEN", "regen007098CA0EF|2022-07-06--15-01-26--0"), ("VOLKSWAGEN", "regen007098CA0EF|2022-07-06--15-01-26--0"),
("MAZDA", "regen61BA413D53B|2022-07-06--14-39-42--0"), ("MAZDA", "regen61BA413D53B|2022-07-06--14-39-42--0"),
] ]
@ -65,7 +66,7 @@ def run_test_process(data):
res = None res = None
if not args.upload_only: if not args.upload_only:
lr = LogReader.from_bytes(lr_dat) lr = LogReader.from_bytes(lr_dat)
res, log_msgs = test_process(cfg, lr, ref_log_path, args.ignore_fields, args.ignore_msgs) res, log_msgs = test_process(cfg, lr, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs)
# save logs so we can upload when updating refs # save logs so we can upload when updating refs
save_log(cur_log_fn, log_msgs) save_log(cur_log_fn, log_msgs)
@ -83,7 +84,7 @@ def get_log_data(segment):
return (segment, f.read()) return (segment, f.read())
def test_process(cfg, lr, ref_log_path, ignore_fields=None, ignore_msgs=None): def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None):
if ignore_fields is None: if ignore_fields is None:
ignore_fields = [] ignore_fields = []
if ignore_msgs is None: if ignore_msgs is None:
@ -96,10 +97,10 @@ def test_process(cfg, lr, ref_log_path, ignore_fields=None, ignore_msgs=None):
# check to make sure openpilot is engaged in the route # check to make sure openpilot is engaged in the route
if cfg.proc_name == "controlsd": if cfg.proc_name == "controlsd":
if not check_enabled(log_msgs): if not check_enabled(log_msgs):
raise Exception(f"Route never enabled: {ref_log_path}") return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs
try: try:
return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance, cfg.field_tolerances), log_msgs
except Exception as e: except Exception as e:
return str(e), log_msgs return str(e), log_msgs
@ -216,7 +217,7 @@ if __name__ == "__main__":
results: Any = defaultdict(dict) results: Any = defaultdict(dict)
p2 = pool.map(run_test_process, pool_args) p2 = pool.map(run_test_process, pool_args)
for (segment, proc, subtest_name, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)): for (segment, proc, subtest_name, result) in tqdm(p2, desc="Running Tests", total=len(pool_args)):
if isinstance(result, list): if not args.upload_only:
results[segment][proc + subtest_name] = result results[segment][proc + subtest_name] = result
diff1, diff2, failed = format_diff(results, ref_commit) diff1, diff2, failed = format_diff(results, ref_commit)

@ -526,6 +526,10 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct
fn += "turn_straight"; fn += "turn_straight";
} }
if (!active) {
fn += "_inactive";
}
auto icon = new QLabel; auto icon = new QLabel;
int wh = active ? 125 : 75; int wh = active ? 125 : 75;
icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {wh, wh}, Qt::IgnoreAspectRatio)); icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {wh, wh}, Qt::IgnoreAspectRatio));

@ -104,7 +104,7 @@ MapPanel::MapPanel(QWidget* parent) : QWidget(parent) {
screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation)); screenshot->setPixmap(pm.scaledToWidth(1080, Qt::SmoothTransformation));
no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter); no_prime_layout->addWidget(screenshot, 0, Qt::AlignHCenter);
QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma \nprime subscription. Sign up now: https://connect.comma.ai")); QLabel *signup = new QLabel(tr("Get turn-by-turn directions displayed and more with a comma\nprime subscription. Sign up now: https://connect.comma.ai"));
signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)"); signup->setStyleSheet(R"(font-size: 45px; color: white; font-weight:300;)");
signup->setAlignment(Qt::AlignCenter); signup->setAlignment(Qt::AlignCenter);

@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) {
} else if (n.security_type == SecurityType::OPEN) { } else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n); wifi->connect(n);
} else if (n.security_type == SecurityType::WPA) { } else if (n.security_type == SecurityType::WPA) {
QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"") + n.ssid + "\"", true, 8); QString pass = InputDialog::getText(tr("Enter password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) {
void Networking::wrongPassword(const QString &ssid) { void Networking::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(ssid)) { if (wifi->seenNetworks.contains(ssid)) {
const Network &n = wifi->seenNetworks.value(ssid); const Network &n = wifi->seenNetworks.value(ssid);
QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"") + n.ssid +"\"", true, 8); QString pass = InputDialog::getText(tr("Wrong password"), this, tr("for \"%1\"").arg(QString::fromUtf8(n.ssid)), true, 8);
if (!pass.isEmpty()) { if (!pass.isEmpty()) {
wifi->connect(n, pass); wifi->connect(n, pass);
} }
@ -174,7 +174,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(editApnButton); list->addItem(editApnButton);
// Set initial config // Set initial config
wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn"))); wifi->updateGsmSettings(roamingEnabled, QString::fromStdString(params.get("GsmApn")));
main_layout->addWidget(new ScrollView(list, this)); main_layout->addWidget(new ScrollView(list, this));
main_layout->addStretch(1); main_layout->addStretch(1);
@ -296,7 +296,7 @@ void WifiUI::refresh() {
QPushButton *forgetBtn = new QPushButton(tr("FORGET")); QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn"); forgetBtn->setObjectName("forgetBtn");
QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { QObject::connect(forgetBtn, &QPushButton::clicked, [=]() {
if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"") + QString::fromUtf8(network.ssid) + "\"?", this)) { if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), this)) {
wifi->forgetConnection(network.ssid); wifi->forgetConnection(network.ssid);
} }
}); });

@ -102,7 +102,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
// offroad-only buttons // offroad-only buttons
auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"), auto dcamBtn = new ButtonControl(tr("Driver Camera"), tr("PREVIEW"),
tr("Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)")); tr("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"));
connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); }); connect(dcamBtn, &ButtonControl::clicked, [=]() { emit showDriverView(); });
addItem(dcamBtn); addItem(dcamBtn);
@ -249,7 +249,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) {
}); });
auto uninstallBtn = new ButtonControl(tr("Uninstall ") + getBrand(), tr("UNINSTALL")); auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL"));
connect(uninstallBtn, &ButtonControl::clicked, [&]() { connect(uninstallBtn, &ButtonControl::clicked, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) { if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true); params.putBool("DoUninstall", true);

@ -165,7 +165,7 @@ void InputDialog::handleEnter() {
done(QDialog::Accepted); done(QDialog::Accepted);
emitText(line->text()); emitText(line->text());
} else { } else {
setMessage(tr("Need at least ") + QString::number(minLength) + tr(" characters!"), false); setMessage(tr("Need at least %1 characters!").arg(minLength), false);
} }
} }

@ -88,13 +88,16 @@ PairingPopup::PairingPopup(QWidget *parent) : QDialogBase(parent) {
title->setWordWrap(true); title->setWordWrap(true);
vlayout->addWidget(title); vlayout->addWidget(title);
QLabel *instructions = new QLabel(tr(R"( QLabel *instructions = new QLabel(QString(R"(
<ol type='1' style='margin-left: 15px;'> <ol type='1' style='margin-left: 15px;'>
<li style='margin-bottom: 50px;'>Go to https://connect.comma.ai on your phone</li> <li style='margin-bottom: 50px;'>%1</li>
<li style='margin-bottom: 50px;'>Click "add new device" and scan the QR code on the right</li> <li style='margin-bottom: 50px;'>%2</li>
<li style='margin-bottom: 50px;'>Bookmark connect.comma.ai to your home screen to use it like an app</li> <li style='margin-bottom: 50px;'>%3</li>
</ol> </ol>
)"), this); )").arg(tr("Go to https://connect.comma.ai on your phone"))
.arg(tr("Click \"add new device\" and scan the QR code on the right"))
.arg(tr("Bookmark connect.comma.ai to your home screen to use it like an app")), this);
instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;"); instructions->setStyleSheet("font-size: 47px; font-weight: bold; color: black;");
instructions->setWordWrap(true); instructions->setWordWrap(true);
vlayout->addWidget(instructions); vlayout->addWidget(instructions);

@ -0,0 +1,39 @@
# Multilanguage
![multilanguage_onroad](https://user-images.githubusercontent.com/25857203/178912800-2c798af8-78e3-498e-9e19-35906e0bafff.png)
## Contributing
Before getting started, make sure you have set up the openpilot Ubuntu development environment by reading the [tools README.md](/tools/README.md).
### Adding a New Language
openpilot provides a few tools to help contributors manage their translations and to ensure quality. To get started:
1. Add your new language to [languages.json](/selfdrive/ui/translations/languages.json) with the appropriate [language code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) and the localized language name (Simplified Chinese is `中文(繁體)`).
2. Generate the translation file (`*.ts`):
```shell
selfdrive/ui/update_translations.py
```
3. Edit the translation file, marking each translation as completed:
```shell
linguist selfdrive/ui/translations/your_language_file.ts
```
4. Save your file and generate the compiled QM file used by the Qt UI:
```shell
selfdrive/ui/update_translations.py --release
```
### Improving an Existing Language
Follow the steps above, omitting steps 1. and 2. Any time you edit translations you'll want to make sure to compile them.
### Testing
openpilot has a unit test to make sure all translations are up to date with the text in openpilot and that all translations are completed.
Run and fix any issues:
```python
selfdrive/ui/tests/test_translations.py
```

@ -134,8 +134,8 @@
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="104"/> <location filename="../qt/offroad/settings.cc" line="104"/>
<source>Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)</source> <source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
<translation>()</translation> <translation>()</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="108"/> <location filename="../qt/offroad/settings.cc" line="108"/>

@ -45,7 +45,7 @@
<message> <message>
<location filename="../qt/offroad/networking.cc" line="136"/> <location filename="../qt/offroad/networking.cc" line="136"/>
<source>Enter new tethering password</source> <source>Enter new tethering password</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="144"/> <location filename="../qt/offroad/networking.cc" line="144"/>
@ -70,7 +70,7 @@
<message> <message>
<location filename="../qt/offroad/networking.cc" line="165"/> <location filename="../qt/offroad/networking.cc" line="165"/>
<source>leave blank for automatic configuration</source> <source>leave blank for automatic configuration</source>
<translation> </translation> <translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -92,7 +92,7 @@
<message> <message>
<location filename="../qt/offroad/onboarding.cc" line="140"/> <location filename="../qt/offroad/onboarding.cc" line="140"/>
<source>You must accept the Terms and Conditions in order to use openpilot.</source> <source>You must accept the Terms and Conditions in order to use openpilot.</source>
<translation> openpilot을 .</translation> <translation>openpilot을 .</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/onboarding.cc" line="149"/> <location filename="../qt/offroad/onboarding.cc" line="149"/>
@ -102,7 +102,7 @@
<message> <message>
<location filename="../qt/offroad/onboarding.cc" line="154"/> <location filename="../qt/offroad/onboarding.cc" line="154"/>
<source>Decline, uninstall %1</source> <source>Decline, uninstall %1</source>
<translation> %1</translation> <translation>, %1 </translation>
</message> </message>
</context> </context>
<context> <context>
@ -134,8 +134,8 @@
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="105"/> <location filename="../qt/offroad/settings.cc" line="105"/>
<source>Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)</source> <source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
<translation> . ( )</translation> <translation> . ( )</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="109"/> <location filename="../qt/offroad/settings.cc" line="109"/>
@ -150,7 +150,7 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="112"/> <location filename="../qt/offroad/settings.cc" line="112"/>
<source>Are you sure you want to reset calibration?</source> <source>Are you sure you want to reset calibration?</source>
<translation> </translation> <translation> ?</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="119"/> <location filename="../qt/offroad/settings.cc" line="119"/>
@ -165,12 +165,12 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="119"/> <location filename="../qt/offroad/settings.cc" line="119"/>
<source>Review the rules, features, and limitations of openpilot</source> <source>Review the rules, features, and limitations of openpilot</source>
<translation>openpilot의 , , </translation> <translation>openpilot의 , </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="121"/> <location filename="../qt/offroad/settings.cc" line="121"/>
<source>Are you sure you want to review the training guide?</source> <source>Are you sure you want to review the training guide?</source>
<translation> </translation> <translation> ?</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="129"/> <location filename="../qt/offroad/settings.cc" line="129"/>
@ -185,7 +185,7 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="137"/> <location filename="../qt/offroad/settings.cc" line="137"/>
<source>Change Language</source> <source>Change Language</source>
<translation></translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="137"/> <location filename="../qt/offroad/settings.cc" line="137"/>
@ -195,7 +195,7 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="141"/> <location filename="../qt/offroad/settings.cc" line="141"/>
<source>Select a language</source> <source>Select a language</source>
<translation></translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="161"/> <location filename="../qt/offroad/settings.cc" line="161"/>
@ -210,37 +210,37 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="186"/> <location filename="../qt/offroad/settings.cc" line="186"/>
<source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source> <source>openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required.</source>
<translation>openpilot은 4° , 5° 8° . openpilot은 .</translation> <translation>openpilot은 4° , 5° 8° . openpilot은 .</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="197"/> <location filename="../qt/offroad/settings.cc" line="197"/>
<source> Your device is pointed %1° %2 and %3° %4.</source> <source> Your device is pointed %1° %2 and %3° %4.</source>
<translation> %1° %2 %3° %4 .</translation> <translation> %1° %2 %3° %4 .</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="198"/> <location filename="../qt/offroad/settings.cc" line="198"/>
<source>down</source> <source>down</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="198"/> <location filename="../qt/offroad/settings.cc" line="198"/>
<source>up</source> <source>up</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="199"/> <location filename="../qt/offroad/settings.cc" line="199"/>
<source>left</source> <source>left</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="199"/> <location filename="../qt/offroad/settings.cc" line="199"/>
<source>right</source> <source>right</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="210"/> <location filename="../qt/offroad/settings.cc" line="210"/>
<source>Are you sure you want to reboot?</source> <source>Are you sure you want to reboot?</source>
<translation> </translation> <translation> ?</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="217"/> <location filename="../qt/offroad/settings.cc" line="217"/>
@ -250,7 +250,7 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="223"/> <location filename="../qt/offroad/settings.cc" line="223"/>
<source>Are you sure you want to power off?</source> <source>Are you sure you want to power off?</source>
<translation> </translation> <translation> ?</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="230"/> <location filename="../qt/offroad/settings.cc" line="230"/>
@ -263,7 +263,7 @@
<message> <message>
<location filename="../qt/widgets/drive_stats.cc" line="37"/> <location filename="../qt/widgets/drive_stats.cc" line="37"/>
<source>Drives</source> <source>Drives</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/drive_stats.cc" line="39"/> <location filename="../qt/widgets/drive_stats.cc" line="39"/>
@ -273,7 +273,7 @@
<message> <message>
<location filename="../qt/widgets/drive_stats.cc" line="44"/> <location filename="../qt/widgets/drive_stats.cc" line="44"/>
<source>ALL TIME</source> <source>ALL TIME</source>
<translation> </translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/drive_stats.cc" line="46"/> <location filename="../qt/widgets/drive_stats.cc" line="46"/>
@ -308,13 +308,8 @@
</message> </message>
<message> <message>
<location filename="../qt/widgets/input.cc" line="168"/> <location filename="../qt/widgets/input.cc" line="168"/>
<source>Need at least </source> <source>Need at least %1 characters!</source>
<translation> </translation> <translation> %1 !</translation>
</message>
<message>
<location filename="../qt/widgets/input.cc" line="168"/>
<source> characters!</source>
<translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -343,27 +338,27 @@
<context> <context>
<name>MapETA</name> <name>MapETA</name>
<message> <message>
<location filename="../qt/maps/map.cc" line="618"/> <location filename="../qt/maps/map.cc" line="622"/>
<source>eta</source> <source>eta</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="628"/> <location filename="../qt/maps/map.cc" line="632"/>
<source>min</source> <source>min</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="632"/> <location filename="../qt/maps/map.cc" line="636"/>
<source>hr</source> <source>hr</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="652"/> <location filename="../qt/maps/map.cc" line="656"/>
<source>km</source> <source>km</source>
<translation>km</translation> <translation>km</translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="655"/> <location filename="../qt/maps/map.cc" line="659"/>
<source>mi</source> <source>mi</source>
<translation>mi</translation> <translation>mi</translation>
</message> </message>
@ -401,7 +396,7 @@
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="66"/> <location filename="../qt/maps/map_settings.cc" line="66"/>
<source>CLEAR</source> <source>CLEAR</source>
<translation>CLEAR</translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="81"/> <location filename="../qt/maps/map_settings.cc" line="81"/>
@ -411,14 +406,14 @@
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="95"/> <location filename="../qt/maps/map_settings.cc" line="95"/>
<source>Try the Navigation Beta</source> <source>Try the Navigation Beta</source>
<translation>() </translation> <translation>() </translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="107"/> <location filename="../qt/maps/map_settings.cc" line="107"/>
<source>Get turn-by-turn directions displayed and more with a comma <source>Get turn-by-turn directions displayed and more with a comma
prime subscription. Sign up now: https://connect.comma.ai</source> prime subscription. Sign up now: https://connect.comma.ai</source>
<translation> comma prime을 . <translation> comma prime을 .
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="164"/> <location filename="../qt/maps/map_settings.cc" line="164"/>
@ -437,7 +432,7 @@ location set</source>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="282"/> <location filename="../qt/maps/map_settings.cc" line="282"/>
<source>no recent destinations</source> <source>no recent destinations</source>
<translation> </translation> <translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -471,7 +466,7 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="30"/> <location filename="../qt/offroad/networking.cc" line="30"/>
<source>Advanced</source> <source>Advanced</source>
<translation></translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="87"/> <location filename="../qt/offroad/networking.cc" line="87"/>
@ -481,8 +476,8 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="87"/> <location filename="../qt/offroad/networking.cc" line="87"/>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
<source>for &quot;</source> <source>for &quot;%1&quot;</source>
<translation> &quot;</translation> <translation> &quot;%1&quot;</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
@ -547,52 +542,50 @@ location set</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="91"/> <location filename="../qt/widgets/prime.cc" line="97"/>
<source> <source>Go to https://connect.comma.ai on your phone</source>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <translation>https://connect.comma.ai에 접속하세요</translation>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Go to https://connect.comma.ai on your phone&lt;/li&gt; </message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Click &quot;add new device&quot; and scan the QR code on the right&lt;/li&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Bookmark connect.comma.ai to your home screen to use it like an app&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="98"/>
&lt;/ol&gt; <source>Click &quot;add new device&quot; and scan the QR code on the right</source>
</source> <translation>&quot; &quot; QR </translation>
<translation> </message>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;https://connect.comma.ai에 접속하세요&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="99"/>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;&quot; &quot; QR .&lt;/li&gt; <source>Bookmark connect.comma.ai to your home screen to use it like an app</source>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;connect.comma.ai을 .&lt;/li&gt; <translation>connect.comma.ai을 </translation>
&lt;/ol&gt;
</translation>
</message> </message>
</context> </context>
<context> <context>
<name>PrimeAdWidget</name> <name>PrimeAdWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="184"/> <location filename="../qt/widgets/prime.cc" line="187"/>
<source>Upgrade Now</source> <source>Upgrade Now</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="189"/> <location filename="../qt/widgets/prime.cc" line="192"/>
<source>Become a comma prime member at connect.comma.ai</source> <source>Become a comma prime member at connect.comma.ai</source>
<translation>connect.comma.ai에서 comma prime에 </translation> <translation>connect.comma.ai에서 comma prime에 </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="196"/> <location filename="../qt/widgets/prime.cc" line="199"/>
<source>PRIME FEATURES:</source> <source>PRIME FEATURES:</source>
<translation>PRIME </translation> <translation>PRIME </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Remote access</source> <source>Remote access</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>1 year of storage</source> <source>1 year of storage</source>
<translation>1 </translation> <translation>1 </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Developer perks</source> <source>Developer perks</source>
<translation> </translation> <translation> </translation>
</message> </message>
@ -600,22 +593,22 @@ location set</source>
<context> <context>
<name>PrimeUserWidget</name> <name>PrimeUserWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="123"/> <location filename="../qt/widgets/prime.cc" line="126"/>
<source> SUBSCRIBED</source> <source> SUBSCRIBED</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="129"/> <location filename="../qt/widgets/prime.cc" line="132"/>
<source>comma prime</source> <source>comma prime</source>
<translation>comma prime</translation> <translation>comma prime</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="135"/> <location filename="../qt/widgets/prime.cc" line="138"/>
<source>CONNECT.COMMA.AI</source> <source>CONNECT.COMMA.AI</source>
<translation>CONNECT.COMMA.AI</translation> <translation>CONNECT.COMMA.AI</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="148"/> <location filename="../qt/widgets/prime.cc" line="151"/>
<source>COMMA POINTS</source> <source>COMMA POINTS</source>
<translation>COMMA POINTS</translation> <translation>COMMA POINTS</translation>
</message> </message>
@ -775,12 +768,12 @@ location set</source>
<message> <message>
<location filename="../qt/setup/setup.cc" line="117"/> <location filename="../qt/setup/setup.cc" line="117"/>
<source>Getting Started</source> <source>Getting Started</source>
<translation></translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/setup/setup.cc" line="122"/> <location filename="../qt/setup/setup.cc" line="122"/>
<source>Before we get on the road, lets finish installation and cover some details.</source> <source>Before we get on the road, lets finish installation and cover some details.</source>
<translation> .</translation> <translation> .</translation>
</message> </message>
<message> <message>
<location filename="../qt/setup/setup.cc" line="147"/> <location filename="../qt/setup/setup.cc" line="147"/>
@ -857,19 +850,19 @@ location set</source>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="230"/> <location filename="../qt/widgets/prime.cc" line="233"/>
<source>Finish Setup</source> <source>Finish Setup</source>
<translation></translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="236"/> <location filename="../qt/widgets/prime.cc" line="239"/>
<source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source> <source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source>
<translation> (connect.comma.ai) comma prime .</translation> <translation> (connect.comma.ai) comma prime .</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="243"/> <location filename="../qt/widgets/prime.cc" line="246"/>
<source>Pair device</source> <source>Pair device</source>
<translation></translation> <translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -911,12 +904,12 @@ location set</source>
<message> <message>
<location filename="../qt/sidebar.cc" line="76"/> <location filename="../qt/sidebar.cc" line="76"/>
<source>GOOD</source> <source>GOOD</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/sidebar.cc" line="78"/> <location filename="../qt/sidebar.cc" line="78"/>
<source>OK</source> <source>OK</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/sidebar.cc" line="82"/> <location filename="../qt/sidebar.cc" line="82"/>
@ -1009,7 +1002,7 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="239"/> <location filename="../qt/offroad/settings.cc" line="239"/>
<source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source> <source>The last time openpilot successfully checked for an update. The updater only runs while the car is off.</source>
<translation> openpilot에서 . .</translation> <translation> openpilot이 . .</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="240"/> <location filename="../qt/offroad/settings.cc" line="240"/>
@ -1019,22 +1012,22 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="245"/> <location filename="../qt/offroad/settings.cc" line="245"/>
<source>CHECKING</source> <source>CHECKING</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>Uninstall </source> <source>UNINSTALL</source>
<translation> </translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>UNINSTALL</source> <source>Uninstall %1</source>
<translation></translation> <translation> %1</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="254"/> <location filename="../qt/offroad/settings.cc" line="254"/>
<source>Are you sure you want to uninstall?</source> <source>Are you sure you want to uninstall?</source>
<translation></translation> <translation>?</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="268"/> <location filename="../qt/offroad/settings.cc" line="268"/>
@ -1084,7 +1077,7 @@ location set</source>
<message> <message>
<location filename="../qt/widgets/ssh_keys.cc" line="50"/> <location filename="../qt/widgets/ssh_keys.cc" line="50"/>
<source>Username &apos;%1&apos; has no keys on GitHub</source> <source>Username &apos;%1&apos; has no keys on GitHub</source>
<translation> &apos;%1&apos; GitHub에 </translation> <translation>&apos;%1&apos; GitHub에 </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/ssh_keys.cc" line="54"/> <location filename="../qt/widgets/ssh_keys.cc" line="54"/>
@ -1094,7 +1087,7 @@ location set</source>
<message> <message>
<location filename="../qt/widgets/ssh_keys.cc" line="56"/> <location filename="../qt/widgets/ssh_keys.cc" line="56"/>
<source>Username &apos;%1&apos; doesn&apos;t exist on GitHub</source> <source>Username &apos;%1&apos; doesn&apos;t exist on GitHub</source>
<translation> &apos;%1&apos; GitHub에 </translation> <translation>&apos;%1&apos; GitHub에 </translation>
</message> </message>
</context> </context>
<context> <context>
@ -1120,7 +1113,7 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/onboarding.cc" line="111"/> <location filename="../qt/offroad/onboarding.cc" line="111"/>
<source>Scroll to accept</source> <source>Scroll to accept</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/onboarding.cc" line="126"/> <location filename="../qt/offroad/onboarding.cc" line="126"/>
@ -1168,12 +1161,12 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="52"/> <location filename="../qt/offroad/settings.cc" line="52"/>
<source>Display speed in km/h instead of mph.</source> <source>Display speed in km/h instead of mph.</source>
<translation>mph km/h로 .</translation> <translation>mph km/h로 .</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="57"/> <location filename="../qt/offroad/settings.cc" line="57"/>
<source>Record and Upload Driver Camera</source> <source>Record and Upload Driver Camera</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="58"/> <location filename="../qt/offroad/settings.cc" line="58"/>
@ -1193,7 +1186,7 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="70"/> <location filename="../qt/offroad/settings.cc" line="70"/>
<source>Show ETA in 24h format</source> <source>Show ETA in 24h format</source>
<translation>24 ETA </translation> <translation>24 </translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="71"/> <location filename="../qt/offroad/settings.cc" line="71"/>
@ -1274,8 +1267,8 @@ location set</source>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="299"/> <location filename="../qt/offroad/networking.cc" line="299"/>
<source>Forget Wi-Fi Network &quot;</source> <source>Forget Wi-Fi Network &quot;%1&quot;?</source>
<translation>wifi &quot;</translation> <translation>wifi &quot;%1&quot;?</translation>
</message> </message>
</context> </context>
</TS> </TS>

@ -134,8 +134,8 @@
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="105"/> <location filename="../qt/offroad/settings.cc" line="105"/>
<source>Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)</source> <source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="109"/> <location filename="../qt/offroad/settings.cc" line="109"/>
@ -308,13 +308,8 @@
</message> </message>
<message> <message>
<location filename="../qt/widgets/input.cc" line="168"/> <location filename="../qt/widgets/input.cc" line="168"/>
<source>Need at least </source> <source>Need at least %1 characters!</source>
<translation> </translation> <translation> %1 </translation>
</message>
<message>
<location filename="../qt/widgets/input.cc" line="168"/>
<source> characters!</source>
<translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -343,27 +338,27 @@
<context> <context>
<name>MapETA</name> <name>MapETA</name>
<message> <message>
<location filename="../qt/maps/map.cc" line="618"/> <location filename="../qt/maps/map.cc" line="622"/>
<source>eta</source> <source>eta</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="628"/> <location filename="../qt/maps/map.cc" line="632"/>
<source>min</source> <source>min</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="632"/> <location filename="../qt/maps/map.cc" line="636"/>
<source>hr</source> <source>hr</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="652"/> <location filename="../qt/maps/map.cc" line="656"/>
<source>km</source> <source>km</source>
<translation>km</translation> <translation>km</translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="655"/> <location filename="../qt/maps/map.cc" line="659"/>
<source>mi</source> <source>mi</source>
<translation>mi</translation> <translation>mi</translation>
</message> </message>
@ -415,7 +410,7 @@
</message> </message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="107"/> <location filename="../qt/maps/map_settings.cc" line="107"/>
<source>Get turn-by-turn directions displayed and more with a comma <source>Get turn-by-turn directions displayed and more with a comma
prime subscription. Sign up now: https://connect.comma.ai</source> prime subscription. Sign up now: https://connect.comma.ai</source>
<translation>comma prime以获取导航 <translation>comma prime以获取导航
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
@ -479,8 +474,8 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="87"/> <location filename="../qt/offroad/networking.cc" line="87"/>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
<source>for &quot;</source> <source>for &quot;%1&quot;</source>
<translation>&quot;</translation> <translation>&quot;%1&quot;</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
@ -545,52 +540,50 @@ location set</source>
<translation>comma账号配对</translation> <translation>comma账号配对</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="91"/> <location filename="../qt/widgets/prime.cc" line="97"/>
<source> <source>Go to https://connect.comma.ai on your phone</source>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <translation>访 https://connect.comma.ai</translation>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Go to https://connect.comma.ai on your phone&lt;/li&gt; </message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Click &quot;add new device&quot; and scan the QR code on the right&lt;/li&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Bookmark connect.comma.ai to your home screen to use it like an app&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="98"/>
&lt;/ol&gt; <source>Click &quot;add new device&quot; and scan the QR code on the right</source>
</source> <translation></translation>
<translation> </message>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;访 https://connect.comma.ai&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="99"/>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;&lt;/li&gt; <source>Bookmark connect.comma.ai to your home screen to use it like an app</source>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt; connect.comma.ai 便使&lt;/li&gt; <translation> connect.comma.ai 便使</translation>
&lt;/ol&gt;
</translation>
</message> </message>
</context> </context>
<context> <context>
<name>PrimeAdWidget</name> <name>PrimeAdWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="184"/> <location filename="../qt/widgets/prime.cc" line="187"/>
<source>Upgrade Now</source> <source>Upgrade Now</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="189"/> <location filename="../qt/widgets/prime.cc" line="192"/>
<source>Become a comma prime member at connect.comma.ai</source> <source>Become a comma prime member at connect.comma.ai</source>
<translation>connect.comma.ai以注册comma prime会员</translation> <translation>connect.comma.ai以注册comma prime会员</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="196"/> <location filename="../qt/widgets/prime.cc" line="199"/>
<source>PRIME FEATURES:</source> <source>PRIME FEATURES:</source>
<translation>comma prime特权</translation> <translation>comma prime特权</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Remote access</source> <source>Remote access</source>
<translation>访</translation> <translation>访</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>1 year of storage</source> <source>1 year of storage</source>
<translation>1</translation> <translation>1</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Developer perks</source> <source>Developer perks</source>
<translation></translation> <translation></translation>
</message> </message>
@ -598,22 +591,22 @@ location set</source>
<context> <context>
<name>PrimeUserWidget</name> <name>PrimeUserWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="123"/> <location filename="../qt/widgets/prime.cc" line="126"/>
<source> SUBSCRIBED</source> <source> SUBSCRIBED</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="129"/> <location filename="../qt/widgets/prime.cc" line="132"/>
<source>comma prime</source> <source>comma prime</source>
<translation>comma prime</translation> <translation>comma prime</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="135"/> <location filename="../qt/widgets/prime.cc" line="138"/>
<source>CONNECT.COMMA.AI</source> <source>CONNECT.COMMA.AI</source>
<translation>CONNECT.COMMA.AI</translation> <translation>CONNECT.COMMA.AI</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="148"/> <location filename="../qt/widgets/prime.cc" line="151"/>
<source>COMMA POINTS</source> <source>COMMA POINTS</source>
<translation>COMMA POINTS点数</translation> <translation>COMMA POINTS点数</translation>
</message> </message>
@ -855,17 +848,17 @@ location set</source>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="230"/> <location filename="../qt/widgets/prime.cc" line="233"/>
<source>Finish Setup</source> <source>Finish Setup</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="236"/> <location filename="../qt/widgets/prime.cc" line="239"/>
<source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source> <source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source>
<translation>comma connect connect.comma.aicomma prime优惠</translation> <translation>comma connect connect.comma.aicomma prime优惠</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="243"/> <location filename="../qt/widgets/prime.cc" line="246"/>
<source>Pair device</source> <source>Pair device</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1021,13 +1014,13 @@ location set</source>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>Uninstall </source> <source>UNINSTALL</source>
<translation> </translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>UNINSTALL</source> <source>Uninstall %1</source>
<translation></translation> <translation> %1</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="254"/> <location filename="../qt/offroad/settings.cc" line="254"/>
@ -1272,8 +1265,8 @@ location set</source>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="299"/> <location filename="../qt/offroad/networking.cc" line="299"/>
<source>Forget Wi-Fi Network &quot;</source> <source>Forget Wi-Fi Network &quot;%1&quot;?</source>
<translation>WiFi网络&quot;</translation> <translation>WiFi网络 &quot;%1&quot;?</translation>
</message> </message>
</context> </context>
</TS> </TS>

@ -125,7 +125,7 @@
<message> <message>
<location filename="../qt/offroad/settings.cc" line="104"/> <location filename="../qt/offroad/settings.cc" line="104"/>
<source>Driver Camera</source> <source>Driver Camera</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="104"/> <location filename="../qt/offroad/settings.cc" line="104"/>
@ -134,8 +134,8 @@
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="105"/> <location filename="../qt/offroad/settings.cc" line="105"/>
<source>Preview the driver facing camera to help optimize device mounting position for best driver monitoring experience. (vehicle must be off)</source> <source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
<translation>便調()</translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="109"/> <location filename="../qt/offroad/settings.cc" line="109"/>
@ -308,13 +308,8 @@
</message> </message>
<message> <message>
<location filename="../qt/widgets/input.cc" line="168"/> <location filename="../qt/widgets/input.cc" line="168"/>
<source>Need at least </source> <source>Need at least %1 characters!</source>
<translation> </translation> <translation> %1 </translation>
</message>
<message>
<location filename="../qt/widgets/input.cc" line="168"/>
<source> characters!</source>
<translation> </translation>
</message> </message>
</context> </context>
<context> <context>
@ -343,27 +338,27 @@
<context> <context>
<name>MapETA</name> <name>MapETA</name>
<message> <message>
<location filename="../qt/maps/map.cc" line="618"/> <location filename="../qt/maps/map.cc" line="622"/>
<source>eta</source> <source>eta</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="628"/> <location filename="../qt/maps/map.cc" line="632"/>
<source>min</source> <source>min</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="632"/> <location filename="../qt/maps/map.cc" line="636"/>
<source>hr</source> <source>hr</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="652"/> <location filename="../qt/maps/map.cc" line="656"/>
<source>km</source> <source>km</source>
<translation>km</translation> <translation>km</translation>
</message> </message>
<message> <message>
<location filename="../qt/maps/map.cc" line="655"/> <location filename="../qt/maps/map.cc" line="659"/>
<source>mi</source> <source>mi</source>
<translation>mi</translation> <translation>mi</translation>
</message> </message>
@ -415,16 +410,11 @@
</message> </message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="107"/> <location filename="../qt/maps/map_settings.cc" line="107"/>
<source>Get turn-by-turn directions displayed and more with a comma <source>Get turn-by-turn directions displayed and more with a comma
prime subscription. Sign up now: https://connect.comma.ai</source> prime subscription. Sign up now: https://connect.comma.ai</source>
<translation> comma 使 <translation> comma 使
https://connect.comma.ai</translation> https://connect.comma.ai</translation>
</message> </message>
<message>
<source>Get turn-by-turn directions displayed and more with a comma
prime subscription. Sign up now: https://connect.comma.ai</source>
<translation type="vanished"> comma 使https://connect.comma.ai</translation>
</message>
<message> <message>
<location filename="../qt/maps/map_settings.cc" line="164"/> <location filename="../qt/maps/map_settings.cc" line="164"/>
<source>No home <source>No home
@ -486,8 +476,8 @@ location set</source>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="87"/> <location filename="../qt/offroad/networking.cc" line="87"/>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
<source>for &quot;</source> <source>for &quot;%1&quot;</source>
<translation> &quot;</translation> <translation> &quot;%1&quot;</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="97"/> <location filename="../qt/offroad/networking.cc" line="97"/>
@ -552,52 +542,50 @@ location set</source>
<translation> comma </translation> <translation> comma </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="91"/> <location filename="../qt/widgets/prime.cc" line="97"/>
<source> <source>Go to https://connect.comma.ai on your phone</source>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <translation> https://connect.comma.ai</translation>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Go to https://connect.comma.ai on your phone&lt;/li&gt; </message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Click &quot;add new device&quot; and scan the QR code on the right&lt;/li&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt;Bookmark connect.comma.ai to your home screen to use it like an app&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="98"/>
&lt;/ol&gt; <source>Click &quot;add new device&quot; and scan the QR code on the right</source>
</source> <translation> &quot;add new device&quot; </translation>
<translation> </message>
&lt;ol type=&apos;1&apos; style=&apos;margin-left: 15px;&apos;&gt; <message>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt; https://connect.comma.ai&lt;/li&gt; <location filename="../qt/widgets/prime.cc" line="99"/>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt; &quot;add new device&quot; &lt;/li&gt; <source>Bookmark connect.comma.ai to your home screen to use it like an app</source>
&lt;li style=&apos;margin-bottom: 50px;&apos;&gt; connect.comma.ai 便 App 使&lt;/li&gt; <translation> connect.comma.ai 便 App 使</translation>
&lt;/ol&gt;
</translation>
</message> </message>
</context> </context>
<context> <context>
<name>PrimeAdWidget</name> <name>PrimeAdWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="184"/> <location filename="../qt/widgets/prime.cc" line="187"/>
<source>Upgrade Now</source> <source>Upgrade Now</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="189"/> <location filename="../qt/widgets/prime.cc" line="192"/>
<source>Become a comma prime member at connect.comma.ai</source> <source>Become a comma prime member at connect.comma.ai</source>
<translation> connect.comma.ai </translation> <translation> connect.comma.ai </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="196"/> <location filename="../qt/widgets/prime.cc" line="199"/>
<source>PRIME FEATURES:</source> <source>PRIME FEATURES:</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Remote access</source> <source>Remote access</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>1 year of storage</source> <source>1 year of storage</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="201"/> <location filename="../qt/widgets/prime.cc" line="204"/>
<source>Developer perks</source> <source>Developer perks</source>
<translation></translation> <translation></translation>
</message> </message>
@ -605,22 +593,22 @@ location set</source>
<context> <context>
<name>PrimeUserWidget</name> <name>PrimeUserWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="123"/> <location filename="../qt/widgets/prime.cc" line="126"/>
<source> SUBSCRIBED</source> <source> SUBSCRIBED</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="129"/> <location filename="../qt/widgets/prime.cc" line="132"/>
<source>comma prime</source> <source>comma prime</source>
<translation>comma </translation> <translation>comma </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="135"/> <location filename="../qt/widgets/prime.cc" line="138"/>
<source>CONNECT.COMMA.AI</source> <source>CONNECT.COMMA.AI</source>
<translation>CONNECT.COMMA.AI</translation> <translation>CONNECT.COMMA.AI</translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="148"/> <location filename="../qt/widgets/prime.cc" line="151"/>
<source>COMMA POINTS</source> <source>COMMA POINTS</source>
<translation>COMMA </translation> <translation>COMMA </translation>
</message> </message>
@ -865,17 +853,17 @@ location set</source>
<context> <context>
<name>SetupWidget</name> <name>SetupWidget</name>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="230"/> <location filename="../qt/widgets/prime.cc" line="233"/>
<source>Finish Setup</source> <source>Finish Setup</source>
<translation></translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="236"/> <location filename="../qt/widgets/prime.cc" line="239"/>
<source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source> <source>Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer.</source>
<translation> comma connect (connect.comma.ai) comma </translation> <translation> comma connect (connect.comma.ai) comma </translation>
</message> </message>
<message> <message>
<location filename="../qt/widgets/prime.cc" line="243"/> <location filename="../qt/widgets/prime.cc" line="246"/>
<source>Pair device</source> <source>Pair device</source>
<translation></translation> <translation></translation>
</message> </message>
@ -1031,13 +1019,13 @@ location set</source>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>Uninstall </source> <source>UNINSTALL</source>
<translation> </translation> <translation></translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="252"/> <location filename="../qt/offroad/settings.cc" line="252"/>
<source>UNINSTALL</source> <source>Uninstall %1</source>
<translation></translation> <translation> %1</translation>
</message> </message>
<message> <message>
<location filename="../qt/offroad/settings.cc" line="254"/> <location filename="../qt/offroad/settings.cc" line="254"/>
@ -1282,8 +1270,8 @@ location set</source>
</message> </message>
<message> <message>
<location filename="../qt/offroad/networking.cc" line="299"/> <location filename="../qt/offroad/networking.cc" line="299"/>
<source>Forget Wi-Fi Network &quot;</source> <source>Forget Wi-Fi Network &quot;%1&quot;?</source>
<translation> Wi-Fi &quot;</translation> <translation> Wi-Fi &quot;%1&quot;?</translation>
</message> </message>
</context> </context>
</TS> </TS>

@ -10,7 +10,7 @@ TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations")
LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json") LANGUAGES_FILE = os.path.join(TRANSLATIONS_DIR, "languages.json")
def update_translations(release=False, translations_dir=TRANSLATIONS_DIR): def update_translations(release=False, vanish=False, translations_dir=TRANSLATIONS_DIR):
with open(LANGUAGES_FILE, "r") as f: with open(LANGUAGES_FILE, "r") as f:
translation_files = json.load(f) translation_files = json.load(f)
@ -20,7 +20,10 @@ def update_translations(release=False, translations_dir=TRANSLATIONS_DIR):
continue continue
tr_file = os.path.join(translations_dir, f"{file}.ts") tr_file = os.path.join(translations_dir, f"{file}.ts")
ret = os.system(f"lupdate -recursive {UI_DIR} -ts {tr_file}") args = f"lupdate -recursive {UI_DIR} -ts {tr_file}"
if vanish:
args += " -no-obsolete"
ret = os.system(args)
assert ret == 0 assert ret == 0
if release: if release:
@ -32,6 +35,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update translation files for UI", parser = argparse.ArgumentParser(description="Update translation files for UI",
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--release", action="store_true", help="Create compiled QM translation files used by UI") parser.add_argument("--release", action="store_true", help="Create compiled QM translation files used by UI")
parser.add_argument("--vanish", action="store_true", help="Remove translations with source text no longer found")
args = parser.parse_args() args = parser.parse_args()
update_translations(args.release) update_translations(args.release, args.vanish)

@ -10,7 +10,6 @@ if arch == "larch64":
env.Program('camerad', [ env.Program('camerad', [
'main.cc', 'main.cc',
'cameras/camera_common.cc', 'cameras/camera_common.cc',
'transforms/rgb_to_yuv.cc',
'imgproc/utils.cc', 'imgproc/utils.cc',
cameras, cameras,
], LIBS=libs) ], LIBS=libs)
@ -19,5 +18,4 @@ if GetOption("test"):
env.Program('test/ae_gray_test', [ env.Program('test/ae_gray_test', [
'test/ae_gray_test.cc', 'test/ae_gray_test.cc',
'cameras/camera_common.cc', 'cameras/camera_common.cc',
'transforms/rgb_to_yuv.cc',
], LIBS=libs) ], LIBS=libs)

@ -32,15 +32,14 @@ public:
Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) {
char args[4096]; char args[4096];
const CameraInfo *ci = &s->ci; const CameraInfo *ci = &s->ci;
hdr_ = ci->hdr;
snprintf(args, sizeof(args), snprintf(args, sizeof(args),
"-cl-fast-relaxed-math -cl-denorms-are-zero " "-cl-fast-relaxed-math -cl-denorms-are-zero "
"-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d "
"-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DRGB_STRIDE=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d " "-DRGB_WIDTH=%d -DRGB_HEIGHT=%d -DRGB_STRIDE=%d -DYUV_STRIDE=%d -DUV_OFFSET=%d "
"-DBAYER_FLIP=%d -DHDR=%d -DCAM_NUM=%d%s", "-DCAM_NUM=%d%s",
ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset,
b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset, b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset,
ci->bayer_flip, ci->hdr, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : "");
const char *cl_file = "cameras/real_debayer.cl"; const char *cl_file = "cameras/real_debayer.cl";
cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args);
krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err)); krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err));
@ -63,12 +62,10 @@ public:
private: private:
cl_kernel krnl_; cl_kernel krnl_;
bool hdr_;
}; };
void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType init_rgb_type, VisionStreamType init_yuv_type) { void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType init_yuv_type) {
vipc_server = v; vipc_server = v;
this->rgb_type = init_rgb_type;
this->yuv_type = init_yuv_type; this->yuv_type = init_yuv_type;
const CameraInfo *ci = &s->ci; const CameraInfo *ci = &s->ci;
@ -89,11 +86,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s,
rgb_width = ci->frame_width; rgb_width = ci->frame_width;
rgb_height = ci->frame_height; rgb_height = ci->frame_height;
yuv_transform = get_model_yuv_transform(ci->bayer); yuv_transform = get_model_yuv_transform();
vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height);
rgb_stride = vipc_server->get_buffer(rgb_type)->stride;
LOGD("created %d UI vipc buffers with size %dx%d", UI_BUF_COUNT, rgb_width, rgb_height);
int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width); int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width);
int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, rgb_height); int nv12_height = VENUS_Y_SCANLINES(COLOR_FMT_NV12, rgb_height);
@ -104,10 +97,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s,
vipc_server->create_buffers_with_sizes(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset); vipc_server->create_buffers_with_sizes(yuv_type, YUV_BUFFER_COUNT, false, rgb_width, rgb_height, nv12_size, nv12_width, nv12_uv_offset);
LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, nv12_width, nv12_height); LOGD("created %d YUV vipc buffers with size %dx%d", YUV_BUFFER_COUNT, nv12_width, nv12_height);
if (ci->bayer) { debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset);
debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset);
}
rgb2yuv = std::make_unique<Rgb2Yuv>(context, device_id, rgb_width, rgb_height, rgb_stride);
#ifdef __APPLE__ #ifdef __APPLE__
q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err));
@ -135,7 +125,6 @@ bool CameraBuf::acquire() {
} }
cur_frame_data = camera_bufs_metadata[cur_buf_idx]; cur_frame_data = camera_bufs_metadata[cur_buf_idx];
cur_rgb_buf = vipc_server->get_buffer(rgb_type);
cur_yuv_buf = vipc_server->get_buffer(yuv_type); cur_yuv_buf = vipc_server->get_buffer(yuv_type);
cl_mem camrabuf_cl = camera_bufs[cur_buf_idx].buf_cl; cl_mem camrabuf_cl = camera_bufs[cur_buf_idx].buf_cl;
cl_event event; cl_event event;
@ -144,12 +133,7 @@ bool CameraBuf::acquire() {
cur_camera_buf = &camera_bufs[cur_buf_idx]; cur_camera_buf = &camera_bufs[cur_buf_idx];
if (debayer) { debayer->queue(q, camrabuf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event);
debayer->queue(q, camrabuf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event);
} else {
assert(rgb_stride == camera_state->ci.frame_stride);
rgb2yuv->queue(q, camrabuf_cl, cur_rgb_buf->buf_cl);
}
clWaitForEvents(1, &event); clWaitForEvents(1, &event);
CL_CHECK(clReleaseEvent(event)); CL_CHECK(clReleaseEvent(event));
@ -193,32 +177,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr
framed.setProcessingTime(frame_data.processing_time); framed.setProcessingTime(frame_data.processing_time);
} }
kj::Array<uint8_t> get_frame_image(const CameraBuf *b) {
static const int x_min = util::getenv("XMIN", 0);
static const int y_min = util::getenv("YMIN", 0);
static const int env_xmax = util::getenv("XMAX", -1);
static const int env_ymax = util::getenv("YMAX", -1);
static const int scale = util::getenv("SCALE", 1);
assert(b->cur_rgb_buf);
const int x_max = env_xmax != -1 ? env_xmax : b->rgb_width - 1;
const int y_max = env_ymax != -1 ? env_ymax : b->rgb_height - 1;
const int new_width = (x_max - x_min + 1) / scale;
const int new_height = (y_max - y_min + 1) / scale;
const uint8_t *dat = (const uint8_t *)b->cur_rgb_buf->addr;
kj::Array<uint8_t> frame_image = kj::heapArray<uint8_t>(new_width*new_height*3);
uint8_t *resized_dat = frame_image.begin();
int goff = x_min*3 + y_min*b->rgb_stride;
for (int r=0;r<new_height;r++) {
for (int c=0;c<new_width;c++) {
memcpy(&resized_dat[(r*new_width+c)*3], &dat[goff+r*b->rgb_stride*scale+c*3*scale], 3*sizeof(uint8_t));
}
}
return kj::mv(frame_image);
}
kj::Array<uint8_t> get_raw_frame_image(const CameraBuf *b) { kj::Array<uint8_t> get_raw_frame_image(const CameraBuf *b) {
const uint8_t *dat = (const uint8_t *)b->cur_camera_buf->addr; const uint8_t *dat = (const uint8_t *)b->cur_camera_buf->addr;

@ -9,7 +9,6 @@
#include "cereal/visionipc/visionbuf.h" #include "cereal/visionipc/visionbuf.h"
#include "cereal/visionipc/visionipc.h" #include "cereal/visionipc/visionipc.h"
#include "cereal/visionipc/visionipc_server.h" #include "cereal/visionipc/visionipc_server.h"
#include "system/camerad/transforms/rgb_to_yuv.h"
#include "common/mat.h" #include "common/mat.h"
#include "common/queue.h" #include "common/queue.h"
#include "common/swaglog.h" #include "common/swaglog.h"
@ -27,7 +26,6 @@
#define CAMERA_ID_IMX390 9 #define CAMERA_ID_IMX390 9
#define CAMERA_ID_MAX 10 #define CAMERA_ID_MAX 10
const int UI_BUF_COUNT = 4;
const int YUV_BUFFER_COUNT = 40; const int YUV_BUFFER_COUNT = 40;
enum CameraType { enum CameraType {
@ -36,11 +34,6 @@ enum CameraType {
WideRoadCam WideRoadCam
}; };
// TODO: remove these once all the internal tools are moved to vipc
const bool env_send_driver = getenv("SEND_DRIVER") != NULL;
const bool env_send_road = getenv("SEND_ROAD") != NULL;
const bool env_send_wide_road = getenv("SEND_WIDE_ROAD") != NULL;
// for debugging // for debugging
const bool env_disable_road = getenv("DISABLE_ROAD") != NULL; const bool env_disable_road = getenv("DISABLE_ROAD") != NULL;
const bool env_disable_wide_road = getenv("DISABLE_WIDE_ROAD") != NULL; const bool env_disable_wide_road = getenv("DISABLE_WIDE_ROAD") != NULL;
@ -51,9 +44,6 @@ const bool env_log_raw_frames = getenv("LOG_RAW_FRAMES") != NULL;
typedef struct CameraInfo { typedef struct CameraInfo {
uint32_t frame_width, frame_height; uint32_t frame_width, frame_height;
uint32_t frame_stride; uint32_t frame_stride;
bool bayer;
int bayer_flip;
bool hdr;
uint32_t frame_offset = 0; uint32_t frame_offset = 0;
uint32_t extra_height = 0; uint32_t extra_height = 0;
int registers_offset = -1; int registers_offset = -1;
@ -92,9 +82,8 @@ private:
VisionIpcServer *vipc_server; VisionIpcServer *vipc_server;
CameraState *camera_state; CameraState *camera_state;
Debayer *debayer = nullptr; Debayer *debayer = nullptr;
std::unique_ptr<Rgb2Yuv> rgb2yuv;
VisionStreamType rgb_type, yuv_type; VisionStreamType yuv_type;
int cur_buf_idx; int cur_buf_idx;
@ -116,7 +105,7 @@ public:
CameraBuf() = default; CameraBuf() = default;
~CameraBuf(); ~CameraBuf();
void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType rgb_type, VisionStreamType yuv_type); void init(cl_device_id device_id, cl_context context, CameraState *s, VisionIpcServer * v, int frame_cnt, VisionStreamType yuv_type);
bool acquire(); bool acquire();
void release(); void release();
void queue(size_t buf_idx); void queue(size_t buf_idx);
@ -125,7 +114,6 @@ public:
typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt); typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt);
void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data); void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &frame_data);
kj::Array<uint8_t> get_frame_image(const CameraBuf *b);
kj::Array<uint8_t> get_raw_frame_image(const CameraBuf *b); kj::Array<uint8_t> get_raw_frame_image(const CameraBuf *b);
float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip);
std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback);

@ -46,19 +46,11 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = {
.registers_offset = 0, .registers_offset = 0,
.frame_offset = AR0231_REGISTERS_HEIGHT, .frame_offset = AR0231_REGISTERS_HEIGHT,
.stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT,
.bayer = true,
.bayer_flip = 1,
.hdr = false,
}, },
[CAMERA_ID_IMX390] = { [CAMERA_ID_IMX390] = {
.frame_width = FRAME_WIDTH, .frame_width = FRAME_WIDTH,
.frame_height = FRAME_HEIGHT, .frame_height = FRAME_HEIGHT,
.frame_stride = FRAME_STRIDE, .frame_stride = FRAME_STRIDE,
.bayer = true,
.bayer_flip = 1,
.hdr = false,
}, },
}; };
@ -614,7 +606,7 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) {
// ******************* camera ******************* // ******************* camera *******************
void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServer * v, int camera_id_, int camera_num_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, bool enabled_) { void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServer * v, int camera_id_, int camera_num_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, bool enabled_) {
multi_cam_state = multi_cam_state_; multi_cam_state = multi_cam_state_;
camera_id = camera_id_; camera_id = camera_id_;
camera_num = camera_num_; camera_num = camera_num_;
@ -638,7 +630,7 @@ void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServe
exposure_time = 5; exposure_time = 5;
cur_ev[0] = cur_ev[1] = cur_ev[2] = (dc_gain_enabled ? DC_GAIN : 1) * sensor_analog_gains[gain_idx] * exposure_time; cur_ev[0] = cur_ev[1] = cur_ev[2] = (dc_gain_enabled ? DC_GAIN : 1) * sensor_analog_gains[gain_idx] * exposure_time;
buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, rgb_type, yuv_type); buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, yuv_type);
} }
void CameraState::camera_open() { void CameraState::camera_open() {
@ -833,9 +825,9 @@ void CameraState::camera_open() {
} }
void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) {
s->driver_cam.camera_init(s, v, CAMERA_ID_AR0231, 2, 20, device_id, ctx, VISION_STREAM_RGB_DRIVER, VISION_STREAM_DRIVER, !env_disable_driver); s->driver_cam.camera_init(s, v, CAMERA_ID_AR0231, 2, 20, device_id, ctx, VISION_STREAM_DRIVER, !env_disable_driver);
s->road_cam.camera_init(s, v, CAMERA_ID_AR0231, 1, 20, device_id, ctx, VISION_STREAM_RGB_ROAD, VISION_STREAM_ROAD, !env_disable_road); s->road_cam.camera_init(s, v, CAMERA_ID_AR0231, 1, 20, device_id, ctx, VISION_STREAM_ROAD, !env_disable_road);
s->wide_road_cam.camera_init(s, v, CAMERA_ID_AR0231, 0, 20, device_id, ctx, VISION_STREAM_RGB_WIDE_ROAD, VISION_STREAM_WIDE_ROAD, !env_disable_wide_road); s->wide_road_cam.camera_init(s, v, CAMERA_ID_AR0231, 0, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD, !env_disable_wide_road);
s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"});
} }
@ -1233,9 +1225,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt)
auto framed = msg.initEvent().initDriverCameraState(); auto framed = msg.initEvent().initDriverCameraState();
framed.setFrameType(cereal::FrameData::FrameType::FRONT); framed.setFrameType(cereal::FrameData::FrameType::FRONT);
fill_frame_data(framed, c->buf.cur_frame_data); fill_frame_data(framed, c->buf.cur_frame_data);
if (env_send_driver) {
framed.setImage(get_frame_image(&c->buf));
}
if (c->camera_id == CAMERA_ID_AR0231) { if (c->camera_id == CAMERA_ID_AR0231) {
ar0231_process_registers(s, c, framed); ar0231_process_registers(s, c, framed);
} }
@ -1248,9 +1238,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
MessageBuilder msg; MessageBuilder msg;
auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState(); auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState();
fill_frame_data(framed, b->cur_frame_data); fill_frame_data(framed, b->cur_frame_data);
if ((c == &s->road_cam && env_send_road) || (c == &s->wide_road_cam && env_send_wide_road)) { if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation
framed.setImage(get_frame_image(b));
} else if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation
framed.setImage(get_raw_frame_image(b)); framed.setImage(get_raw_frame_image(b));
} }
LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera");

@ -55,7 +55,7 @@ public:
void sensors_start(); void sensors_start();
void camera_open(); void camera_open();
void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType rgb_type, VisionStreamType yuv_type, bool enabled); void camera_init(MultiCameraState *multi_cam_state, VisionIpcServer * v, int camera_id, int camera_num, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type, bool enabled);
void camera_close(); void camera_close();
std::map<uint16_t, uint16_t> ar0231_parse_registers(uint8_t *data, std::initializer_list<uint16_t> addrs); std::map<uint16_t, uint16_t> ar0231_parse_registers(uint8_t *data, std::initializer_list<uint16_t> addrs);

@ -1,36 +0,0 @@
#include "system/camerad/transforms/rgb_to_yuv.h"
#include <cassert>
#include <cstdio>
Rgb2Yuv::Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride) {
assert(width % 2 == 0 && height % 2 == 0);
char args[1024];
snprintf(args, sizeof(args),
"-cl-fast-relaxed-math -cl-denorms-are-zero "
#ifdef CL_DEBUG
"-DCL_DEBUG "
#endif
"-DWIDTH=%d -DHEIGHT=%d -DUV_WIDTH=%d -DUV_HEIGHT=%d -DRGB_STRIDE=%d -DRGB_SIZE=%d",
width, height, width / 2, height / 2, rgb_stride, width * height);
cl_program prg = cl_program_from_file(ctx, device_id, "transforms/rgb_to_yuv.cl", args);
krnl = CL_CHECK_ERR(clCreateKernel(prg, "rgb_to_yuv", &err));
CL_CHECK(clReleaseProgram(prg));
work_size[0] = (width + (width % 4 == 0 ? 0 : (4 - width % 4))) / 4;
work_size[1] = (height + (height % 4 == 0 ? 0 : (4 - height % 4))) / 4;
}
Rgb2Yuv::~Rgb2Yuv() {
CL_CHECK(clReleaseKernel(krnl));
}
void Rgb2Yuv::queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl) {
CL_CHECK(clSetKernelArg(krnl, 0, sizeof(cl_mem), &rgb_cl));
CL_CHECK(clSetKernelArg(krnl, 1, sizeof(cl_mem), &yuv_cl));
cl_event event;
CL_CHECK(clEnqueueNDRangeKernel(q, krnl, 2, NULL, &work_size[0], NULL, 0, 0, &event));
CL_CHECK(clWaitForEvents(1, &event));
CL_CHECK(clReleaseEvent(event));
}

@ -1,14 +0,0 @@
#pragma once
#include "common/clutil.h"
class Rgb2Yuv {
public:
Rgb2Yuv(cl_context ctx, cl_device_id device_id, int width, int height, int rgb_stride);
~Rgb2Yuv();
void queue(cl_command_queue q, cl_mem rgb_cl, cl_mem yuv_cl);
private:
size_t work_size[2];
cl_kernel krnl;
};

@ -1,191 +0,0 @@
#include <fcntl.h>
#include <getopt.h>
#include <memory.h>
#include <unistd.h>
#include <cassert>
#include <cmath>
#include <csignal>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#ifdef ANDROID
#define MAXE 0
#include <unistd.h>
#else
// The libyuv implementation on ARM is slightly different than on x86
// Our implementation matches the ARM version, so accept errors of 1
#define MAXE 1
#endif
#include <CL/cl.h>
#include "libyuv.h"
#include "system/camerad/transforms/rgb_to_yuv.h"
#include "common/clutil.h"
static inline double millis_since_boot() {
struct timespec t;
clock_gettime(CLOCK_BOOTTIME, &t);
return t.tv_sec * 1000.0 + t.tv_nsec * 1e-6;
}
void cl_init(cl_device_id &device_id, cl_context &context) {
device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT);
context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err));
}
bool compare_results(uint8_t *a, uint8_t *b, int len, int stride, int width, int height, uint8_t *rgb) {
int min_diff = 0., max_diff = 0., max_e = 0.;
int e1 = 0, e0 = 0;
int e0y = 0, e0u = 0, e0v = 0, e1y = 0, e1u = 0, e1v = 0;
int max_e_i = 0;
for (int i = 0;i < len;i++) {
int e = ((int)a[i]) - ((int)b[i]);
if(e < min_diff) {
min_diff = e;
}
if(e > max_diff) {
max_diff = e;
}
int e_abs = std::abs(e);
if(e_abs > max_e) {
max_e = e_abs;
max_e_i = i;
}
if(e_abs < 1) {
e0++;
if(i < stride * height)
e0y++;
else if(i < stride * height + stride * height / 4)
e0u++;
else
e0v++;
} else {
e1++;
if(i < stride * height)
e1y++;
else if(i < stride * height + stride * height / 4)
e1u++;
else
e1v++;
}
}
//printf("max diff : %d, min diff : %d, e < 1: %d, e >= 1: %d\n", max_diff, min_diff, e0, e1);
//printf("Y: e < 1: %d, e >= 1: %d, U: e < 1: %d, e >= 1: %d, V: e < 1: %d, e >= 1: %d\n", e0y, e1y, e0u, e1u, e0v, e1v);
if(max_e <= MAXE) {
return true;
}
int row = max_e_i / stride;
if(row < height) {
printf("max error is Y: %d = (libyuv: %u - cl: %u), row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], row, max_e_i % stride);
} else if(row >= height && row < (height + height / 4)) {
printf("max error is U: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height) / 2, max_e_i % stride / 2);
} else {
printf("max error is V: %d = %u - %u, row: %d, col: %d\n", max_e, a[max_e_i], b[max_e_i], (row - height - height / 4) / 2, max_e_i % stride / 2);
}
return false;
}
int main(int argc, char** argv) {
srand(1337);
cl_device_id device_id;
cl_context context;
cl_init(device_id, context) ;
int err;
const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0};
cl_command_queue q = clCreateCommandQueueWithProperties(context, device_id, props, &err);
if(err != 0) {
std::cout << "clCreateCommandQueueWithProperties error: " << err << std::endl;
}
int width = 1164;
int height = 874;
int opt = 0;
while ((opt = getopt(argc, argv, "f")) != -1)
{
switch (opt)
{
case 'f':
std::cout << "Using front camera dimensions" << std::endl;
int width = 1152;
int height = 846;
}
}
std::cout << "Width: " << width << " Height: " << height << std::endl;
uint8_t *rgb_frame = new uint8_t[width * height * 3];
RGBToYUVState rgb_to_yuv_state;
rgb_to_yuv_init(&rgb_to_yuv_state, context, device_id, width, height, width * 3);
int frame_yuv_buf_size = width * height * 3 / 2;
cl_mem yuv_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, frame_yuv_buf_size, (void*)NULL, &err));
uint8_t *frame_yuv_buf = new uint8_t[frame_yuv_buf_size];
uint8_t *frame_yuv_ptr_y = frame_yuv_buf;
uint8_t *frame_yuv_ptr_u = frame_yuv_buf + (width * height);
uint8_t *frame_yuv_ptr_v = frame_yuv_ptr_u + ((width/2) * (height/2));
cl_mem rgb_cl = CL_CHECK_ERR(clCreateBuffer(context, CL_MEM_READ_WRITE, width * height * 3, (void*)NULL, &err));
int mismatched = 0;
int counter = 0;
srand (time(NULL));
for (int i = 0; i < 100; i++) {
for (int i = 0; i < width * height * 3; i++) {
rgb_frame[i] = (uint8_t)rand();
}
double t1 = millis_since_boot();
libyuv::RGB24ToI420((uint8_t*)rgb_frame, width * 3,
frame_yuv_ptr_y, width,
frame_yuv_ptr_u, width/2,
frame_yuv_ptr_v, width/2,
width, height);
double t2 = millis_since_boot();
//printf("Libyuv: rgb to yuv: %.2fms\n", t2-t1);
clEnqueueWriteBuffer(q, rgb_cl, CL_TRUE, 0, width * height * 3, (void *)rgb_frame, 0, NULL, NULL);
t1 = millis_since_boot();
rgb_to_yuv_queue(&rgb_to_yuv_state, q, rgb_cl, yuv_cl);
t2 = millis_since_boot();
//printf("OpenCL: rgb to yuv: %.2fms\n", t2-t1);
uint8_t *yyy = (uint8_t *)clEnqueueMapBuffer(q, yuv_cl, CL_TRUE,
CL_MAP_READ, 0, frame_yuv_buf_size,
0, NULL, NULL, &err);
if(!compare_results(frame_yuv_ptr_y, yyy, frame_yuv_buf_size, width, width, height, (uint8_t*)rgb_frame))
mismatched++;
clEnqueueUnmapMemObject(q, yuv_cl, yyy, 0, NULL, NULL);
// std::this_thread::sleep_for(std::chrono::milliseconds(20));
if(counter++ % 100 == 0)
printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched);
}
printf("Matched: %d, Mismatched: %d\n", counter - mismatched, mismatched);
delete[] frame_yuv_buf;
rgb_to_yuv_destroy(&rgb_to_yuv_state);
clReleaseContext(context);
delete[] rgb_frame;
if (mismatched == 0)
return 0;
else
return -1;
}

@ -222,7 +222,7 @@ def extract_casync_image(target_slot_number: int, partition: dict, cloudlog):
raise Exception(f"Raw hash mismatch '{partition['hash_raw'].lower()}'") raise Exception(f"Raw hash mismatch '{partition['hash_raw'].lower()}'")
def flash_partition(target_slot_number: int, partition: dict, cloudlog): def flash_partition(target_slot_number: int, partition: dict, cloudlog, standalone=False):
cloudlog.info(f"Downloading and writing {partition['name']}") cloudlog.info(f"Downloading and writing {partition['name']}")
if verify_partition(target_slot_number, partition): if verify_partition(target_slot_number, partition):
@ -236,7 +236,7 @@ def flash_partition(target_slot_number: int, partition: dict, cloudlog):
path = get_partition_path(target_slot_number, partition) path = get_partition_path(target_slot_number, partition)
if 'casync_caibx' in partition: if ('casync_caibx' in partition) and not standalone:
extract_casync_image(target_slot_number, partition, cloudlog) extract_casync_image(target_slot_number, partition, cloudlog)
else: else:
extract_compressed_image(target_slot_number, partition, cloudlog) extract_compressed_image(target_slot_number, partition, cloudlog)
@ -263,7 +263,7 @@ def swap(manifest_path: str, target_slot_number: int, cloudlog) -> None:
cloudlog.error(f"Swap failed {out}") cloudlog.error(f"Swap failed {out}")
def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) -> None: def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog, standalone=False) -> None:
update = json.load(open(manifest_path)) update = json.load(open(manifest_path))
cloudlog.info(f"Target slot {target_slot_number}") cloudlog.info(f"Target slot {target_slot_number}")
@ -276,7 +276,7 @@ def flash_agnos_update(manifest_path: str, target_slot_number: int, cloudlog) ->
for retries in range(10): for retries in range(10):
try: try:
flash_partition(target_slot_number, partition, cloudlog) flash_partition(target_slot_number, partition, cloudlog, standalone)
success = True success = True
break break
@ -320,9 +320,9 @@ if __name__ == "__main__":
elif args.swap: elif args.swap:
while not verify_agnos_update(args.manifest, target_slot_number): while not verify_agnos_update(args.manifest, target_slot_number):
logging.error("Verification failed. Flashing AGNOS") logging.error("Verification failed. Flashing AGNOS")
flash_agnos_update(args.manifest, target_slot_number, logging) flash_agnos_update(args.manifest, target_slot_number, logging, standalone=True)
logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}") logging.warning(f"Verification succeeded. Swapping to slot {target_slot_number}")
swap(args.manifest, target_slot_number, logging) swap(args.manifest, target_slot_number, logging)
else: else:
flash_agnos_update(args.manifest, target_slot_number, logging) flash_agnos_update(args.manifest, target_slot_number, logging, standalone=True)

@ -0,0 +1,74 @@
#!/usr/bin/env python3
import argparse
import collections
import multiprocessing
import os
from typing import Dict, List
import requests
from tqdm import tqdm
import system.hardware.tici.casync as casync
def get_chunk_download_size(chunk):
sha = chunk.sha.hex()
path = os.path.join(remote_url, sha[:4], sha + ".cacnk")
if os.path.isfile(path):
return os.path.getsize(path)
else:
r = requests.head(path)
r.raise_for_status()
return int(r.headers['content-length'])
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Compute overlap between two casync manifests')
parser.add_argument('frm')
parser.add_argument('to')
args = parser.parse_args()
frm = casync.parse_caibx(args.frm)
to = casync.parse_caibx(args.to)
remote_url = args.to.replace('.caibx', '')
most_common = collections.Counter(t.sha for t in to).most_common(1)[0][0]
frm_dict = casync.build_chunk_dict(frm)
# Get content-length for each chunk
with multiprocessing.Pool() as pool:
szs = list(tqdm(pool.imap(get_chunk_download_size, to), total=len(to)))
chunk_sizes = {t.sha: sz for (t, sz) in zip(to, szs)}
sources: Dict[str, List[int]] = {
'seed': [],
'remote_uncompressed': [],
'remote_compressed': [],
}
for chunk in to:
# Assume most common chunk is the zero chunk
if chunk.sha == most_common:
continue
if chunk.sha in frm_dict:
sources['seed'].append(chunk.length)
else:
sources['remote_uncompressed'].append(chunk.length)
sources['remote_compressed'].append(chunk_sizes[chunk.sha])
print()
print("Update statistics (excluding zeros)")
print()
print("Download only with no seed:")
print(f" Remote (uncompressed)\t\t{sum(sources['seed'] + sources['remote_uncompressed']) / 1000 / 1000:.2f} MB\tn = {len(to)}")
print(f" Remote (compressed download)\t{sum(chunk_sizes.values()) / 1000 / 1000:.2f} MB\tn = {len(to)}")
print()
print("Upgrade with seed partition:")
print(f" Seed (uncompressed)\t\t{sum(sources['seed']) / 1000 / 1000:.2f} MB\t\t\t\tn = {len(sources['seed'])}")
sz, n = sum(sources['remote_uncompressed']), len(sources['remote_uncompressed'])
print(f" Remote (uncompressed)\t\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}")
sz, n = sum(sources['remote_compressed']), len(sources['remote_compressed'])
print(f" Remote (compressed download)\t{sz / 1000 / 1000:.2f} MB\t(avg {sz / 1000 / 1000 / n:4f} MB)\tn = {n}")

@ -47,22 +47,22 @@ LogReader::~LogReader() {
} }
bool LogReader::load(const std::string &url, std::atomic<bool> *abort, bool local_cache, int chunk_size, int retries) { bool LogReader::load(const std::string &url, std::atomic<bool> *abort, bool local_cache, int chunk_size, int retries) {
FileReader f(local_cache, chunk_size, retries); raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort);
std::string data = f.read(url, abort); if (raw_.empty()) return false;
if (data.empty()) return false;
return load((std::byte*)data.data(), data.size(), abort); if (url.find(".bz2") != std::string::npos) {
raw_ = decompressBZ2(raw_, abort);
if (raw_.empty()) return false;
}
return parse(abort);
} }
bool LogReader::load(const std::byte *data, size_t size, std::atomic<bool> *abort) { bool LogReader::load(const std::byte *data, size_t size, std::atomic<bool> *abort) {
raw_ = decompressBZ2(data, size, abort); raw_.assign((const char *)data, size);
if (raw_.empty()) { return parse(abort);
if (!(abort && *abort)) { }
rWarning("failed to decompress log");
}
return false;
}
bool LogReader::parse(std::atomic<bool> *abort) {
try { try {
kj::ArrayPtr<const capnp::word> words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); kj::ArrayPtr<const capnp::word> words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word));
while (words.size() > 0 && !(abort && *abort)) { while (words.size() > 0 && !(abort && *abort)) {

@ -52,10 +52,10 @@ public:
~LogReader(); ~LogReader();
bool load(const std::string &url, std::atomic<bool> *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::string &url, std::atomic<bool> *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0);
bool load(const std::byte *data, size_t size, std::atomic<bool> *abort = nullptr); bool load(const std::byte *data, size_t size, std::atomic<bool> *abort = nullptr);
std::vector<Event*> events; std::vector<Event*> events;
private: private:
bool parse(std::atomic<bool> *abort);
std::string raw_; std::string raw_;
#ifdef HAS_MEMORY_RESOURCE #ifdef HAS_MEMORY_RESOURCE
std::pmr::monotonic_buffer_resource *mbr_ = nullptr; std::pmr::monotonic_buffer_resource *mbr_ = nullptr;

@ -82,9 +82,9 @@ void Route::addFileToSegment(int n, const QString &file) {
const int pos = name.lastIndexOf("--"); const int pos = name.lastIndexOf("--");
name = pos != -1 ? name.mid(pos + 2) : name; name = pos != -1 ? name.mid(pos + 2) : name;
if (name == "rlog.bz2") { if (name == "rlog.bz2" || name == "rlog") {
segments_[n].rlog = file; segments_[n].rlog = file;
} else if (name == "qlog.bz2") { } else if (name == "qlog.bz2" || name == "qlog") {
segments_[n].qlog = file; segments_[n].qlog = file;
} else if (name == "fcamera.hevc") { } else if (name == "fcamera.hevc") {
segments_[n].road_cam = file; segments_[n].road_cam = file;

@ -71,6 +71,7 @@ TEST_CASE("LogReader") {
FileReader reader(true); FileReader reader(true);
std::string corrupt_content = reader.read(TEST_RLOG_URL); std::string corrupt_content = reader.read(TEST_RLOG_URL);
corrupt_content.resize(corrupt_content.length() / 2); corrupt_content.resize(corrupt_content.length() / 2);
corrupt_content = decompressBZ2(corrupt_content);
LogReader log; LogReader log;
REQUIRE(log.load((std::byte *)corrupt_content.data(), corrupt_content.size())); REQUIRE(log.load((std::byte *)corrupt_content.data(), corrupt_content.size()));
REQUIRE(log.events.size() > 0); REQUIRE(log.events.size() > 0);

@ -22,6 +22,7 @@ if [[ "$DETACH" ]]; then
EXTRA_ARGS="-d" EXTRA_ARGS="-d"
fi fi
docker kill carla_sim || true
docker run \ docker run \
--name carla_sim \ --name carla_sim \
--rm \ --rm \

Loading…
Cancel
Save