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
steps:
- name: Wait for green check mark
uses: commaai/wait-on-check-action@f16fc3bb6cd4886520b4e9328db1d42104d5cadc
uses: lewagon/wait-on-check-action@e2558238c09778af25867eb5de5a3ce4bbae3dcd
with:
ref: master
wait-interval: 30

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

@ -17,12 +17,12 @@ env:
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e 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: |
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 .
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
@ -58,6 +58,9 @@ jobs:
run: |
TARGET_DIR=$STRIPPED_DIR release/build_devel.sh
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
run: |
eval "$BUILD"
@ -66,6 +69,10 @@ jobs:
run: |
cd $STRIPPED_DIR
${{ 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 && \
python -m unittest discover selfdrive/car"
@ -510,3 +517,67 @@ jobs:
run: |
$DOCKER_LOGIN
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
language: system
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:
- --error-exitcode=1
- --language=c++

@ -105,23 +105,25 @@ Directory Structure
├── third_party # External libraries
├── pyextra # Extra python packages
└── 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
└── proclogd # Logs information from /proc
└── selfdrive # Code needed to drive the car
├── assets # Fonts, images, and sounds for UI
├── athena # Allows communication with the app
├── 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
├── common # Shared C/C++ code for the daemons
├── controls # Planning and controls
├── debug # Tools to help you debug and do car ports
├── locationd # Precise localization and vehicle parameter estimation
├── loggerd # Logger and uploader of car data
├── manager # Deamon that starts/stops all other daemons as needed
├── modeld # Driving and monitoring model runners
├── proclogd # Logs information from proc
├── sensord # IMU interface code
├── monitoring # Daemon to determine driver attention
├── navd # Turn-by-turn navigation
├── sensord # IMU interface code
├── test # Unit tests, system tests, and a car simulator
└── ui # The UI

@ -1,3 +1,6 @@
Version 0.8.16 (2022-XX-XX)
========================
Version 0.8.15 (2022-07-20)
========================
* 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, 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;
const mat3 transform = (mat3){{
1.0, 0.0, 0.0,
0.0, 1.0, 0.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.**
# Gold - 31 cars
# Gold - 30 cars
|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 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>|
|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|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 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 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-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|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 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 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 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 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>|
@ -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 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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
@ -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|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>|
|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|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|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 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 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>|
@ -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|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|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|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>|
@ -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>|
|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>|
|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|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|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|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-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>|
|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|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|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 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>|
@ -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 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>|
|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|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|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|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 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 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 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 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 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|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|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>|
# Bronze - 80 cars
|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|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|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 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 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 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 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 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 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|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|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>|
|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|
|---|---|---|:---:|:---:|:---:|:---:|:---:|
|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 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|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>|
@ -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 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>|
|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>|
|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 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 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-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 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 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|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|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|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|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|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-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|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|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>|
|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>|
@ -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|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 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|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|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 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-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>|
|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>|
|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-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|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>|
@ -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|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>|
|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|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|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|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 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-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>|
@ -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>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>8</sup>Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.) <br />
## 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/).

@ -29,8 +29,6 @@ camerad
^^^^^^^
.. autodoxygenindex::
:project: system_camerad_cameras
.. autodoxygenindex::
:project: system_camerad_transforms
.. autodoxygenindex::
: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/*
tools/__init__.py
tools/lib/*
tools/joystick/*
tools/replay/*.cc
@ -128,7 +129,15 @@ system/clocksd/.gitignore
system/clocksd/SConscript
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/version.h
@ -187,6 +196,9 @@ selfdrive/controls/lib/lateral_mpc_lib/*
selfdrive/controls/lib/longitudinal_mpc_lib/*
selfdrive/hardware
system/__init__.py
system/hardware/__init__.py
system/hardware/base.h
system/hardware/base.py
@ -220,6 +232,7 @@ selfdrive/locationd/laikad_helpers.py
selfdrive/locationd/locationd.h
selfdrive/locationd/locationd.cc
selfdrive/locationd/paramsd.py
selfdrive/locationd/models/__init__.py
selfdrive/locationd/models/.gitignore
selfdrive/locationd/models/car_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/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/pool.cl
system/camerad/imgproc/utils.cc
@ -330,6 +338,7 @@ selfdrive/manager/process.py
selfdrive/manager/test/__init__.py
selfdrive/manager/test/test_manager.py
selfdrive/modeld/__init__.py
selfdrive/modeld/SConscript
selfdrive/modeld/modeld.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:
cloudlog.warning("Using cached CarParams")
vin = cached_params.carVin
vin, vin_rx_addr = cached_params.carVin, 0
car_fw = list(cached_params.carFw)
else:
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)
car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs)
exact_fw_match, fw_candidates = match_fw_to_car(car_fw)
else:
vin = VIN_UNKNOWN
vin, vin_rx_addr = VIN_UNKNOWN, 0
exact_fw_match, fw_candidates, car_fw = True, set(), []
if len(vin) != 17:
@ -166,7 +166,7 @@ def fingerprint(logcan, sendcan):
source = car.CarParams.FingerprintSource.fixed
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

@ -1,7 +1,8 @@
from opendbc.can.packer import CANPacker
from common.realtime import DT_CTRL
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.values import RAM_CARS, CarControllerParams
from selfdrive.car.chrysler.values import CAR, RAM_CARS, CarControllerParams
class CarController:
@ -13,23 +14,45 @@ class CarController:
self.hud_count = 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.params = CarControllerParams(CP)
def update(self, CC, CS, low_speed_alert):
def update(self, CC, CS):
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
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 ***
das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0
if CC.cruiseControl.cancel:
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, cancel=True))
elif CC.enabled and CS.out.cruiseState.standstill:
can_sends.append(create_cruise_buttons(self.packer, CS.button_counter + 1, das_bus, resume=True))
# cruise buttons
if (self.frame - self.last_button_frame)*DT_CTRL > 0.05:
das_bus = 2 if self.CP.carFingerprint in RAM_CARS else 0
# 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
if self.frame % 25 == 0:
@ -40,22 +63,22 @@ class CarController:
# steering
if self.frame % 2 == 0:
# steer torque
new_steer = int(round(CC.actuators.steer * CarControllerParams.STEER_MAX))
apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, CarControllerParams)
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, self.params)
if not lkas_active:
apply_steer = 0
self.steer_rate_limited = new_steer != apply_steer
self.apply_steer_last = apply_steer
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
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.lkas_active_prev = lkas_active
self.lkas_control_bit_prev = lkas_control_bit
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

@ -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)
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
enabled_val = 2 if CP.carFingerprint in RAM_CARS else 1
values = {
"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)

@ -46,15 +46,15 @@ class CarInterface(CarInterfaceBase):
# Ram
elif candidate == CAR.RAM_1500:
ret.steerActuatorDelay = 0.2
ret.wheelbase = 3.88
ret.steerRatio = 16.3
ret.mass = 2493. + STD_CARGO_KG
ret.maxLateralAccel = 2.4
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:
raise ValueError(f"Unsupported car: {candidate}")
@ -93,4 +93,4 @@ class CarInterface(CarInterfaceBase):
return ret
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:
STEER_MAX = 261 # higher than this faults the EPS on Chrysler/Jeep. Ram DT allows more
STEER_DELTA_UP = 3
STEER_DELTA_DOWN = 3
STEER_ERROR_MAX = 80
def __init__(self, CP):
self.STEER_MAX = 261 # higher than this faults the EPS on Chrysler/Jeep. Ram DT allows more
self.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
@ -47,8 +53,8 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = {
CAR.PACIFICA_2018: ChryslerCarInfo("Chrysler Pacifica 2017-18"),
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_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-20", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"),
CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-21"),
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-22"),
}
# Unique CAN messages:

@ -6,6 +6,7 @@ EXT_DIAG_RESPONSE = b'\x50\x03'
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):
"""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.
@ -26,9 +27,22 @@ def disable_ecu(logcan, sendcan, bus=0, addr=0x7d0, com_cont_req=b'\x28\x83\x01'
cloudlog.warning("ecu disabled")
return True
except Exception:
cloudlog.exception("ecu disable exception")
print(f"ecu disable retry ({i+1}) ...")
cloudlog.warning("ecu disable failed")
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
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
class Request:
@ -207,6 +212,13 @@ REQUESTS: List[Request] = [
[CHRYSLER_VERSION_REQUEST],
[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)
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)
matches = match_fw_to_car_exact(build_fw_dict(car_fw))
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
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)
if brand is not None:
versions = {brand: versions[brand]}
if query_brand is not None:
versions = {query_brand: versions[query_brand]}
if extra is not None:
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)
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_chunk in chunks(addr):
for r in requests:
@ -444,7 +456,7 @@ def get_fw_versions(logcan, sendcan, brand=None, extra=None, timeout=0.1, debug=
if addrs:
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:
cloudlog.warning(f"FW query exception: {traceback.format_exc()}")
@ -497,13 +509,13 @@ if __name__ == "__main__":
t = time.time()
print("Getting vin...")
addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
print(f"VIN: {vin}")
addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug)
print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}')
print(f"Getting VIN took {time.time() - t:.3f} s")
print()
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)
print()

@ -109,13 +109,13 @@ class HondaCarInfo(CarInfo):
CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = {
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),
],
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_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),
],
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.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_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.HRV: HondaCarInfo("Honda HR-V 2019-20", harness=Harness.nidec),
CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", min_steer_speed=0., harness=Harness.nidec),
CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", 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.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.PILOT: HondaCarInfo("Honda Pilot 2016-21", harness=Harness.nidec),
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-22", 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.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),
}
@ -1406,6 +1406,7 @@ FW_VERSIONS = {
b'38897-T20-A020\x00\x00',
b'38897-T20-A510\x00\x00',
b'38897-T21-A010\x00\x00',
b'38897-T20-A210\x00\x00',
],
(Ecu.srs, 0x18DA53F1, None): [
b'77959-T20-A970\x00\x00',
@ -1415,6 +1416,7 @@ FW_VERSIONS = {
b'78108-T21-A220\x00\x00',
b'78108-T21-A620\x00\x00',
b'78108-T23-A110\x00\x00',
b'78108-T21-A230\x00\x00',
],
(Ecu.vsa, 0x18DA28F1, None): [
b'57114-T20-AB40\x00\x00',
@ -1429,6 +1431,7 @@ FW_VERSIONS = {
b'37805-64L-A540\x00\x00',
b'37805-64S-A540\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 CC.cruiseControl.cancel:
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
# cruise standstill 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
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.interfaces import CarStateBase
PREV_BUTTON_SAMPLES = 4
PREV_BUTTON_SAMPLES = 8
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
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"])
return ret
@ -362,7 +365,9 @@ class CarState(CarStateBase):
("CRUISE_ACTIVE", "SCC1"),
("SET_SPEED", "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"),

@ -18,11 +18,9 @@ def create_cam_0x2a4(packer, frame, camera_values):
})
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 = {
"_COUNTER": cnt % 0xf,
"SET_ME_1": 1,
"DISTANCE_BTN": 1 if resume else 0,
"PAUSE_RESUME_BTN": 1 if cancel else 0,
"CRUISE_BUTTONS": btn,
}
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 panda import Panda
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 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
@ -321,8 +321,7 @@ class CarInterface(CarInterfaceBase):
# To avoid re-engaging when openpilot cancels, check user engagement intention via buttons
# Main button also can trigger an engagement on these cars
allow_enable = any(btn in 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 or True)
events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable)
if self.CS.brake_error:
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 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_EV6: HyundaiCarInfo("Kia EV6 2022", "All", harness=Harness.hyundai_p),
# 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_G80: HyundaiCarInfo("Genesis G80 2018", "All", harness=Harness.hyundai_h),
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2018", "All", harness=Harness.hyundai_c),
CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h),
CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c),
}
class Buttons:
@ -167,7 +167,7 @@ class Buttons:
RES_ACCEL = 1
SET_DECEL = 2
GAP_DIST = 3
CANCEL = 4
CANCEL = 4 # on newer models, this is a pause/resume button
FINGERPRINTS = {
CAR.ELANTRA: [{

@ -135,11 +135,11 @@ class CarInterfaceBase(ABC):
return ret
@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)
tune.init('torque')
tune.torque.useSteeringAngle = True
tune.torque.useSteeringAngle = use_steering_angle
tune.torque.kp = 1.0 / params['LAT_ACCEL_FACTOR']
tune.torque.kf = 1.0 / 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])
request_counter[tx_addr] += 1
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
else:
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.MAZDA3: MazdaCarInfo("Mazda 3 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"),
}
@ -65,6 +65,7 @@ FW_VERSIONS = {
],
(Ecu.engine, 0x7e0, None): [
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',
],
(Ecu.fwdRadar, 0x764, None): [
@ -266,6 +267,7 @@ FW_VERSIONS = {
(Ecu.engine, 0x7e0, None): [
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'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x764, None): [
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-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-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
],
(Ecu.transmission, 0x7e1, None): [
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.ASCENT: SubaruCarInfo("Subaru Ascent 2019-20", "All"),
CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"),
CAR.IMPREZA: [
SubaruCarInfo("Subaru Impreza 2017-19"),
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"),
],
CAR.IMPREZA_2020: [
SubaruCarInfo("Subaru Impreza 2020-21"),
SubaruCarInfo("Subaru Impreza 2020-22"),
SubaruCarInfo("Subaru Crosstrek 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.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),

@ -21,7 +21,7 @@ COMMA BODY: [.nan, 1000, .nan]
# Totally new cars
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
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
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,
hud_control.rightLaneVisible, hud_control.leftLaneDepart,
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_TSS2: [
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"),
],
CAR.COROLLAH_TSS2: [
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_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"),
@ -151,14 +152,14 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = {
# Lexus
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_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_IS: ToyotaCarInfo("Lexus IS 2017-19"),
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_NX_TSS2: ToyotaCarInfo("Lexus NX 2020"),
CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020"),
CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2020"),
CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"),
CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"),
CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-2020"),
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_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"),
@ -788,6 +789,7 @@ FW_VERSIONS = {
(Ecu.eps, 0x7a1, None): [
b'8965B12361\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'8965B76050\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'F152612820\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'F152612A00\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'F152676293\x00\x00\x00\x00\x00\x00',
b'F152676303\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): [
b'\x018821F3301100\x00\x00\x00\x00',
@ -832,6 +835,7 @@ FW_VERSIONS = {
b'\x028646F1202000\x00\x00\x00\x008646G2601200\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'\x028646F1601100\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'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00',
@ -1302,6 +1306,7 @@ FW_VERSIONS = {
b'\x01896634AA0000\x00\x00\x00\x00',
b'\x01896634AA1000\x00\x00\x00\x00',
b'\x01896634A88000\x00\x00\x00\x00',
b'\x01896634A89000\x00\x00\x00\x00',
],
(Ecu.fwdRadar, 0x750, 0xf): [
b'\x018821F0R01100\x00\x00\x00\x00',
@ -1381,11 +1386,12 @@ FW_VERSIONS = {
b'8965B42172\x00\x00\x00\x00\x00\x00',
],
(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'\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): [
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)):
try:
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
if vin.startswith(b'\x11'):
vin = vin[1:18]
return addr[0], vin.decode()
return addr[0], rx_addr, vin.decode()
print(f"vin query retry ({i+1}) ...")
except Exception:
cloudlog.warning(f"VIN query exception: {traceback.format_exc()}")
return 0, VIN_UNKNOWN
return 0, 0, VIN_UNKNOWN
if __name__ == "__main__":
@ -41,5 +41,5 @@ if __name__ == "__main__":
sendcan = messaging.pub_sock('sendcan')
logcan = messaging.sub_sock('can')
time.sleep(1)
addr, vin = get_vin(logcan, sendcan, 1, debug=False)
print(hex(addr), vin)
addr, vin_rx_addr, vin = get_vin(logcan, sendcan, 1, debug=False)
print(f'TX: {hex(addr)}, RX: {hex(vin_rx_addr)}, VIN: {vin}')

@ -37,6 +37,7 @@ class CANBUS:
pt = 0
cam = 2
class DBC_FILES:
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
@ -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\" " +
"from the vehicle drop-down for a harness that integrates at the CAN gateway inside the dashboard.",
Column.MODEL)
VW_VARIANT = CarFootnote(
"Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)",
Column.MODEL)
@dataclass
@ -133,25 +137,43 @@ class VWCarInfo(CarInfo):
CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.ARTEON_MK1: VWCarInfo("Volkswagen Arteon 2018, 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.ATLAS_MK1: VWCarInfo("Volkswagen Atlas 2018-19, 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
CAR.ARTEON_MK1: [
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: [
VWCarInfo("Volkswagen e-Golf 2014, 2018-20"),
VWCarInfo("Volkswagen Golf 2015-20"),
VWCarInfo("Volkswagen Golf Alltrack 2017-18"),
VWCarInfo("Volkswagen Golf GTE 2016"),
VWCarInfo("Volkswagen Golf GTI 2018-21"),
VWCarInfo("Volkswagen Golf R 2016-19"),
VWCarInfo("Volkswagen Golf SportsVan 2016"),
VWCarInfo("Volkswagen Golf SportWagen 2015"),
VWCarInfo("Volkswagen e-Golf 2014-20"),
VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf Alltrack 2015-19"),
VWCarInfo("Volkswagen Golf GTD 2015-20"),
VWCarInfo("Volkswagen Golf GTE 2015-20"),
VWCarInfo("Volkswagen Golf GTI 2015-21"),
VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]),
VWCarInfo("Volkswagen Golf SportsVan 2015-20"),
],
CAR.JETTA_MK7: [
VWCarInfo("Volkswagen Jetta 2018-21"),
VWCarInfo("Volkswagen Jetta GLI 2021"),
VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
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.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.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),

@ -96,7 +96,11 @@ class Controls:
self.sm = sm
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',
'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState'] + self.camera_packets + joystick_packet,
@ -224,12 +228,8 @@ class Controls:
if not self.CP.notCar:
self.events.add_from_msg(self.sm['driverMonitoringState'].events)
# Handle car events. Ignore when CAN is invalid
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
else:
# Add car events, ignore if CAN isn't valid
if CS.canValid:
self.events.add_from_msg(CS.events)
# Create events for temperature, disk space, and memory
@ -309,14 +309,19 @@ class Controls:
self.events.add(EventName.cameraFrameRate)
if self.rk.lagging:
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)
if not self.sm.valid['pandaStates']:
self.events.add(EventName.usbError)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
elif not CS.canValid:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
no_system_errors = len(self.events) != num_events
if (not self.sm.all_checks() or self.can_rcv_error) and no_system_errors and CS.canValid and not CS.canTimeout:
has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE))
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():
self.events.add(EventName.commIssue)
elif not self.sm.all_freq_ok():
@ -406,7 +411,8 @@ class Controls:
if not self.initialized:
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:
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
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 laika import AstroDog
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.gps_time import GPSTime
from laika.helpers import ConstellationId
@ -27,6 +28,7 @@ from system.swaglog import cloudlog
MAX_TIME_GAP = 10
EPHEMERIS_CACHE = 'LaikadEphemeris'
DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache"
CACHE_VERSION = 0.1
POS_FIX_RESIDUAL_THRESHOLD = 100.0
@ -42,7 +44,7 @@ class Laikad:
valid_ephem_types: Valid ephemeris types to be used by AstroDog
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.auto_fetch_orbits = auto_fetch_orbits
@ -77,8 +79,8 @@ class Laikad:
cloudlog.exception("Error parsing cache")
timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan'
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"Total: {sum([len(v) for v in cache['orbits']])} and {sum([len(v) for v in cache['nav']])}")
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"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):
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:
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)
if len(pos_fix) > 0 and np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD:
self.last_pos_fix = pos_fix[:3]
self.last_pos_residual = pos_fix_residual
if len(pos_fix) > 0:
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
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]
processed_measurements = process_measurements(new_meas, self.astro_dog)
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 []
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)
kf_valid = all(self.kf_valid(t))
@ -183,7 +191,7 @@ class Laikad:
def fetch_orbits(self, t: GPSTime, block):
# 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):
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
if block: # Used for testing purposes
@ -203,15 +211,17 @@ class Laikad:
self.cache_ephemeris(t=t)
def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types):
astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=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, cache_dir=cache_dir)
cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}")
start_time = time.monotonic()
try:
astro_dog.get_orbit_data(t, only_predictions=True)
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
except (RuntimeError, ValueError, IOError) as e:
except (DownloadFailed, RuntimeError, ValueError, IOError) as e:
cloudlog.warning(f"No orbit data found or parsing failure: {e}")
return None, None, t
@ -301,6 +311,7 @@ def main(sm=None, pm=None):
replay = "REPLAY" in os.environ
use_internet = "LAIKAD_NO_INTERNET" not in os.environ
laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet)
while True:
sm.update()

@ -8,6 +8,7 @@ from unittest.mock import Mock, patch
from common.params import Params
from laika.constants import SECS_IN_DAY
from laika.downloader import DownloadFailed
from laika.ephemeris import EphemerisType, GPSEphemeris
from laika.gps_time import GPSTime
from laika.helpers import ConstellationId, TimeRangeHolder
@ -51,6 +52,9 @@ def get_measurement_mock(gpstime, sat_ephemeris):
return meas
GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC = GPSTime.from_datetime(datetime(2022, month=1, day=29, hour=12))
class TestLaikad(unittest.TestCase):
@classmethod
@ -109,7 +113,7 @@ class TestLaikad(unittest.TestCase):
data_mock = defaultdict(str)
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.fetch_orbits(gpstime, block=True)
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')
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)
correct_msgs = verify_messages(self.logs, laikad)
self.assertEqual(16, len(correct_msgs))

@ -46,11 +46,25 @@ def remove_ignored_fields(msg, ignore):
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:
ignore_fields = []
if ignore_msgs is None:
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))
@ -72,7 +86,6 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non
msg1_dict = msg1.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)
# 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):
try:
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]
finite = math.isfinite(a) and math.isfinite(b)
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:
pass
return True

@ -14,6 +14,7 @@ from cereal import car, log
from cereal.services import service_list
from common.params import Params
from common.timeout import Timeout
from common.realtime import DT_CTRL
from panda.python import ALTERNATIVE_EXPERIENCE
from selfdrive.car.car_helpers import get_car, interfaces
from selfdrive.test.process_replay.helpers import OpenpilotPrefix
@ -27,7 +28,7 @@ TIMEOUT = 15
PROC_REPLAY_DIR = os.path.dirname(os.path.abspath(__file__))
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):
@ -548,11 +549,17 @@ def cpp_replay_process(cfg, lr, fingerprint=None):
def check_enabled(msgs):
cur_enabled_count = 0
max_enabled_count = 0
for msg in msgs:
if msg.which() == "carParams":
if msg.carParams.notCar:
return True
elif msg.which() == "controlsState":
if msg.controlsState.active:
return True
return False
cur_enabled_count += 1
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)
# check to make sure openpilot is engaged in the route
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

@ -18,14 +18,14 @@ from tools.lib.logreader import LogReader
original_segments = [
("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY
("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)
("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR)
("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2
("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC)
("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH)
("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
("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT
("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL
@ -38,17 +38,18 @@ original_segments = [
segments = [
("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"),
("TOYOTA2", "regenDEDB1D9C991|2022-07-06--14-54-08--0"),
("TOYOTA3", "regenDDC1FE60734|2022-07-06--14-32-06--0"),
("HONDA", "regen17B09D158B8|2022-07-06--14-31-46--0"),
("HONDA2", "regen041739C3E9A|2022-07-06--15-08-02--0"),
("CHRYSLER", "regenBB2F9C1425C|2022-07-06--14-31-41--0"),
("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--2"),
("SUBARU", "regen732B69F33B1|2022-07-06--14-36-18--0"),
("HONDA", "regenE62960EEC38|2022-07-14--19-33-24--0"),
("HONDA2", "regenC3EBD92F029|2022-07-14--19-29-47--0"),
("CHRYSLER", "regen38346FB33D0|2022-07-14--18-05-26--0"),
("RAM", "2f4452b03ccb98f0|2022-07-07--08-01-56--3"),
("SUBARU", "regen54A1E2BE5AA|2022-07-14--18-07-50--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"),
("MAZDA", "regen61BA413D53B|2022-07-06--14-39-42--0"),
]
@ -65,7 +66,7 @@ def run_test_process(data):
res = None
if not args.upload_only:
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_log(cur_log_fn, log_msgs)
@ -83,7 +84,7 @@ def get_log_data(segment):
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:
ignore_fields = []
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
if cfg.proc_name == "controlsd":
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:
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:
return str(e), log_msgs
@ -216,7 +217,7 @@ if __name__ == "__main__":
results: Any = defaultdict(dict)
p2 = pool.map(run_test_process, 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
diff1, diff2, failed = format_diff(results, ref_commit)

@ -526,6 +526,10 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct
fn += "turn_straight";
}
if (!active) {
fn += "_inactive";
}
auto icon = new QLabel;
int wh = active ? 125 : 75;
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));
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->setAlignment(Qt::AlignCenter);

@ -84,7 +84,7 @@ void Networking::connectToNetwork(const Network &n) {
} else if (n.security_type == SecurityType::OPEN) {
wifi->connect(n);
} 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()) {
wifi->connect(n, pass);
}
@ -94,7 +94,7 @@ void Networking::connectToNetwork(const Network &n) {
void Networking::wrongPassword(const QString &ssid) {
if (wifi->seenNetworks.contains(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()) {
wifi->connect(n, pass);
}
@ -174,7 +174,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid
list->addItem(editApnButton);
// 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->addStretch(1);
@ -296,7 +296,7 @@ void WifiUI::refresh() {
QPushButton *forgetBtn = new QPushButton(tr("FORGET"));
forgetBtn->setObjectName("forgetBtn");
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);
}
});

@ -102,7 +102,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
// offroad-only buttons
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(); });
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, [&]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) {
params.putBool("DoUninstall", true);

@ -165,7 +165,7 @@ void InputDialog::handleEnter() {
done(QDialog::Accepted);
emitText(line->text());
} 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);
vlayout->addWidget(title);
QLabel *instructions = new QLabel(tr(R"(
QLabel *instructions = new QLabel(QString(R"(
<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;'>Click "add new device" and scan the QR code on the right</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;'>%1</li>
<li style='margin-bottom: 50px;'>%2</li>
<li style='margin-bottom: 50px;'>%3</li>
</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->setWordWrap(true);
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>
<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>
<translation>()</translation>
<source>Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)</source>
<translation>()</translation>
</message>
<message>
<location filename="../qt/offroad/settings.cc" line="108"/>

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

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

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

@ -10,7 +10,7 @@ TRANSLATIONS_DIR = os.path.join(UI_DIR, "translations")
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:
translation_files = json.load(f)
@ -20,7 +20,10 @@ def update_translations(release=False, translations_dir=TRANSLATIONS_DIR):
continue
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
if release:
@ -32,6 +35,7 @@ if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Update translation files for UI",
formatter_class=argparse.ArgumentDefaultsHelpFormatter)
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()
update_translations(args.release)
update_translations(args.release, args.vanish)

@ -10,7 +10,6 @@ if arch == "larch64":
env.Program('camerad', [
'main.cc',
'cameras/camera_common.cc',
'transforms/rgb_to_yuv.cc',
'imgproc/utils.cc',
cameras,
], LIBS=libs)
@ -19,5 +18,4 @@ if GetOption("test"):
env.Program('test/ae_gray_test', [
'test/ae_gray_test.cc',
'cameras/camera_common.cc',
'transforms/rgb_to_yuv.cc',
], 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) {
char args[4096];
const CameraInfo *ci = &s->ci;
hdr_ = ci->hdr;
snprintf(args, sizeof(args),
"-cl-fast-relaxed-math -cl-denorms-are-zero "
"-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 "
"-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,
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";
cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args);
krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err));
@ -63,12 +62,10 @@ public:
private:
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;
this->rgb_type = init_rgb_type;
this->yuv_type = init_yuv_type;
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_height = ci->frame_height;
yuv_transform = get_model_yuv_transform(ci->bayer);
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);
yuv_transform = get_model_yuv_transform();
int nv12_width = VENUS_Y_STRIDE(COLOR_FMT_NV12, rgb_width);
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);
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);
}
rgb2yuv = std::make_unique<Rgb2Yuv>(context, device_id, rgb_width, rgb_height, rgb_stride);
debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset);
#ifdef __APPLE__
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_rgb_buf = vipc_server->get_buffer(rgb_type);
cur_yuv_buf = vipc_server->get_buffer(yuv_type);
cl_mem camrabuf_cl = camera_bufs[cur_buf_idx].buf_cl;
cl_event event;
@ -144,12 +133,7 @@ bool CameraBuf::acquire() {
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);
} else {
assert(rgb_stride == camera_state->ci.frame_stride);
rgb2yuv->queue(q, camrabuf_cl, cur_rgb_buf->buf_cl);
}
debayer->queue(q, camrabuf_cl, cur_yuv_buf->buf_cl, rgb_width, rgb_height, &event);
clWaitForEvents(1, &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);
}
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) {
const uint8_t *dat = (const uint8_t *)b->cur_camera_buf->addr;

@ -9,7 +9,6 @@
#include "cereal/visionipc/visionbuf.h"
#include "cereal/visionipc/visionipc.h"
#include "cereal/visionipc/visionipc_server.h"
#include "system/camerad/transforms/rgb_to_yuv.h"
#include "common/mat.h"
#include "common/queue.h"
#include "common/swaglog.h"
@ -27,7 +26,6 @@
#define CAMERA_ID_IMX390 9
#define CAMERA_ID_MAX 10
const int UI_BUF_COUNT = 4;
const int YUV_BUFFER_COUNT = 40;
enum CameraType {
@ -36,11 +34,6 @@ enum CameraType {
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
const bool env_disable_road = getenv("DISABLE_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 {
uint32_t frame_width, frame_height;
uint32_t frame_stride;
bool bayer;
int bayer_flip;
bool hdr;
uint32_t frame_offset = 0;
uint32_t extra_height = 0;
int registers_offset = -1;
@ -92,9 +82,8 @@ private:
VisionIpcServer *vipc_server;
CameraState *camera_state;
Debayer *debayer = nullptr;
std::unique_ptr<Rgb2Yuv> rgb2yuv;
VisionStreamType rgb_type, yuv_type;
VisionStreamType yuv_type;
int cur_buf_idx;
@ -116,7 +105,7 @@ public:
CameraBuf() = default;
~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();
void release();
void queue(size_t buf_idx);
@ -125,7 +114,6 @@ public:
typedef void (*process_thread_cb)(MultiCameraState *s, CameraState *c, int cnt);
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);
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);

@ -46,19 +46,11 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = {
.registers_offset = 0,
.frame_offset = AR0231_REGISTERS_HEIGHT,
.stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT,
.bayer = true,
.bayer_flip = 1,
.hdr = false,
},
[CAMERA_ID_IMX390] = {
.frame_width = FRAME_WIDTH,
.frame_height = FRAME_HEIGHT,
.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 *******************
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_;
camera_id = camera_id_;
camera_num = camera_num_;
@ -638,7 +630,7 @@ void CameraState::camera_init(MultiCameraState *multi_cam_state_, VisionIpcServe
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;
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() {
@ -833,9 +825,9 @@ void CameraState::camera_open() {
}
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->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->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->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_ROAD, !env_disable_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"});
}
@ -1233,9 +1225,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt)
auto framed = msg.initEvent().initDriverCameraState();
framed.setFrameType(cereal::FrameData::FrameType::FRONT);
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) {
ar0231_process_registers(s, c, framed);
}
@ -1248,9 +1238,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) {
MessageBuilder msg;
auto framed = c == &s->road_cam ? msg.initEvent().initRoadCameraState() : msg.initEvent().initWideRoadCameraState();
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)) {
framed.setImage(get_frame_image(b));
} else if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation
if (env_log_raw_frames && c == &s->road_cam && cnt % 100 == 5) { // no overlap with qlog decimation
framed.setImage(get_raw_frame_image(b));
}
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 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();
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()}'")
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']}")
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)
if 'casync_caibx' in partition:
if ('casync_caibx' in partition) and not standalone:
extract_casync_image(target_slot_number, partition, cloudlog)
else:
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}")
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))
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):
try:
flash_partition(target_slot_number, partition, cloudlog)
flash_partition(target_slot_number, partition, cloudlog, standalone)
success = True
break
@ -320,9 +320,9 @@ if __name__ == "__main__":
elif args.swap:
while not verify_agnos_update(args.manifest, target_slot_number):
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}")
swap(args.manifest, target_slot_number, logging)
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) {
FileReader f(local_cache, chunk_size, retries);
std::string data = f.read(url, abort);
if (data.empty()) return false;
raw_ = FileReader(local_cache, chunk_size, retries).read(url, abort);
if (raw_.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) {
raw_ = decompressBZ2(data, size, abort);
if (raw_.empty()) {
if (!(abort && *abort)) {
rWarning("failed to decompress log");
}
return false;
}
raw_.assign((const char *)data, size);
return parse(abort);
}
bool LogReader::parse(std::atomic<bool> *abort) {
try {
kj::ArrayPtr<const capnp::word> words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word));
while (words.size() > 0 && !(abort && *abort)) {

@ -52,10 +52,10 @@ public:
~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::byte *data, size_t size, std::atomic<bool> *abort = nullptr);
std::vector<Event*> events;
private:
bool parse(std::atomic<bool> *abort);
std::string raw_;
#ifdef HAS_MEMORY_RESOURCE
std::pmr::monotonic_buffer_resource *mbr_ = nullptr;

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

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

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

Loading…
Cancel
Save