Merge branch 'master' of https://github.com/commaai/openpilot into pq-upstream

pull/24768/head
Jason Young 3 years ago
commit 7a57a67a0f
  1. 51
      .github/workflows/selfdrive_tests.yaml
  2. 9
      .github/workflows/tools_tests.yaml
  3. 2
      .gitignore
  4. 1
      Dockerfile.openpilot
  5. 37
      Dockerfile.openpilot_base_cl
  6. 4
      README.md
  7. 1
      RELEASES.md
  8. 20
      SConstruct
  9. 2
      cereal
  10. 2
      common/modeldata.h
  11. 1
      common/params.cc
  12. 9
      common/realtime.py
  13. 6
      common/swaglog.cc
  14. 5
      common/tests/test_swaglog.cc
  15. 12
      common/transformations/camera.py
  16. 84
      docs/CARS.md
  17. 2
      docs/c_docs.rst
  18. 1
      docs/docker/Dockerfile
  19. 2
      laika_repo
  20. 6
      launch_chffrplus.sh
  21. 2
      launch_env.sh
  22. 2
      opendbc
  23. 2
      panda
  24. 18
      release/files_common
  25. 6
      selfdrive/athena/athenad.py
  26. 21
      selfdrive/boardd/boardd.cc
  27. 2
      selfdrive/boardd/main.cc
  28. 6
      selfdrive/camerad/cameras/camera_common.cc
  29. 2
      selfdrive/camerad/main.cc
  30. 22
      selfdrive/camerad/snapshot/snapshot.py
  31. 7
      selfdrive/car/__init__.py
  32. 1
      selfdrive/car/body/interface.py
  33. 2
      selfdrive/car/body/values.py
  34. 21
      selfdrive/car/car_helpers.py
  35. 8
      selfdrive/car/chrysler/interface.py
  36. 28
      selfdrive/car/docs.py
  37. 11
      selfdrive/car/docs_definitions.py
  38. 40
      selfdrive/car/fingerprints.py
  39. 21
      selfdrive/car/fw_versions.py
  40. 2
      selfdrive/car/gm/interface.py
  41. 15
      selfdrive/car/honda/interface.py
  42. 12
      selfdrive/car/hyundai/interface.py
  43. 35
      selfdrive/car/interfaces.py
  44. 2
      selfdrive/car/isotp_parallel_query.py
  45. 2
      selfdrive/car/subaru/interface.py
  46. 6
      selfdrive/car/subaru/values.py
  47. 2
      selfdrive/car/tests/routes.py
  48. 2
      selfdrive/car/tests/test_docs.py
  49. 21
      selfdrive/car/tests/test_fw_fingerprint.py
  50. 24
      selfdrive/car/tests/test_models.py
  51. 30
      selfdrive/car/toyota/interface.py
  52. 2
      selfdrive/car/toyota/tunes.py
  53. 1
      selfdrive/car/toyota/values.py
  54. 6
      selfdrive/car/volkswagen/interface.py
  55. 6
      selfdrive/car/volkswagen/values.py
  56. 30
      selfdrive/controls/controlsd.py
  57. 2
      selfdrive/controls/lib/events.py
  58. 10
      selfdrive/controls/lib/lane_planner.py
  59. 2
      selfdrive/controls/lib/latcontrol_torque.py
  60. 5
      selfdrive/controls/plannerd.py
  61. 3
      selfdrive/controls/radard.py
  62. 36
      selfdrive/debug/test_car_model.py
  63. 1
      selfdrive/hardware/__init__.py
  64. 6
      selfdrive/hardware/base.h
  65. 3
      selfdrive/hardware/hw.h
  66. 14
      selfdrive/hardware/tici/agnos.json
  67. 6
      selfdrive/hardware/tici/hardware.h
  68. 6
      selfdrive/hardware/tici/test_power_draw.py
  69. 75
      selfdrive/locationd/laikad.py
  70. 60
      selfdrive/locationd/test/test_laikad.py
  71. 2
      selfdrive/loggerd/bootlog.cc
  72. 2
      selfdrive/loggerd/encoderd.cc
  73. 7
      selfdrive/loggerd/logger.cc
  74. 4
      selfdrive/loggerd/loggerd.cc
  75. 12
      selfdrive/loggerd/loggerd.h
  76. 24
      selfdrive/loggerd/tests/test_loggerd.py
  77. 6
      selfdrive/manager/build.py
  78. 12
      selfdrive/manager/process_config.py
  79. 4
      selfdrive/manager/test/test_manager.py
  80. 2
      selfdrive/modeld/modeld.cc
  81. 60
      selfdrive/modeld/models/README.md
  82. 58
      selfdrive/modeld/models/dmonitoring.cc
  83. 11
      selfdrive/modeld/runners/onnx_runner.py
  84. 17
      selfdrive/monitoring/driver_monitor.py
  85. 14
      selfdrive/navd/helpers.py
  86. 41
      selfdrive/navd/navd.py
  87. 2
      selfdrive/test/process_replay/helpers.py
  88. 53
      selfdrive/test/process_replay/model_replay.py
  89. 7
      selfdrive/test/process_replay/process_replay.py
  90. 2
      selfdrive/test/process_replay/ref_commit
  91. 23
      selfdrive/test/process_replay/regen.py
  92. 46
      selfdrive/test/process_replay/regen_all.py
  93. 7
      selfdrive/test/process_replay/test_processes.py
  94. 10
      selfdrive/test/update_ci_routes.py
  95. 13
      selfdrive/thermald/thermald.py
  96. 4
      selfdrive/timezoned.py
  97. 5
      selfdrive/tombstoned.py
  98. 6
      selfdrive/ui/qt/offroad/driverview.cc
  99. 11
      selfdrive/ui/qt/offroad/settings.cc
  100. 29
      selfdrive/ui/qt/onroad.cc
  101. Some files were not shown because too many files have changed in this diff Show More

@ -7,6 +7,7 @@ on:
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl
DOCKER_REGISTRY: ghcr.io/commaai DOCKER_REGISTRY: ghcr.io/commaai
AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }}
@ -17,6 +18,13 @@ env:
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/scons_cache:/tmp/scons_cache -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c
BUILD_CL: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base_cl) || true
docker pull $DOCKER_REGISTRY/$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
UNIT_TEST: coverage run --append -m unittest discover UNIT_TEST: coverage run --append -m unittest discover
jobs: jobs:
@ -193,10 +201,13 @@ jobs:
submodules: true submodules: true
- name: Build Docker image - name: Build Docker image
run: eval "$BUILD" run: eval "$BUILD"
- name: Build CL Docker image
run: eval "$BUILD_CL"
- name: Push to container registry - name: Push to container registry
run: | run: |
$DOCKER_LOGIN $DOCKER_LOGIN
docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest
docker push $DOCKER_REGISTRY/$CL_BASE_IMAGE:latest
static_analysis: static_analysis:
name: static analysis name: static analysis
@ -295,7 +306,7 @@ jobs:
./common/tests/test_swaglog && \ ./common/tests/test_swaglog && \
./selfdrive/boardd/tests/test_boardd_usbprotocol && \ ./selfdrive/boardd/tests/test_boardd_usbprotocol && \
./selfdrive/loggerd/tests/test_logger &&\ ./selfdrive/loggerd/tests/test_logger &&\
./selfdrive/proclogd/tests/test_proclog && \ ./system/proclogd/tests/test_proclog && \
./selfdrive/ui/replay/tests/test_replay && \ ./selfdrive/ui/replay/tests/test_replay && \
./selfdrive/camerad/test/ae_gray_test && \ ./selfdrive/camerad/test/ae_gray_test && \
coverage xml" coverage xml"
@ -351,6 +362,44 @@ jobs:
CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only"
- name: "Upload coverage to Codecov" - name: "Upload coverage to Codecov"
uses: codecov/codecov-action@v2 uses: codecov/codecov-action@v2
model_replay_onnx:
name: model replay onnx
runs-on: ubuntu-20.04
timeout-minutes: 50
steps:
- uses: actions/checkout@v3
with:
submodules: true
- name: Pull LFS
run: git lfs pull
- name: Cache dependencies
id: dependency-cache
uses: actions/cache@v2
with:
path: /tmp/comma_download_cache
key: ${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/model_replay.py') }}
- 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
# Sim docker is needed to get the intel OPENCL drivers
run: eval "$BUILD_CL"
- name: Run replay
run: |
${{ env.RUN_CL }} "scons -j$(nproc) && \
ONNXCPU=1 FILEREADER_CACHE=1 CI=1 coverage run \
selfdrive/test/process_replay/model_replay.py -j$(nproc) && \
coverage xml"
test_longitudinal: test_longitudinal:
name: longitudinal name: longitudinal

@ -5,6 +5,7 @@ on:
env: env:
BASE_IMAGE: openpilot-base BASE_IMAGE: openpilot-base
CL_BASE_IMAGE: openpilot-base-cl
DOCKER_REGISTRY: ghcr.io/commaai DOCKER_REGISTRY: ghcr.io/commaai
DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u adeebshihadeh -p ${{ secrets.CONTAINER_TOKEN }}
@ -12,6 +13,10 @@ env:
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base) || true
docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true docker pull $DOCKER_REGISTRY/$BASE_IMAGE:latest || true
docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base . docker build --cache-from $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $DOCKER_REGISTRY/$BASE_IMAGE:latest -t $BASE_IMAGE:latest -f Dockerfile.openpilot_base .
BUILD_CL: |
docker pull $(grep -iohP '(?<=^from)\s+\S+' Dockerfile.openpilot_base_cl) || true
docker pull $DOCKER_REGISTRY/$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: docker run --shm-size 1G -v $PWD:/tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e \ RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e \
GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c GITHUB_REPOSITORY -e GITHUB_RUN_ID -v /tmp/comma_download_cache:/tmp/comma_download_cache $BASE_IMAGE /bin/sh -c
@ -49,8 +54,10 @@ jobs:
run: git lfs pull run: git lfs pull
- name: Build base image - name: Build base image
run: eval "$BUILD" run: eval "$BUILD"
- name: Build base cl image
run: eval "$BUILD_CL"
- name: Pull latest simulator image - name: Pull latest simulator image
run: eval "$BUILD"docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true run: docker pull $DOCKER_REGISTRY/$IMAGE_NAME:latest || true
- name: Build simulator image - name: Build simulator image
run: docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim . run: docker build --cache-from $DOCKER_REGISTRY/$IMAGE_NAME:latest -t $DOCKER_REGISTRY/$IMAGE_NAME:latest -f tools/sim/Dockerfile.sim .
- name: Push to container registry - name: Push to container registry

2
.gitignore vendored

@ -41,7 +41,7 @@ board/obj/
selfdrive/boardd/boardd selfdrive/boardd/boardd
selfdrive/logcatd/logcatd selfdrive/logcatd/logcatd
selfdrive/mapd/default_speeds_by_region.json selfdrive/mapd/default_speeds_by_region.json
selfdrive/proclogd/proclogd system/proclogd/proclogd
selfdrive/ui/_ui selfdrive/ui/_ui
selfdrive/test/longitudinal_maneuvers/out selfdrive/test/longitudinal_maneuvers/out
selfdrive/visiond/visiond selfdrive/visiond/visiond

@ -28,5 +28,6 @@ COPY ./opendbc ${OPENPILOT_PATH}/opendbc
COPY ./cereal ${OPENPILOT_PATH}/cereal COPY ./cereal ${OPENPILOT_PATH}/cereal
COPY ./panda ${OPENPILOT_PATH}/panda COPY ./panda ${OPENPILOT_PATH}/panda
COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive
COPY ./system ${OPENPILOT_PATH}/system
RUN scons -j$(nproc) RUN scons -j$(nproc)

@ -0,0 +1,37 @@
FROM ghcr.io/commaai/openpilot-base:latest
RUN apt-get update && apt-get install -y --no-install-recommends\
apt-utils \
alien \
unzip \
tar \
curl \
xz-utils \
dbus \
gcc-arm-none-eabi \
tmux \
vim \
lsb-core \
libx11-6 \
&& rm -rf /var/lib/apt/lists/*
# Intel OpenCL driver
ARG INTEL_DRIVER=l_opencl_p_18.1.0.015.tgz
ARG INTEL_DRIVER_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532
RUN mkdir -p /tmp/opencl-driver-intel
WORKDIR /tmp/opencl-driver-intel
RUN echo INTEL_DRIVER is $INTEL_DRIVER && \
curl -O $INTEL_DRIVER_URL/$INTEL_DRIVER && \
tar -xzf $INTEL_DRIVER && \
for i in $(basename $INTEL_DRIVER .tgz)/rpm/*.rpm; do alien --to-deb $i; done && \
dpkg -i *.deb && \
rm -rf $INTEL_DRIVER $(basename $INTEL_DRIVER .tgz) *.deb && \
mkdir -p /etc/OpenCL/vendors && \
echo /opt/intel/opencl_compilers_and_libraries_18.1.0.015/linux/compiler/lib/intel64_lin/libintelocl.so > /etc/OpenCL/vendors/intel.icd && \
rm -rf /tmp/opencl-driver-intel
ENV NVIDIA_VISIBLE_DEVICES all
ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute
ENV QTWEBENGINE_DISABLE_SANDBOX 1
RUN dbus-uuidgen > /etc/machine-id

@ -102,6 +102,9 @@ Directory Structure
├── panda # Code used to communicate on CAN ├── panda # Code used to communicate on CAN
├── third_party # External libraries ├── third_party # External libraries
├── pyextra # Extra python packages ├── pyextra # Extra python packages
└── system # Generic services
├── logcatd # systemd journal as a service
└── proclogd # Logs information from /proc
└── selfdrive # Code needed to drive the car └── selfdrive # Code needed to drive the car
├── assets # Fonts, images, and sounds for UI ├── assets # Fonts, images, and sounds for UI
├── athena # Allows communication with the app ├── athena # Allows communication with the app
@ -112,7 +115,6 @@ Directory Structure
├── controls # Planning and controls ├── controls # Planning and controls
├── debug # Tools to help you debug and do car ports ├── debug # Tools to help you debug and do car ports
├── locationd # Precise localization and vehicle parameter estimation ├── locationd # Precise localization and vehicle parameter estimation
├── logcatd # Android logcat as a service
├── loggerd # Logger and uploader of car data ├── loggerd # Logger and uploader of car data
├── modeld # Driving and monitoring model runners ├── modeld # Driving and monitoring model runners
├── proclogd # Logs information from proc ├── proclogd # Logs information from proc

@ -5,6 +5,7 @@ Version 0.8.15 (2022-XX-XX)
* Much smoother control, consistent across the speed range * Much smoother control, consistent across the speed range
* Effective feedforward that uses road roll * Effective feedforward that uses road roll
* Simplified tuning, all car-specific parameters can be derived from data * Simplified tuning, all car-specific parameters can be derived from data
* AGNOS 5
Version 0.8.14 (2022-06-01) Version 0.8.14 (2022-06-01)
======================== ========================

@ -1,5 +1,4 @@
import os import os
import shutil
import subprocess import subprocess
import sys import sys
import sysconfig import sysconfig
@ -7,6 +6,8 @@ import platform
import numpy as np import numpy as np
TICI = os.path.isfile('/TICI') TICI = os.path.isfile('/TICI')
AGNOS = TICI
Decider('MD5-timestamp') Decider('MD5-timestamp')
AddOption('--test', AddOption('--test',
@ -56,7 +57,7 @@ real_arch = arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rst
if platform.system() == "Darwin": if platform.system() == "Darwin":
arch = "Darwin" arch = "Darwin"
if arch == "aarch64" and TICI: if arch == "aarch64" and AGNOS:
arch = "larch64" arch = "larch64"
USE_WEBCAM = os.getenv("USE_WEBCAM") is not None USE_WEBCAM = os.getenv("USE_WEBCAM") is not None
@ -226,7 +227,7 @@ if GetOption('compile_db'):
env.CompilationDatabase('compile_commands.json') env.CompilationDatabase('compile_commands.json')
# Setup cache dir # Setup cache dir
cache_dir = '/data/scons_cache' if TICI else '/tmp/scons_cache' cache_dir = '/data/scons_cache' if AGNOS else '/tmp/scons_cache'
CacheDir(cache_dir) CacheDir(cache_dir)
Clean(["."], cache_dir) Clean(["."], cache_dir)
@ -376,6 +377,14 @@ if arch != "larch64":
Export('rednose_config') Export('rednose_config')
SConscript(['rednose/SConscript']) SConscript(['rednose/SConscript'])
# Build system services
SConscript([
'system/clocksd/SConscript',
'system/proclogd/SConscript',
])
if arch != "Darwin":
SConscript(['system/logcatd/SConscript'])
# Build openpilot # Build openpilot
SConscript(['cereal/SConscript']) SConscript(['cereal/SConscript'])
@ -395,8 +404,6 @@ SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript'])
SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript'])
SConscript(['selfdrive/boardd/SConscript']) SConscript(['selfdrive/boardd/SConscript'])
SConscript(['selfdrive/proclogd/SConscript'])
SConscript(['selfdrive/clocksd/SConscript'])
SConscript(['selfdrive/loggerd/SConscript']) SConscript(['selfdrive/loggerd/SConscript'])
@ -404,9 +411,6 @@ SConscript(['selfdrive/locationd/SConscript'])
SConscript(['selfdrive/sensord/SConscript']) SConscript(['selfdrive/sensord/SConscript'])
SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/ui/SConscript'])
if arch != "Darwin":
SConscript(['selfdrive/logcatd/SConscript'])
if GetOption('test'): if GetOption('test'):
SConscript('panda/tests/safety/SConscript') SConscript('panda/tests/safety/SConscript')

@ -1 +1 @@
Subproject commit bf4960f83ccdefc7e9d2e405878ae6c8efced83b Subproject commit 7cbb4f1c8cb7cfc798a058c611801442b40feb52

@ -24,8 +24,6 @@ constexpr auto T_IDXS_FLOAT = build_idxs<float, TRAJECTORY_SIZE>(10.0);
constexpr auto X_IDXS = build_idxs<double, TRAJECTORY_SIZE>(192.0); constexpr auto X_IDXS = build_idxs<double, TRAJECTORY_SIZE>(192.0);
constexpr auto X_IDXS_FLOAT = build_idxs<float, TRAJECTORY_SIZE>(192.0); constexpr auto X_IDXS_FLOAT = build_idxs<float, TRAJECTORY_SIZE>(192.0);
const int TICI_CAM_WIDTH = 1928;
namespace tici_dm_crop { namespace tici_dm_crop {
const int x_offset = -72; const int x_offset = -72;
const int y_offset = -144; const int y_offset = -144;

@ -104,7 +104,6 @@ std::unordered_map<std::string, uint32_t> keys = {
{"DoReboot", CLEAR_ON_MANAGER_START}, {"DoReboot", CLEAR_ON_MANAGER_START},
{"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START},
{"DoUninstall", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START},
{"EndToEndToggle", PERSISTENT},
{"ForcePowerDown", CLEAR_ON_MANAGER_START}, {"ForcePowerDown", CLEAR_ON_MANAGER_START},
{"GitBranch", PERSISTENT}, {"GitBranch", PERSISTENT},
{"GitCommit", PERSISTENT}, {"GitCommit", PERSISTENT},

@ -8,19 +8,14 @@ from typing import Optional, List, Union
from setproctitle import getproctitle # pylint: disable=no-name-in-module from setproctitle import getproctitle # pylint: disable=no-name-in-module
from common.clock import sec_since_boot # pylint: disable=no-name-in-module, import-error from common.clock import sec_since_boot # pylint: disable=no-name-in-module, import-error
from selfdrive.hardware import PC, TICI from selfdrive.hardware import PC
# time step for each process # time step for each process
DT_CTRL = 0.01 # controlsd DT_CTRL = 0.01 # controlsd
DT_MDL = 0.05 # model DT_MDL = 0.05 # model
DT_TRML = 0.5 # thermald and manager DT_TRML = 0.5 # thermald and manager
DT_DMON = 0.05 # driver monitoring
# driver monitoring
if TICI:
DT_DMON = 0.05
else:
DT_DMON = 0.1
class Priority: class Priority:

@ -50,11 +50,7 @@ class SwaglogState : public LogState {
ctx_j["dirty"] = !getenv("CLEAN"); ctx_j["dirty"] = !getenv("CLEAN");
// device type // device type
if (Hardware::TICI()) { ctx_j["device"] = Hardware::get_name();
ctx_j["device"] = "tici";
} else {
ctx_j["device"] = "pc";
}
LogState::initialize(); LogState::initialize();
} }
}; };

@ -56,10 +56,7 @@ void recv_log(int thread_cnt, int thread_msg_cnt) {
REQUIRE(ctx["version"].string_value() == COMMA_VERSION); REQUIRE(ctx["version"].string_value() == COMMA_VERSION);
std::string device = "pc"; std::string device = Hardware::get_name();
if (Hardware::TICI()) {
device = "tici";
}
REQUIRE(ctx["device"].string_value() == device); REQUIRE(ctx["device"].string_value() == device);
int thread_id = atoi(msg["msg"].string_value().c_str()); int thread_id = atoi(msg["msg"].string_value().c_str());

@ -1,7 +1,6 @@
import numpy as np import numpy as np
import common.transformations.orientation as orient import common.transformations.orientation as orient
from selfdrive.hardware import TICI
## -- hardcoded hardware params -- ## -- hardcoded hardware params --
eon_f_focal_length = 910.0 eon_f_focal_length = 910.0
@ -45,14 +44,9 @@ tici_fcam_intrinsics_inv = np.linalg.inv(tici_fcam_intrinsics)
tici_ecam_intrinsics_inv = np.linalg.inv(tici_ecam_intrinsics) tici_ecam_intrinsics_inv = np.linalg.inv(tici_ecam_intrinsics)
if not TICI: FULL_FRAME_SIZE = tici_f_frame_size
FULL_FRAME_SIZE = eon_f_frame_size FOCAL = tici_f_focal_length
FOCAL = eon_f_focal_length fcam_intrinsics = tici_fcam_intrinsics
fcam_intrinsics = eon_fcam_intrinsics
else:
FULL_FRAME_SIZE = tici_f_frame_size
FOCAL = tici_f_focal_length
fcam_intrinsics = tici_fcam_intrinsics
W, H = FULL_FRAME_SIZE[0], FULL_FRAME_SIZE[1] W, H = FULL_FRAME_SIZE[0], FULL_FRAME_SIZE[1]

@ -51,13 +51,7 @@ How We Rate The Cars
|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-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 Hybrid 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|ES Hybrid 2019-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX 2020|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX 2020|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RX 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|RX 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|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-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|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-full.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-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 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry Hybrid 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry Hybrid 2021-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Corolla 2020-22|All|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -75,12 +69,7 @@ How We Rate The Cars
|Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained| |Make|Model|Supported Package|openpilot ACC|Stop and Go|Steer to 0|Steering Torque|Actively Maintained|
|---|---|---|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|
|Audi|A3 2014-19|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|A3 Sportback e-tron 2017-18|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|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-full.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-full.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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|RS3 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|S3 2015-17|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Genesis|G70 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Genesis|G70 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Genesis|G80 2018|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Genesis|G80 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>|
|Hyundai|Elantra 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Hyundai|Elantra 2021-22|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -94,7 +83,6 @@ How We Rate The Cars
|Hyundai|Santa Fe 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-full.svg" width="22" /></a>|<a href="##"><img valign="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|Santa Fe 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-full.svg" width="22" /></a>|<a href="##"><img valign="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|Santa Fe 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>| |Hyundai|Santa Fe 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>|
|Hyundai|Santa Fe Plug-in 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>| |Hyundai|Santa Fe Plug-in 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>|
|Hyundai|Sonata 2018-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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Hyundai|Tucson Diesel 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>| |Hyundai|Tucson Diesel 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|Ceed 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|Ceed 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|Forte 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|Forte 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>|
@ -107,18 +95,15 @@ How We Rate The Cars
|Kia|Sorento 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Sorento 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Sorento 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Sorento 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Stinger 2018|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Stinger 2018|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|ES Hybrid 2017-18|LSS|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|NX Hybrid 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|NX Hybrid 2018-19|All|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|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-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-empty.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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|RX 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-empty.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-full.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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|SEAT|Ateca 2018|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |SEAT|Ateca 2018|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|SEAT|Leon 2014-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |SEAT|Leon 2014-20|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Forester 2019-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|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Impreza 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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Škoda|Kamiq 2021[<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>| |Škoda|Kamiq 2021[<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>|
|Škoda|Karoq 2019|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>| |Škoda|Karoq 2019|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>|
|Škoda|Kodiaq 2018-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>| |Škoda|Kodiaq 2018-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>|
@ -126,36 +111,20 @@ How We Rate The Cars
|Škoda|Octavia RS 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>| |Škoda|Octavia RS 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>|
|Škoda|Scala 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>| |Škoda|Scala 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>|
|Škoda|Superb 2015-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>| |Škoda|Superb 2015-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>|
|Toyota|Avalon 2019-21|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|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon Hybrid 2019-21|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|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|C-HR 2017-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>| |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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|C-HR Hybrid 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>| |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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Camry Hybrid 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Camry Hybrid 2018-20|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>[<sup>4</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|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|RAV4 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 Hybrid 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 Hybrid 2022|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Arteon 2018, 2021[<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|Arteon 2018, 2021[<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|Atlas 2018-19, 2022[<sup>8</sup>](#footnotes)|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Passat 2015-19[<sup>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|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-full.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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Passat 2015-18[<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|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Volkswagen|Polo 2020|Driver Assistance|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|T-Cross 2021[<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|T-Cross 2021[<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|T-Roc 2021[<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|T-Roc 2021[<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|Taos 2022[<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|Taos 2022[<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|Tiguan 2019-22[<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|Touran 2017|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|Touran 2017|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 Cars ## Bronze Cars
@ -165,6 +134,11 @@ How We Rate The Cars
|Acura|ILX 2016-19|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|ILX 2016-19|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Acura|RDX 2016-18|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|RDX 2016-18|AcuraWatch Plus|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Acura|RDX 2019-21|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Acura|RDX 2019-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>|
|Audi|A3 2014-19|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|A3 Sportback e-tron 2017-18|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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Audi|RS3 2018|ACC + Lane Assist|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>| |Cadillac|Escalade ESV 2016[<sup>1</sup>](#footnotes)|ACC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Chrysler|Pacifica 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Chrysler|Pacifica 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
@ -196,29 +170,55 @@ How We Rate The Cars
|Hyundai|Ioniq Electric 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-empty.svg" width="22" /></a>|<a href="##"><img valign="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 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-empty.svg" width="22" /></a>|<a href="##"><img valign="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 Hybrid 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-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>| |Hyundai|Ioniq Hybrid 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-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>|
|Hyundai|Ioniq Plug-in Hybrid 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-empty.svg" width="22" /></a>|<a href="##"><img valign="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 Plug-in Hybrid 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-empty.svg" width="22" /></a>|<a href="##"><img valign="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|Sonata 2018-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-full.svg" width="22" /></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>|
|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-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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.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-empty.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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Niro Plug-in Hybrid 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Niro Plug-in Hybrid 2019|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Kia|Optima 2017|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Kia|Optima 2017|SCC + LKAS|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Lexus|IS 2017-19|All|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Lexus|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-empty.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|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-empty.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-empty.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-empty.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-empty.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-full.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-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Nissan|Altima 2019-20|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Nissan|Altima 2019-20|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Nissan|Leaf 2018-22|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Nissan|Leaf 2018-22|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Nissan|Rogue 2018-20|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Nissan|Rogue 2018-20|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Nissan|X-Trail 2017|ProPILOT|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Nissan|X-Trail 2017|ProPILOT|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Ascent 2019-20|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|Ascent 2019-20|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Crosstrek 2018-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Impreza 2017-19|EyeSight|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Subaru|Impreza 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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon 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|Avalon 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon 2019-21|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Avalon Hybrid 2019-21|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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|C-HR 2017-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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|C-HR Hybrid 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Corolla 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="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 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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="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-empty.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-empty.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-half.svg" width="22" /></a>[<sup>5</sup>](#footnotes)|<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-half.svg" width="22" /></a>[<sup>5</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Prius Prime 2017-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>[<sup>5</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Prius Prime 2017-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>[<sup>5</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|Prius v 2017|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>5</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|Prius v 2017|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>5</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Toyota|RAV4 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>| |Toyota|RAV4 2016-18|TSS-P|<a href="##"><img valign="top" src="assets/icon-star-half.svg" width="22" /></a>[<sup>3</sup>](#footnotes)|<a href="##"><img valign="top" src="assets/icon-star-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-empty.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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Atlas 2018-19, 2022[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|California 2021[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="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|California 2021[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="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|Caravelle 2020[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="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|Caravelle 2020[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="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-empty.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-empty.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-empty.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-empty.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-empty.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-empty.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-empty.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-empty.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-empty.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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
|Volkswagen|Tiguan 2019-22[<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-empty.svg" width="22" /></a>|<a href="##"><img valign="top" src="assets/icon-star-full.svg" width="22" /></a>|
<a id="footnotes"></a> <a id="footnotes"></a>

@ -66,7 +66,7 @@ qt
proclogd proclogd
^^^^^^^^ ^^^^^^^^
.. autodoxygenindex:: .. autodoxygenindex::
:project: selfdrive_proclogd :project: system_proclogd
modeld modeld
^^^^^^ ^^^^^^

@ -30,6 +30,7 @@ COPY ./opendbc ${OPENPILOT_PATH}/opendbc
COPY ./cereal ${OPENPILOT_PATH}/cereal COPY ./cereal ${OPENPILOT_PATH}/cereal
COPY ./panda ${OPENPILOT_PATH}/panda COPY ./panda ${OPENPILOT_PATH}/panda
COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive COPY ./selfdrive ${OPENPILOT_PATH}/selfdrive
COPY ./system ${OPENPILOT_PATH}/system
COPY ./*.md ${OPENPILOT_PATH}/ COPY ./*.md ${OPENPILOT_PATH}/
RUN scons -j$(nproc) RUN scons -j$(nproc)

@ -1 +1 @@
Subproject commit 231eafbf659309b85acb5b575b7f898e7a4f196e Subproject commit ba6ed3277cadfdc8697206784afbd7f9a223798b

@ -8,7 +8,7 @@ source "$BASEDIR/launch_env.sh"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
function tici_init { function agnos_init {
# wait longer for weston to come up # wait longer for weston to come up
if [ -f "$BASEDIR/prebuilt" ]; then if [ -f "$BASEDIR/prebuilt" ]; then
sleep 3 sleep 3
@ -77,9 +77,7 @@ function launch {
export PYTHONPATH="$PWD:$PWD/pyextra" export PYTHONPATH="$PWD:$PWD/pyextra"
# hardware specific init # hardware specific init
if [ -f /TICI ]; then agnos_init
tici_init
fi
# write tmux scrollback to a file # write tmux scrollback to a file
tmux capture-pane -pq -S-1000 > /tmp/launch_log tmux capture-pane -pq -S-1000 > /tmp/launch_log

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

@ -1 +1 @@
Subproject commit f107e767bda05b2101c88275646fffe33ad6dec0 Subproject commit 8a5292158d24aa5892ceeda3edde28633a968537

@ -1 +1 @@
Subproject commit 6d19b46ef525b0ae224e0bf7db71918e96f9be66 Subproject commit 475a9a312410908abcaa32f2e48140fcbfc2362f

@ -119,9 +119,9 @@ selfdrive/car/tesla/*.py
selfdrive/car/toyota/*.py selfdrive/car/toyota/*.py
selfdrive/car/volkswagen/*.py selfdrive/car/volkswagen/*.py
selfdrive/clocksd/.gitignore system/clocksd/.gitignore
selfdrive/clocksd/SConscript system/clocksd/SConscript
selfdrive/clocksd/clocksd.cc system/clocksd/clocksd.cc
selfdrive/debug/*.py selfdrive/debug/*.py
@ -220,13 +220,13 @@ selfdrive/locationd/models/live_kf.cc
selfdrive/locationd/calibrationd.py selfdrive/locationd/calibrationd.py
selfdrive/logcatd/SConscript system/logcatd/SConscript
selfdrive/logcatd/logcatd_systemd.cc system/logcatd/logcatd_systemd.cc
selfdrive/proclogd/SConscript system/proclogd/SConscript
selfdrive/proclogd/main.cc system/proclogd/main.cc
selfdrive/proclogd/proclog.cc system/proclogd/proclog.cc
selfdrive/proclogd/proclog.h system/proclogd/proclog.h
selfdrive/loggerd/SConscript selfdrive/loggerd/SConscript
selfdrive/loggerd/encoder/encoder.cc selfdrive/loggerd/encoder/encoder.cc

@ -32,7 +32,7 @@ from common.basedir import PERSIST
from common.file_helpers import CallbackReader from common.file_helpers import CallbackReader
from common.params import Params from common.params import Params
from common.realtime import sec_since_boot, set_core_affinity from common.realtime import sec_since_boot, set_core_affinity
from selfdrive.hardware import HARDWARE, PC, TICI from selfdrive.hardware import HARDWARE, PC, AGNOS
from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.config import ROOT
from selfdrive.loggerd.xattr_cache import getxattr, setxattr from selfdrive.loggerd.xattr_cache import getxattr, setxattr
from selfdrive.statsd import STATS_DIR from selfdrive.statsd import STATS_DIR
@ -413,8 +413,8 @@ def primeActivated(activated):
@dispatcher.add_method @dispatcher.add_method
def setBandwithLimit(upload_speed_kbps, download_speed_kbps): def setBandwithLimit(upload_speed_kbps, download_speed_kbps):
if not TICI: if not AGNOS:
return {"success": 0, "error": "only supported on comma three"} return {"success": 0, "error": "only supported on AGNOS"}
try: try:
HARDWARE.set_bandwidth_limit(upload_speed_kbps, download_speed_kbps) HARDWARE.set_bandwidth_limit(upload_speed_kbps, download_speed_kbps)

@ -405,17 +405,12 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) {
auto ps = evt.initPeripheralState(); auto ps = evt.initPeripheralState();
ps.setPandaType(panda->hw_type); ps.setPandaType(panda->hw_type);
if (Hardware::TICI()) { double read_time = millis_since_boot();
double read_time = millis_since_boot(); ps.setVoltage(Hardware::get_voltage());
ps.setVoltage(std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str())); ps.setCurrent(Hardware::get_current());
ps.setCurrent(std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str())); read_time = millis_since_boot() - read_time;
read_time = millis_since_boot() - read_time; if (read_time > 50) {
if (read_time > 50) { LOGW("reading hwmon took %lfms", read_time);
LOGW("reading hwmon took %lfms", read_time);
}
} else {
ps.setVoltage(pandaState.voltage_pkt);
ps.setCurrent(pandaState.current_pkt);
} }
uint16_t fan_speed_rpm = panda->get_fan_speed(); uint16_t fan_speed_rpm = panda->get_fan_speed();
@ -534,9 +529,7 @@ void peripheral_control_thread(Panda *panda) {
int cur_integ_lines = event.getDriverCameraState().getIntegLines(); int cur_integ_lines = event.getDriverCameraState().getIntegLines();
float cur_gain = event.getDriverCameraState().getGain(); float cur_gain = event.getDriverCameraState().getGain();
if (Hardware::TICI()) { cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain);
cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain);
}
last_front_frame_t = event.getLogMonoTime(); last_front_frame_t = event.getLogMonoTime();
if (cur_integ_lines <= CUTOFF_IL) { if (cur_integ_lines <= CUTOFF_IL) {

@ -12,7 +12,7 @@ int main(int argc, char *argv[]) {
int err; int err;
err = util::set_realtime_priority(54); err = util::set_realtime_priority(54);
assert(err == 0); assert(err == 0);
err = util::set_core_affinity({Hardware::TICI() ? 4 : 3}); err = util::set_core_affinity({4});
assert(err == 0); assert(err == 0);
} }

@ -90,12 +90,6 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s,
rgb_width = ci->frame_width; rgb_width = ci->frame_width;
rgb_height = ci->frame_height; rgb_height = ci->frame_height;
if (!Hardware::TICI() && ci->bayer) {
// debayering does a 2x downscale
rgb_width = ci->frame_width / 2;
rgb_height = ci->frame_height / 2;
}
yuv_transform = get_model_yuv_transform(ci->bayer); yuv_transform = get_model_yuv_transform(ci->bayer);
vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height); vipc_server->create_buffers(rgb_type, UI_BUF_COUNT, true, rgb_width, rgb_height);

@ -7,7 +7,7 @@
#include "selfdrive/hardware/hw.h" #include "selfdrive/hardware/hw.h"
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
if (Hardware::TICI()) { if (!Hardware::PC()) {
int ret; int ret;
ret = util::set_realtime_priority(53); ret = util::set_realtime_priority(53);
assert(ret == 0); assert(ret == 0);

@ -4,13 +4,12 @@ import time
import numpy as np import numpy as np
from PIL import Image from PIL import Image
from typing import List
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal.visionipc import VisionIpcClient, VisionStreamType from cereal.visionipc import VisionIpcClient, VisionStreamType
from common.params import Params from common.params import Params
from common.realtime import DT_MDL from common.realtime import DT_MDL
from selfdrive.hardware import TICI, PC from selfdrive.hardware import PC
from selfdrive.controls.lib.alertmanager import set_offroad_alert from selfdrive.controls.lib.alertmanager import set_offroad_alert
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
@ -52,11 +51,7 @@ def extract_image(buf, w, h, stride, uv_offset):
return yuv_to_rgb(y, u, v) return yuv_to_rgb(y, u, v)
def rois_in_focus(lapres: List[float]) -> float: def get_snapshots(frame="roadCameraState", front_frame="driverCameraState"):
return sum(1. / len(lapres) for sharpness in lapres if sharpness >= LM_THRESH)
def get_snapshots(frame="roadCameraState", front_frame="driverCameraState", focus_perc_threshold=0.):
sockets = [s for s in (frame, front_frame) if s is not None] sockets = [s for s in (frame, front_frame) if s is not None]
sm = messaging.SubMaster(sockets) sm = messaging.SubMaster(sockets)
vipc_clients = {s: VisionIpcClient("camerad", VISION_STREAMS[s], True) for s in sockets} vipc_clients = {s: VisionIpcClient("camerad", VISION_STREAMS[s], True) for s in sockets}
@ -68,13 +63,6 @@ def get_snapshots(frame="roadCameraState", front_frame="driverCameraState", focu
for client in vipc_clients.values(): for client in vipc_clients.values():
client.connect(True) client.connect(True)
# wait for focus
start_t = time.monotonic()
while time.monotonic() - start_t < 10:
sm.update(100)
if min(sm.rcv_frame.values()) > 1 and rois_in_focus(sm[frame].sharpnessScore) >= focus_perc_threshold:
break
# grab images # grab images
rear, front = None, None rear, front = None, None
if frame is not None: if frame is not None:
@ -113,11 +101,9 @@ def snapshot():
if not PC: if not PC:
managed_processes['camerad'].start() managed_processes['camerad'].start()
frame = "wideRoadCameraState" if TICI else "roadCameraState" frame = "wideRoadCameraState"
front_frame = "driverCameraState" if front_camera_allowed else None front_frame = "driverCameraState" if front_camera_allowed else None
focus_perc_threshold = 0. if TICI else 10 / 12. rear, front = get_snapshots(frame, front_frame)
rear, front = get_snapshots(frame, front_frame, focus_perc_threshold)
finally: finally:
managed_processes['camerad'].stop() managed_processes['camerad'].stop()
params.put_bool("IsTakingSnapshot", False) params.put_bool("IsTakingSnapshot", False)

@ -24,12 +24,13 @@ def create_button_event(cur_but: int, prev_but: int, buttons_dict: Dict[int, cap
return be return be
def create_button_enable_events(buttonEvents: capnp.lib.capnp._DynamicListBuilder) -> List[int]: def create_button_enable_events(buttonEvents: capnp.lib.capnp._DynamicListBuilder, pcm_cruise: bool = False) -> List[int]:
events = [] events = []
for b in buttonEvents: for b in buttonEvents:
# do enable on both accel and decel buttons # do enable on both accel and decel buttons
if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed: if not pcm_cruise:
events.append(EventName.buttonEnable) if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed:
events.append(EventName.buttonEnable)
# do disable on button down # do disable on button down
if b.type == ButtonType.cancel and b.pressed: if b.type == ButtonType.cancel and b.pressed:
events.append(EventName.buttonCancel) events.append(EventName.buttonCancel)

@ -17,6 +17,7 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)] ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)]
ret.minSteerSpeed = -math.inf ret.minSteerSpeed = -math.inf
ret.maxLateralAccel = math.inf # TODO: set to a reasonable value
ret.steerRatio = 0.5 ret.steerRatio = 0.5
ret.steerRateCost = 0.5 ret.steerRateCost = 0.5
ret.steerLimitTimer = 1.0 ret.steerLimitTimer = 1.0

@ -18,7 +18,7 @@ class CAR:
BODY = "COMMA BODY" BODY = "COMMA BODY"
CAR_INFO: Dict[str, CarInfo] = { CAR_INFO: Dict[str, CarInfo] = {
CAR.BODY: CarInfo("comma body", package="All", good_torque=True, harness=Harness.none), CAR.BODY: CarInfo("comma body", package="All", harness=Harness.none),
} }
FW_VERSIONS = { FW_VERSIONS = {

@ -1,9 +1,11 @@
import os import os
from typing import Any, Dict, List from typing import Dict, List
from cereal import car
from common.params import Params from common.params import Params
from common.basedir import BASEDIR from common.basedir import BASEDIR
from selfdrive.version import is_comma_remote, is_tested_branch from selfdrive.version import is_comma_remote, is_tested_branch
from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from selfdrive.car.vin import get_vin, VIN_UNKNOWN from selfdrive.car.vin import get_vin, VIN_UNKNOWN
from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car
@ -11,7 +13,6 @@ from selfdrive.swaglog import cloudlog
import cereal.messaging as messaging import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
from cereal import car
EventName = car.CarEvent.EventName EventName = car.CarEvent.EventName
@ -59,19 +60,6 @@ def load_interfaces(brand_names):
return ret return ret
def get_interface_attr(attr: str) -> Dict[str, Any]:
# returns given attribute from each interface
brand_names = {}
for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]):
try:
brand_name = car_folder.split('/')[-1]
attr_data = getattr(__import__(f'selfdrive.car.{brand_name}.values', fromlist=[attr]), attr, None)
brand_names[brand_name] = attr_data
except (ImportError, OSError):
pass
return brand_names
def _get_interface_names() -> Dict[str, List[str]]: def _get_interface_names() -> Dict[str, List[str]]:
# returns a dict of brand name and its respective models # returns a dict of brand name and its respective models
brand_names = {} brand_names = {}
@ -116,6 +104,9 @@ def fingerprint(logcan, sendcan):
vin = VIN_UNKNOWN vin = VIN_UNKNOWN
exact_fw_match, fw_candidates, car_fw = True, set(), [] exact_fw_match, fw_candidates, car_fw = True, set(), []
if len(vin) != 17:
cloudlog.event("Malformed VIN", vin=vin, error=True)
vin = VIN_UNKNOWN
cloudlog.warning("VIN %s", vin) cloudlog.warning("VIN %s", vin)
Params().put("CarVin", vin) Params().put("CarVin", vin)

@ -23,6 +23,14 @@ class CarInterface(CarInterfaceBase):
ret.steerRateCost = 0.7 ret.steerRateCost = 0.7
ret.steerLimitTimer = 0.4 ret.steerLimitTimer = 0.4
# set max lateral acceleration
if candidate in (CAR.PACIFICA_2018, CAR.PACIFICA_2019_HYBRID, CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019):
ret.maxLateralAccel = 1.6
if candidate in (CAR.PACIFICA_2018_HYBRID,):
ret.maxLateralAccel = 1.4
if candidate in (CAR.PACIFICA_2017_HYBRID,):
ret.maxLateralAccel = 1.2
if candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019): if candidate in (CAR.JEEP_CHEROKEE, CAR.JEEP_CHEROKEE_2019):
ret.wheelbase = 2.91 # in meters ret.wheelbase = 2.91 # in meters
ret.steerRatio = 12.7 ret.steerRatio = 12.7

@ -15,9 +15,8 @@ from selfdrive.car.tests.routes import non_tested_cars
def get_all_footnotes() -> Dict[Enum, int]: def get_all_footnotes() -> Dict[Enum, int]:
all_footnotes = [] all_footnotes = []
for _, footnotes in get_interface_attr("Footnote").items(): for footnotes in get_interface_attr("Footnote", ignore_none=True).values():
if footnotes is not None: all_footnotes += footnotes
all_footnotes += footnotes
return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)}
@ -28,21 +27,20 @@ CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
def get_all_car_info() -> List[CarInfo]: def get_all_car_info() -> List[CarInfo]:
all_car_info: List[CarInfo] = [] all_car_info: List[CarInfo] = []
for models in get_interface_attr("CAR_INFO").values(): for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items():
for model, car_info in models.items(): # Hyundai exception: those with radar have openpilot longitudinal
# Hyundai exception: those with radar have openpilot longitudinal fingerprint = {0: {}, 1: {HKG_RADAR_START_ADDR: 8}, 2: {}, 3: {}}
fingerprint = {0: {}, 1: {HKG_RADAR_START_ADDR: 8}, 2: {}, 3: {}} CP = interfaces[model][0].get_params(model, fingerprint=fingerprint, disable_radar=True)
CP = interfaces[model][0].get_params(model, fingerprint=fingerprint, disable_radar=True)
if CP.dashcamOnly or car_info is None: if CP.dashcamOnly or car_info is None:
continue continue
# A platform can include multiple car models # A platform can include multiple car models
if not isinstance(car_info, list): if not isinstance(car_info, list):
car_info = (car_info,) car_info = (car_info,)
for _car_info in car_info: for _car_info in car_info:
all_car_info.append(_car_info.init(CP, non_tested_cars, ALL_FOOTNOTES)) all_car_info.append(_car_info.init(CP, non_tested_cars, ALL_FOOTNOTES))
# Sort cars by make and model + year # Sort cars by make and model + year
sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: (car.make + car.model).lower()) sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: (car.make + car.model).lower())

@ -1,9 +1,13 @@
import math
from cereal import car from cereal import car
from collections import namedtuple from collections import namedtuple
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from typing import Dict, List, Optional, Union, no_type_check from typing import Dict, List, Optional, Union, no_type_check
STEERING_TORQUE_THRESHOLD = 2.0 # m/s^2
class Tier(Enum): class Tier(Enum):
GOLD = "The best openpilot experience. Great highway driving and beyond." GOLD = "The best openpilot experience. Great highway driving and beyond."
@ -66,6 +70,11 @@ class CarInfo:
if self.min_enable_speed is not None: if self.min_enable_speed is not None:
min_enable_speed = self.min_enable_speed min_enable_speed = self.min_enable_speed
# TODO: remove hardcoded good torque and just use maxLateralAccel
good_torque = self.good_torque
if not math.isnan(CP.maxLateralAccel):
good_torque = CP.maxLateralAccel >= STEERING_TORQUE_THRESHOLD
self.car_name = CP.carName self.car_name = CP.carName
self.make, self.model = self.name.split(' ', 1) self.make, self.model = self.name.split(' ', 1)
self.row = { self.row = {
@ -76,7 +85,7 @@ class CarInfo:
Column.LONGITUDINAL: CP.openpilotLongitudinalControl and not CP.radarOffCan, Column.LONGITUDINAL: CP.openpilotLongitudinalControl and not CP.radarOffCan,
Column.FSR_LONGITUDINAL: min_enable_speed <= 0., Column.FSR_LONGITUDINAL: min_enable_speed <= 0.,
Column.FSR_STEERING: min_steer_speed <= 0., Column.FSR_STEERING: min_steer_speed <= 0.,
Column.STEERING_TORQUE: self.good_torque, Column.STEERING_TORQUE: good_torque,
Column.MAINTAINED: CP.carFingerprint not in non_tested_cars and self.harness is not Harness.none, Column.MAINTAINED: CP.carFingerprint not in non_tested_cars and self.harness is not Harness.none,
} }

@ -1,44 +1,12 @@
import os from selfdrive.car.interfaces import get_interface_attr
from common.basedir import BASEDIR
def get_attr_from_cars(attr, result=dict, combine_brands=True): FW_VERSIONS = get_interface_attr('FW_VERSIONS', combine_brands=True, ignore_none=True)
# read all the folders in selfdrive/car and return a dict where: _FINGERPRINTS = get_interface_attr('FINGERPRINTS', combine_brands=True, ignore_none=True)
# - keys are all the car models
# - values are attr values from all car folders
result = result()
for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]:
try:
car_name = car_folder.split('/')[-1]
values = __import__(f'selfdrive.car.{car_name}.values', fromlist=[attr])
if hasattr(values, attr):
attr_values = getattr(values, attr)
else:
continue
if isinstance(attr_values, dict):
for f, v in attr_values.items():
if combine_brands:
result[f] = v
else:
if car_name not in result:
result[car_name] = {}
result[car_name][f] = v
elif isinstance(attr_values, list):
result += attr_values
except (ImportError, OSError):
pass
return result
FW_VERSIONS = get_attr_from_cars('FW_VERSIONS')
_FINGERPRINTS = get_attr_from_cars('FINGERPRINTS')
_DEBUG_ADDRESS = {1880: 8} # reserved for debug purposes _DEBUG_ADDRESS = {1880: 8} # reserved for debug purposes
def is_valid_for_fingerprint(msg, car_fingerprint): def is_valid_for_fingerprint(msg, car_fingerprint):
adr = msg.address adr = msg.address
# ignore addresses that are more than 11 bits # ignore addresses that are more than 11 bits

@ -1,15 +1,15 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import struct import struct
import traceback import traceback
from typing import Any, List
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Any, List
from tqdm import tqdm from tqdm import tqdm
import panda.python.uds as uds import panda.python.uds as uds
from cereal import car from cereal import car
from selfdrive.car.fingerprints import FW_VERSIONS, get_attr_from_cars from selfdrive.car.interfaces import get_interface_attr
from selfdrive.car.fingerprints import FW_VERSIONS
from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
from selfdrive.car.toyota.values import CAR as TOYOTA from selfdrive.car.toyota.values import CAR as TOYOTA
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
@ -96,9 +96,11 @@ class Request:
brand: str brand: str
request: List[bytes] request: List[bytes]
response: List[bytes] response: List[bytes]
whitelist_ecus: List[int] = field(default_factory=list)
rx_offset: int = DEFAULT_RX_OFFSET rx_offset: int = DEFAULT_RX_OFFSET
bus: int = 1 bus: int = 1
REQUESTS: List[Request] = [ REQUESTS: List[Request] = [
# Subaru # Subaru
Request( Request(
@ -303,7 +305,7 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
addrs = [] addrs = []
parallel_addrs = [] parallel_addrs = []
versions = get_attr_from_cars('FW_VERSIONS', combine_brands=False) versions = get_interface_attr('FW_VERSIONS', ignore_none=True)
if extra is not None: if extra is not None:
versions.update(extra) versions.update(extra)
@ -328,23 +330,26 @@ def get_fw_versions(logcan, sendcan, extra=None, timeout=0.1, debug=False, progr
for addr_chunk in chunks(addr): for addr_chunk in chunks(addr):
for r in REQUESTS: for r in REQUESTS:
try: try:
addrs = [(a, s) for (b, a, s) in addr_chunk if b in (r.brand, 'any')] addrs = [(a, s) for (b, a, s) in addr_chunk if b in (r.brand, 'any') and
(len(r.whitelist_ecus) == 0 or ecu_types[(a, s)] in r.whitelist_ecus)]
if addrs: if addrs:
query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug) query = IsoTpParallelQuery(sendcan, logcan, r.bus, addrs, r.request, r.response, r.rx_offset, debug=debug)
t = 2 * timeout if i == 0 else timeout t = 2 * timeout if i == 0 else timeout
fw_versions.update(query.get_data(t)) fw_versions.update({addr: (version, r.request, r.rx_offset) for addr, version in query.get_data(t).items()})
except Exception: except Exception:
cloudlog.warning(f"FW query exception: {traceback.format_exc()}") cloudlog.warning(f"FW query exception: {traceback.format_exc()}")
# Build capnp list to put into CarParams # Build capnp list to put into CarParams
car_fw = [] car_fw = []
for addr, version in fw_versions.items(): for addr, (version, request, rx_offset) in fw_versions.items():
f = car.CarParams.CarFw.new_message() f = car.CarParams.CarFw.new_message()
f.ecu = ecu_types[addr] f.ecu = ecu_types[addr]
f.fwVersion = version f.fwVersion = version
f.address = addr[0] f.address = addr[0]
f.responseAddress = addr[0] + rx_offset
f.request = request
if addr[1] is not None: if addr[1] is not None:
f.subAddress = addr[1] f.subAddress = addr[1]

@ -75,6 +75,7 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters tire_stiffness_factor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters
ret.steerRatioRear = 0. ret.steerRatioRear = 0.
ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh
ret.maxLateralAccel = 1.6
ret.lateralTuning.pid.kpBP = [0., 40.] ret.lateralTuning.pid.kpBP = [0., 40.]
ret.lateralTuning.pid.kpV = [0., 0.17] ret.lateralTuning.pid.kpV = [0., 0.17]
@ -108,6 +109,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.4 # end to end is 13.46 ret.steerRatio = 14.4 # end to end is 13.46
ret.steerRatioRear = 0. ret.steerRatioRear = 0.
ret.centerToFront = ret.wheelbase * 0.4 ret.centerToFront = ret.wheelbase * 0.4
ret.maxLateralAccel = 1.4
ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia()
elif candidate == CAR.BUICK_REGAL: elif candidate == CAR.BUICK_REGAL:

@ -100,6 +100,7 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 8000], [0, 2560, 3840]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 8000], [0, 2560, 3840]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.1]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.1]]
else: else:
ret.maxLateralAccel = 1.7
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560], [0, 2560]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[1.1], [0.33]]
tire_stiffness_factor = 1. tire_stiffness_factor = 1.
@ -112,6 +113,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.38 # 10.93 is end-to-end spec ret.steerRatio = 15.38 # 10.93 is end-to-end spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 1. tire_stiffness_factor = 1.
ret.maxLateralAccel = 1.6
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
elif candidate in (CAR.ACCORD, CAR.ACCORDH): elif candidate in (CAR.ACCORD, CAR.ACCORDH):
@ -126,6 +128,7 @@ class CarInterface(CarInterfaceBase):
if eps_modified: if eps_modified:
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.09]]
else: else:
ret.maxLateralAccel = 1.6
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
elif candidate == CAR.ACURA_ILX: elif candidate == CAR.ACURA_ILX:
@ -162,6 +165,7 @@ class CarInterface(CarInterfaceBase):
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 10000], [0, 2560, 3840]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 2560, 10000], [0, 2560, 3840]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.21], [0.07]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.21], [0.07]]
else: else:
ret.maxLateralAccel = 1.7
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.64], [0.192]]
tire_stiffness_factor = 0.677 tire_stiffness_factor = 0.677
@ -186,6 +190,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.06 ret.steerRatio = 13.06
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.75 tire_stiffness_factor = 0.75
ret.maxLateralAccel = 1.7
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.05]]
elif candidate == CAR.FREED: elif candidate == CAR.FREED:
@ -216,8 +221,9 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.68 ret.wheelbase = 2.68
ret.centerToFront = ret.wheelbase * 0.38 ret.centerToFront = ret.wheelbase * 0.38
ret.steerRatio = 15.0 # as spec ret.steerRatio = 15.0 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.444 tire_stiffness_factor = 0.444
ret.maxLateralAccel = 0.9
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 1000], [0, 1000]] # TODO: determine if there is a dead zone at the top end
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]]
elif candidate == CAR.ACURA_RDX_3G: elif candidate == CAR.ACURA_RDX_3G:
@ -226,6 +232,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.75 ret.wheelbase = 2.75
ret.centerToFront = ret.wheelbase * 0.41 ret.centerToFront = ret.wheelbase * 0.41
ret.steerRatio = 11.95 # as spec ret.steerRatio = 11.95 # as spec
ret.maxLateralAccel = 1.2
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]]
tire_stiffness_factor = 0.677 tire_stiffness_factor = 0.677
@ -238,6 +245,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.35 # as spec ret.steerRatio = 14.35 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82 tire_stiffness_factor = 0.82
ret.maxLateralAccel = 1.5
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]]
elif candidate == CAR.ODYSSEY_CHN: elif candidate == CAR.ODYSSEY_CHN:
@ -258,6 +266,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 17.25 # as spec ret.steerRatio = 17.25 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.444 tire_stiffness_factor = 0.444
ret.maxLateralAccel = 1.5
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.RIDGELINE: elif candidate == CAR.RIDGELINE:
@ -268,6 +277,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.59 # as spec ret.steerRatio = 15.59 # as spec
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.444 tire_stiffness_factor = 0.444
ret.maxLateralAccel = 1.3
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]]
elif candidate == CAR.INSIGHT: elif candidate == CAR.INSIGHT:
@ -278,6 +288,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.0 # 12.58 is spec end-to-end ret.steerRatio = 15.0 # 12.58 is spec end-to-end
ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end
tire_stiffness_factor = 0.82 tire_stiffness_factor = 0.82
ret.maxLateralAccel = 1.4
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]]
elif candidate == CAR.HONDA_E: elif candidate == CAR.HONDA_E:
@ -367,7 +378,7 @@ class CarInterface(CarInterfaceBase):
events.add(EventName.manualRestart) events.add(EventName.manualRestart)
# handle button presses # handle button presses
events.events.extend(create_button_enable_events(ret.buttonEvents)) events.events.extend(create_button_enable_events(ret.buttonEvents, self.CP.pcmCruise))
ret.events = events.to_msg() ret.events = events.to_msg()

@ -60,6 +60,7 @@ class CarInterface(CarInterfaceBase):
# Values from optimizer # Values from optimizer
ret.steerRatio = 16.55 # 13.8 is spec end-to-end ret.steerRatio = 16.55 # 13.8 is spec end-to-end
tire_stiffness_factor = 0.82 tire_stiffness_factor = 0.82
ret.maxLateralAccel = 3.2
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]]
elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID): elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID):
@ -68,6 +69,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.84 ret.wheelbase = 2.84
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
ret.maxLateralAccel = 2.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.SONATA_LF: elif candidate == CAR.SONATA_LF:
@ -75,6 +77,7 @@ class CarInterface(CarInterfaceBase):
ret.mass = 4497. * CV.LB_TO_KG ret.mass = 4497. * CV.LB_TO_KG
ret.wheelbase = 2.804 ret.wheelbase = 2.804
ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable
ret.maxLateralAccel = 1.8
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.PALISADE: elif candidate == CAR.PALISADE:
@ -83,6 +86,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.90 ret.wheelbase = 2.90
ret.steerRatio = 15.6 * 1.15 ret.steerRatio = 15.6 * 1.15
tire_stiffness_factor = 0.63 tire_stiffness_factor = 0.63
ret.maxLateralAccel = 2.5
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.05]]
elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30):
@ -139,6 +143,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 13.73 # Spec ret.steerRatio = 13.73 # Spec
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
ret.maxLateralAccel = 3.0
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022):
@ -188,6 +193,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.7 ret.wheelbase = 2.7
ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec
tire_stiffness_factor = 0.385 tire_stiffness_factor = 0.385
ret.maxLateralAccel = 2.9
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
if candidate == CAR.KIA_NIRO_HEV: if candidate == CAR.KIA_NIRO_HEV:
@ -219,6 +225,7 @@ class CarInterface(CarInterfaceBase):
ret.mass = 1825. + STD_CARGO_KG ret.mass = 1825. + STD_CARGO_KG
ret.wheelbase = 2.78 ret.wheelbase = 2.78
ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable
ret.maxLateralAccel = 2.4
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_FORTE: elif candidate == CAR.KIA_FORTE:
@ -244,6 +251,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelbase = 2.85 ret.wheelbase = 2.85
ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims) ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims)
tire_stiffness_factor = 0.5 tire_stiffness_factor = 0.5
ret.maxLateralAccel = 2.1
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]]
elif candidate == CAR.KIA_EV6: elif candidate == CAR.KIA_EV6:
@ -254,8 +262,8 @@ class CarInterface(CarInterfaceBase):
get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)] get_safety_config(car.CarParams.SafetyModel.hyundaiHDA2)]
tire_stiffness_factor = 0.65 tire_stiffness_factor = 0.65
max_lat_accel = 2. ret.maxLateralAccel = 2.
set_torque_tune(ret.lateralTuning, max_lat_accel, 0.01) set_torque_tune(ret.lateralTuning, ret.maxLateralAccel, 0.01)
# Genesis # Genesis
elif candidate == CAR.GENESIS_G70: elif candidate == CAR.GENESIS_G70:

@ -1,13 +1,14 @@
import os import os
import time import time
from abc import abstractmethod, ABC from abc import abstractmethod, ABC
from typing import Dict, Tuple, List from typing import Any, Dict, Tuple, List
from cereal import car from cereal import car
from common.basedir import BASEDIR
from common.conversions import Conversions as CV
from common.kalman.simple_kalman import KF1D from common.kalman.simple_kalman import KF1D
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
from common.conversions import Conversions as CV
from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX
from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.events import Events
from selfdrive.controls.lib.vehicle_model import VehicleModel from selfdrive.controls.lib.vehicle_model import VehicleModel
@ -22,7 +23,6 @@ ACCEL_MIN = -3.5
# generic car and radar interfaces # generic car and radar interfaces
class CarInterfaceBase(ABC): class CarInterfaceBase(ABC):
def __init__(self, CP, CarController, CarState): def __init__(self, CP, CarController, CarState):
self.CP = CP self.CP = CP
@ -81,6 +81,7 @@ class CarInterfaceBase(ABC):
ret.steerControlType = car.CarParams.SteerControlType.torque ret.steerControlType = car.CarParams.SteerControlType.torque
ret.minSteerSpeed = 0. ret.minSteerSpeed = 0.
ret.wheelSpeedFactor = 1.0 ret.wheelSpeedFactor = 1.0
ret.maxLateralAccel = float('nan')
ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars ret.pcmCruise = True # openpilot's state is tied to the PCM's cruise state on most cars
ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this
@ -293,3 +294,31 @@ class CarStateBase(ABC):
@staticmethod @staticmethod
def get_loopback_can_parser(CP): def get_loopback_can_parser(CP):
return None return None
# interface-specific helpers
def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str, Any]:
# read all the folders in selfdrive/car and return a dict where:
# - keys are all the car models or brand names
# - values are attr values from all car folders
result = {}
for car_folder in sorted([x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]):
try:
brand_name = car_folder.split('/')[-1]
brand_values = __import__(f'selfdrive.car.{brand_name}.values', fromlist=[attr])
if hasattr(brand_values, attr) or not ignore_none:
attr_data = getattr(brand_values, attr, None)
else:
continue
if combine_brands:
if isinstance(attr_data, dict):
for f, v in attr_data.items():
result[f] = v
else:
result[brand_name] = attr_data
except (ImportError, OSError):
pass
return result

@ -132,7 +132,7 @@ class IsoTpParallelQuery:
request_done[tx_addr] = True request_done[tx_addr] = True
else: else:
request_done[tx_addr] = True request_done[tx_addr] = True
cloudlog.warning(f"iso-tp query bad response: 0x{dat.hex()}") cloudlog.warning(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}")
cur_time = time.monotonic() cur_time = time.monotonic()
if cur_time - last_response_time > timeout: if cur_time - last_response_time > timeout:

@ -51,6 +51,7 @@ class CarInterface(CarInterfaceBase):
ret.centerToFront = ret.wheelbase * 0.5 ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock ret.steerRatio = 17 # learned, 14 stock
ret.steerActuatorDelay = 0.1 ret.steerActuatorDelay = 0.1
ret.maxLateralAccel = 1.3
ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kf = 0.00005
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]]
@ -61,6 +62,7 @@ class CarInterface(CarInterfaceBase):
ret.centerToFront = ret.wheelbase * 0.5 ret.centerToFront = ret.wheelbase * 0.5
ret.steerRatio = 17 # learned, 14 stock ret.steerRatio = 17 # learned, 14 stock
ret.steerActuatorDelay = 0.1 ret.steerActuatorDelay = 0.1
ret.maxLateralAccel = 3.2
ret.lateralTuning.pid.kf = 0.000038 ret.lateralTuning.pid.kf = 0.000038
ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]]
ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]]

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

@ -26,7 +26,7 @@ non_tested_cars = [
HYUNDAI.KIA_OPTIMA_H, HYUNDAI.KIA_OPTIMA_H,
] ]
TestRoute = namedtuple('TestRoute', ['route', 'car_fingerprint', 'segment'], defaults=(None,)) TestRoute = namedtuple('TestRoute', ['route', 'car_model', 'segment'], defaults=(None,))
routes = [ routes = [
TestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY), TestRoute("efdf9af95e71cd84|2022-05-13--19-03-31", COMMA.BODY),

@ -18,7 +18,7 @@ class TestCarDocs(unittest.TestCase):
"Run selfdrive/car/docs.py to generate new supported cars documentation") "Run selfdrive/car/docs.py to generate new supported cars documentation")
def test_missing_car_info(self): def test_missing_car_info(self):
all_car_info_platforms = [p for i in get_interface_attr("CAR_INFO").values() for p in i] all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys()
for platform in sorted(interfaces.keys()): for platform in sorted(interfaces.keys()):
if platform not in all_car_info_platforms: if platform not in all_car_info_platforms:
self.fail("Platform: {} doesn't exist in CarInfo".format(platform)) self.fail("Platform: {} doesn't exist in CarInfo".format(platform))

@ -4,9 +4,9 @@ import unittest
from parameterized import parameterized from parameterized import parameterized
from cereal import car from cereal import car
from selfdrive.car.car_helpers import interfaces from selfdrive.car.car_helpers import get_interface_attr, interfaces
from selfdrive.car.fingerprints import FW_VERSIONS from selfdrive.car.fingerprints import FW_VERSIONS
from selfdrive.car.fw_versions import match_fw_to_car from selfdrive.car.fw_versions import REQUESTS, match_fw_to_car
CarFw = car.CarParams.CarFw CarFw = car.CarParams.CarFw
Ecu = car.CarParams.Ecu Ecu = car.CarParams.Ecu
@ -57,6 +57,23 @@ class TestFwFingerprint(unittest.TestCase):
self.assertTrue(passed, "Blacklisted FW versions found") self.assertTrue(passed, "Blacklisted FW versions found")
def test_fw_request_ecu_whitelist(self):
passed = True
brands = set(r.brand for r in REQUESTS)
versions = get_interface_attr('FW_VERSIONS')
for brand in brands:
whitelisted_ecus = [ecu for r in REQUESTS for ecu in r.whitelist_ecus if r.brand == brand]
brand_ecus = set([fw[0] for car_fw in versions[brand].values() for fw in car_fw])
# each ecu in brand's fw versions needs to be whitelisted at least once
ecus_not_whitelisted = set(brand_ecus) - set(whitelisted_ecus)
if len(whitelisted_ecus) and len(ecus_not_whitelisted):
ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_whitelisted])
print(f'{brand.title()}: FW query whitelist missing ecus: {ecu_strings}')
passed = False
self.assertTrue(passed, "Not all ecus in FW versions found in query whitelists")
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -18,6 +18,7 @@ from selfdrive.car.hyundai.values import CAR as HYUNDAI
from selfdrive.car.tests.routes import non_tested_cars, routes, TestRoute from selfdrive.car.tests.routes import non_tested_cars, routes, TestRoute
from selfdrive.test.openpilotci import get_url from selfdrive.test.openpilotci import get_url
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
from tools.lib.route import Route
from panda.tests.safety import libpandasafety_py from panda.tests.safety import libpandasafety_py
from panda.tests.safety.common import package_can_msg from panda.tests.safety.common import package_can_msg
@ -35,7 +36,7 @@ ignore_addr_checks_valid = [
# build list of test cases # build list of test cases
routes_by_car = defaultdict(set) routes_by_car = defaultdict(set)
for r in routes: for r in routes:
routes_by_car[r.car_fingerprint].add(r) routes_by_car[r.car_model].add(r)
test_cases: List[Tuple[str, Optional[TestRoute]]] = [] test_cases: List[Tuple[str, Optional[TestRoute]]] = []
for i, c in enumerate(sorted(all_known_cars())): for i, c in enumerate(sorted(all_known_cars())):
@ -45,12 +46,17 @@ for i, c in enumerate(sorted(all_known_cars())):
SKIP_ENV_VAR = "SKIP_LONG_TESTS" SKIP_ENV_VAR = "SKIP_LONG_TESTS"
@parameterized_class(('car_model', 'test_route'), test_cases) class TestCarModelBase(unittest.TestCase):
class TestCarModel(unittest.TestCase): car_model = None
test_route = None
ci = True
@unittest.skipIf(SKIP_ENV_VAR in os.environ, f"Long running test skipped. Unset {SKIP_ENV_VAR} to run") @unittest.skipIf(SKIP_ENV_VAR in os.environ, f"Long running test skipped. Unset {SKIP_ENV_VAR} to run")
@classmethod @classmethod
def setUpClass(cls): def setUpClass(cls):
if cls.__name__ == 'TestCarModel' or cls.__name__.endswith('Base'):
raise unittest.SkipTest
if cls.test_route is None: if cls.test_route is None:
if cls.car_model in non_tested_cars: if cls.car_model in non_tested_cars:
print(f"Skipping tests for {cls.car_model}: missing route") print(f"Skipping tests for {cls.car_model}: missing route")
@ -64,7 +70,10 @@ class TestCarModel(unittest.TestCase):
for seg in test_segs: for seg in test_segs:
try: try:
lr = LogReader(get_url(cls.test_route.route, seg)) if cls.ci:
lr = LogReader(get_url(cls.test_route.route, seg))
else:
lr = LogReader(Route(cls.test_route.route).log_paths()[seg])
except Exception: except Exception:
continue continue
@ -79,6 +88,8 @@ class TestCarModel(unittest.TestCase):
elif msg.which() == "carParams": elif msg.which() == "carParams":
if msg.carParams.openpilotLongitudinalControl: if msg.carParams.openpilotLongitudinalControl:
disable_radar = True disable_radar = True
if cls.car_model is None and not cls.ci:
cls.car_model = msg.carParams.carFingerprint
if len(can_msgs) > int(50 / DT_CTRL): if len(can_msgs) > int(50 / DT_CTRL):
break break
@ -249,5 +260,10 @@ class TestCarModel(unittest.TestCase):
self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}") self.assertFalse(len(failed_checks), f"panda safety doesn't agree with openpilot: {failed_checks}")
@parameterized_class(('car_model', 'test_route'), test_cases)
class TestCarModel(TestCarModelBase):
pass
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -38,7 +38,8 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.74 # unknown end-to-end spec ret.steerRatio = 15.74 # unknown end-to-end spec
tire_stiffness_factor = 0.6371 # hand-tune tire_stiffness_factor = 0.6371 # hand-tune
ret.mass = 3045. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3045. * CV.LB_TO_KG + STD_CARGO_KG
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=1.7, FRICTION=0.06) ret.maxLateralAccel = 1.7
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.06)
elif candidate == CAR.PRIUS_V: elif candidate == CAR.PRIUS_V:
stop_and_go = True stop_and_go = True
@ -46,7 +47,8 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 17.4 ret.steerRatio = 17.4
tire_stiffness_factor = 0.5533 tire_stiffness_factor = 0.5533
ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=1.8, FRICTION=0.06) ret.maxLateralAccel = 1.8
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.06)
elif candidate in (CAR.RAV4, CAR.RAV4H): elif candidate in (CAR.RAV4, CAR.RAV4H):
stop_and_go = True if (candidate in CAR.RAV4H) else False stop_and_go = True if (candidate in CAR.RAV4H) else False
@ -54,14 +56,16 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.88 # 14.5 is spec end-to-end ret.steerRatio = 16.88 # 14.5 is spec end-to-end
tire_stiffness_factor = 0.5533 tire_stiffness_factor = 0.5533
ret.mass = 3650. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid ret.mass = 3650. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=1.8, FRICTION=0.06) ret.maxLateralAccel = 1.8
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.06)
elif candidate == CAR.COROLLA: elif candidate == CAR.COROLLA:
ret.wheelbase = 2.70 ret.wheelbase = 2.70
ret.steerRatio = 18.27 ret.steerRatio = 18.27
tire_stiffness_factor = 0.444 # not optimized yet tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 2860. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid ret.mass = 2860. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=2.8, FRICTION=0.024) ret.maxLateralAccel = 2.8
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.024)
elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2): elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2):
stop_and_go = True stop_and_go = True
@ -70,6 +74,7 @@ class CarInterface(CarInterfaceBase):
ret.wheelSpeedFactor = 1.035 ret.wheelSpeedFactor = 1.035
tire_stiffness_factor = 0.5533 tire_stiffness_factor = 0.5533
ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
ret.maxLateralAccel = 1.4
set_lat_tune(ret.lateralTuning, LatTunes.PID_C) set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate in (CAR.CHR, CAR.CHRH): elif candidate in (CAR.CHR, CAR.CHRH):
@ -78,6 +83,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.6 ret.steerRatio = 13.6
tire_stiffness_factor = 0.7933 tire_stiffness_factor = 0.7933
ret.mass = 3300. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3300. * CV.LB_TO_KG + STD_CARGO_KG
ret.maxLateralAccel = 1.3
set_lat_tune(ret.lateralTuning, LatTunes.PID_F) set_lat_tune(ret.lateralTuning, LatTunes.PID_F)
elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2):
@ -87,8 +93,10 @@ class CarInterface(CarInterfaceBase):
tire_stiffness_factor = 0.7933 tire_stiffness_factor = 0.7933
ret.mass = 3400. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid ret.mass = 3400. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
if candidate in (CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): if candidate in (CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2):
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=2.4, FRICTION=0.05) ret.maxLateralAccel = 2.4
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.05)
else: else:
ret.maxLateralAccel = 2.0
set_lat_tune(ret.lateralTuning, LatTunes.PID_C) set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate in (CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): elif candidate in (CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2):
@ -97,6 +105,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.0 ret.steerRatio = 16.0
tire_stiffness_factor = 0.8 tire_stiffness_factor = 0.8
ret.mass = 4700. * CV.LB_TO_KG + STD_CARGO_KG # 4260 + 4-5 people ret.mass = 4700. * CV.LB_TO_KG + STD_CARGO_KG # 4260 + 4-5 people
ret.maxLateralAccel = 2.0
set_lat_tune(ret.lateralTuning, LatTunes.PID_G) set_lat_tune(ret.lateralTuning, LatTunes.PID_G)
elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH): elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH):
@ -105,6 +114,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.0 ret.steerRatio = 16.0
tire_stiffness_factor = 0.8 tire_stiffness_factor = 0.8
ret.mass = 4607. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid limited ret.mass = 4607. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid limited
ret.maxLateralAccel = 1.8
set_lat_tune(ret.lateralTuning, LatTunes.PID_G) set_lat_tune(ret.lateralTuning, LatTunes.PID_G)
elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2): elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2):
@ -115,6 +125,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download
tire_stiffness_factor = 0.7983 tire_stiffness_factor = 0.7983
ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid
ret.maxLateralAccel = 1.6
set_lat_tune(ret.lateralTuning, LatTunes.PID_H) set_lat_tune(ret.lateralTuning, LatTunes.PID_H)
elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022): elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022):
@ -123,6 +134,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.3 ret.steerRatio = 14.3
tire_stiffness_factor = 0.7933 tire_stiffness_factor = 0.7933
ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid
ret.maxLateralAccel = 2.5
set_lat_tune(ret.lateralTuning, LatTunes.PID_D) set_lat_tune(ret.lateralTuning, LatTunes.PID_D)
# 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary. # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary.
@ -138,7 +150,8 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.9 ret.steerRatio = 13.9
tire_stiffness_factor = 0.444 # not optimized yet tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 3060. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3060. * CV.LB_TO_KG + STD_CARGO_KG
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=2.0, FRICTION=0.07) ret.maxLateralAccel = 2.0
set_lat_tune(ret.lateralTuning, LatTunes.TORQUE, MAX_LAT_ACCEL=ret.maxLateralAccel, FRICTION=0.07)
elif candidate in (CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_ESH): elif candidate in (CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_ESH):
stop_and_go = True stop_and_go = True
@ -146,6 +159,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 16.0 # not optimized ret.steerRatio = 16.0 # not optimized
tire_stiffness_factor = 0.444 # not optimized yet tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 3677. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max ret.mass = 3677. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max
ret.maxLateralAccel = 2.2
set_lat_tune(ret.lateralTuning, LatTunes.PID_D) set_lat_tune(ret.lateralTuning, LatTunes.PID_D)
elif candidate == CAR.SIENNA: elif candidate == CAR.SIENNA:
@ -154,6 +168,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 15.5 ret.steerRatio = 15.5
tire_stiffness_factor = 0.444 tire_stiffness_factor = 0.444
ret.mass = 4590. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 4590. * CV.LB_TO_KG + STD_CARGO_KG
ret.maxLateralAccel = 1.6
set_lat_tune(ret.lateralTuning, LatTunes.PID_J) set_lat_tune(ret.lateralTuning, LatTunes.PID_J)
elif candidate in (CAR.LEXUS_IS, CAR.LEXUS_RC): elif candidate in (CAR.LEXUS_IS, CAR.LEXUS_RC):
@ -177,6 +192,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.7 ret.steerRatio = 14.7
tire_stiffness_factor = 0.444 # not optimized yet tire_stiffness_factor = 0.444 # not optimized yet
ret.mass = 4070 * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 4070 * CV.LB_TO_KG + STD_CARGO_KG
ret.maxLateralAccel = 2.0
set_lat_tune(ret.lateralTuning, LatTunes.PID_C) set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate == CAR.PRIUS_TSS2: elif candidate == CAR.PRIUS_TSS2:
@ -185,6 +201,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 13.4 # True steerRatio from older prius ret.steerRatio = 13.4 # True steerRatio from older prius
tire_stiffness_factor = 0.6371 # hand-tune tire_stiffness_factor = 0.6371 # hand-tune
ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG
ret.maxLateralAccel = 2.0
set_lat_tune(ret.lateralTuning, LatTunes.PID_N) set_lat_tune(ret.lateralTuning, LatTunes.PID_N)
elif candidate == CAR.MIRAI: elif candidate == CAR.MIRAI:
@ -193,6 +210,7 @@ class CarInterface(CarInterfaceBase):
ret.steerRatio = 14.8 ret.steerRatio = 14.8
tire_stiffness_factor = 0.8 tire_stiffness_factor = 0.8
ret.mass = 4300. * CV.LB_TO_KG + STD_CARGO_KG ret.mass = 4300. * CV.LB_TO_KG + STD_CARGO_KG
ret.maxLateralAccel = 2.4
set_lat_tune(ret.lateralTuning, LatTunes.PID_C) set_lat_tune(ret.lateralTuning, LatTunes.PID_C)
elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2): elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2):

@ -50,7 +50,7 @@ def set_long_tune(tune, name):
###### LAT ###### ###### LAT ######
def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=.1, use_steering_angle=True): def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=0.01, use_steering_angle=True):
if name == LatTunes.TORQUE: if name == LatTunes.TORQUE:
set_torque_tune(tune, MAX_LAT_ACCEL, FRICTION) set_torque_tune(tune, MAX_LAT_ACCEL, FRICTION)
elif 'PID' in str(name): elif 'PID' in str(name):

@ -100,7 +100,6 @@ class Footnote(Enum):
@dataclass @dataclass
class ToyotaCarInfo(CarInfo): class ToyotaCarInfo(CarInfo):
package: str = "All" package: str = "All"
good_torque: bool = True
harness: Enum = Harness.toyota harness: Enum = Harness.toyota

@ -80,14 +80,17 @@ class CarInterface(CarInterfaceBase):
elif candidate == CAR.ATLAS_MK1: elif candidate == CAR.ATLAS_MK1:
ret.mass = 2011 + STD_CARGO_KG ret.mass = 2011 + STD_CARGO_KG
ret.wheelbase = 2.98 ret.wheelbase = 2.98
ret.maxLateralAccel = 1.4
elif candidate == CAR.GOLF_MK7: elif candidate == CAR.GOLF_MK7:
ret.mass = 1397 + STD_CARGO_KG ret.mass = 1397 + STD_CARGO_KG
ret.wheelbase = 2.62 ret.wheelbase = 2.62
ret.maxLateralAccel = 1.5
elif candidate == CAR.JETTA_MK7: elif candidate == CAR.JETTA_MK7:
ret.mass = 1328 + STD_CARGO_KG ret.mass = 1328 + STD_CARGO_KG
ret.wheelbase = 2.71 ret.wheelbase = 2.71
ret.maxLateralAccel = 1.2
elif candidate == CAR.PASSAT_MK8: elif candidate == CAR.PASSAT_MK8:
ret.mass = 1551 + STD_CARGO_KG ret.mass = 1551 + STD_CARGO_KG
@ -113,6 +116,7 @@ class CarInterface(CarInterfaceBase):
elif candidate == CAR.TIGUAN_MK2: elif candidate == CAR.TIGUAN_MK2:
ret.mass = 1715 + STD_CARGO_KG ret.mass = 1715 + STD_CARGO_KG
ret.wheelbase = 2.74 ret.wheelbase = 2.74
ret.maxLateralAccel = 1.1
elif candidate == CAR.TOURAN_MK2: elif candidate == CAR.TOURAN_MK2:
ret.mass = 1516 + STD_CARGO_KG ret.mass = 1516 + STD_CARGO_KG
@ -130,6 +134,7 @@ class CarInterface(CarInterfaceBase):
elif candidate == CAR.AUDI_A3_MK3: elif candidate == CAR.AUDI_A3_MK3:
ret.mass = 1335 + STD_CARGO_KG ret.mass = 1335 + STD_CARGO_KG
ret.wheelbase = 2.61 ret.wheelbase = 2.61
ret.maxLateralAccel = 1.7
elif candidate == CAR.AUDI_Q2_MK1: elif candidate == CAR.AUDI_Q2_MK1:
ret.mass = 1205 + STD_CARGO_KG ret.mass = 1205 + STD_CARGO_KG
@ -138,6 +143,7 @@ class CarInterface(CarInterfaceBase):
elif candidate == CAR.AUDI_Q3_MK2: elif candidate == CAR.AUDI_Q3_MK2:
ret.mass = 1623 + STD_CARGO_KG ret.mass = 1623 + STD_CARGO_KG
ret.wheelbase = 2.68 ret.wheelbase = 2.68
ret.maxLateralAccel = 1.6
elif candidate == CAR.SEAT_ATECA_MK1: elif candidate == CAR.SEAT_ATECA_MK1:
ret.mass = 1900 + STD_CARGO_KG ret.mass = 1900 + STD_CARGO_KG

@ -141,7 +141,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
VWCarInfo("Volkswagen Jetta 2018-21"), VWCarInfo("Volkswagen Jetta 2018-21"),
VWCarInfo("Volkswagen Jetta GLI 2021"), VWCarInfo("Volkswagen Jetta GLI 2021"),
], ],
CAR.PASSAT_MK8: VWCarInfo("Volkswagen Passat 2015-18", footnotes=[Footnote.PASSAT]), CAR.PASSAT_MK8: VWCarInfo("Volkswagen Passat 2015-19", footnotes=[Footnote.PASSAT]),
CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat 2017, 2021", footnotes=[Footnote.PASSAT_NMS]), CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat 2017, 2021", footnotes=[Footnote.PASSAT_NMS]),
CAR.POLO_MK6: VWCarInfo("Volkswagen Polo 2020"), CAR.POLO_MK6: VWCarInfo("Volkswagen Polo 2020"),
CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533),
@ -452,6 +452,7 @@ FW_VERSIONS = {
b'\xf1\x873G0906264 \xf1\x890004', b'\xf1\x873G0906264 \xf1\x890004',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x870CW300043H \xf1\x891601',
b'\xf1\x870CW300048R \xf1\x890610', b'\xf1\x870CW300048R \xf1\x890610',
b'\xf1\x870D9300014L \xf1\x895002', b'\xf1\x870D9300014L \xf1\x895002',
b'\xf1\x870D9300041A \xf1\x894801', b'\xf1\x870D9300041A \xf1\x894801',
@ -463,6 +464,7 @@ FW_VERSIONS = {
b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311',
b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211',
b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\0165915005914001344701311442900',
b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e5915005914001305701311052900',
b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111', b'\xf1\x875Q0959655S \xf1\x890870\xf1\x82\02315120011111200631145171716121691132111',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
@ -471,11 +473,13 @@ FW_VERSIONS = {
b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1',
b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\00516B00501A1',
b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521B00703A1',
b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600',
], ],
(Ecu.fwdRadar, 0x757, None): [ (Ecu.fwdRadar, 0x757, None): [
b'\xf1\x873Q0907572A \xf1\x890130', b'\xf1\x873Q0907572A \xf1\x890130',
b'\xf1\x873Q0907572B \xf1\x890192', b'\xf1\x873Q0907572B \xf1\x890192',
b'\xf1\x873Q0907572C \xf1\x890195', b'\xf1\x873Q0907572C \xf1\x890195',
b'\xf1\x873Q0907572C \xf1\x890196',
b'\xf1\x875Q0907572R \xf1\x890771', b'\xf1\x875Q0907572R \xf1\x890771',
], ],
}, },

@ -27,7 +27,7 @@ from selfdrive.controls.lib.events import Events, ET
from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert
from selfdrive.controls.lib.vehicle_model import VehicleModel from selfdrive.controls.lib.vehicle_model import VehicleModel
from selfdrive.locationd.calibrationd import Calibration from selfdrive.locationd.calibrationd import Calibration
from selfdrive.hardware import HARDWARE, TICI from selfdrive.hardware import HARDWARE
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
SOFT_DISABLE_TIME = 3 # seconds SOFT_DISABLE_TIME = 3 # seconds
@ -68,17 +68,14 @@ class Controls:
self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState', self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState',
'carControl', 'carEvents', 'carParams']) 'carControl', 'carEvents', 'carParams'])
self.camera_packets = ["roadCameraState", "driverCameraState"] self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
if TICI:
self.camera_packets.append("wideRoadCameraState")
self.can_sock = can_sock self.can_sock = can_sock
if can_sock is None: if can_sock is None:
can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 20 can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 20
self.can_sock = messaging.sub_sock('can', timeout=can_timeout) self.can_sock = messaging.sub_sock('can', timeout=can_timeout)
if TICI: self.log_sock = messaging.sub_sock('androidLog')
self.log_sock = messaging.sub_sock('androidLog')
if CI is None: if CI is None:
# wait for one pandaState and one CAN packet # wait for one pandaState and one CAN packet
@ -358,17 +355,16 @@ class Controls:
if planner_fcw or model_fcw: if planner_fcw or model_fcw:
self.events.add(EventName.fcw) self.events.add(EventName.fcw)
if TICI: for m in messaging.drain_sock(self.log_sock, wait_for_one=False):
for m in messaging.drain_sock(self.log_sock, wait_for_one=False): try:
try: msg = m.androidLog.message
msg = m.androidLog.message if any(err in msg for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED")):
if any(err in msg for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED")): csid = msg.split("CSID:")[-1].split(" ")[0]
csid = msg.split("CSID:")[-1].split(" ")[0] evt = CSID_MAP.get(csid, None)
evt = CSID_MAP.get(csid, None) if evt is not None:
if evt is not None: self.events.add(evt)
self.events.add(evt) except UnicodeDecodeError:
except UnicodeDecodeError: pass
pass
# TODO: fix simulator # TODO: fix simulator
if not SIMULATION: if not SIMULATION:

@ -284,7 +284,7 @@ def comm_issue_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaste
def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: def camera_malfunction_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert:
all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') all_cams = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState')
bad_cams = [s for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])] bad_cams = [s.replace('State', '') for s in all_cams if s in sm.data.keys() and not sm.all_checks([s, ])]
return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams)) return NormalPermanentAlert("Camera Malfunction", ', '.join(bad_cams))

@ -3,20 +3,14 @@ from cereal import log
from common.filter_simple import FirstOrderFilter from common.filter_simple import FirstOrderFilter
from common.numpy_fast import interp from common.numpy_fast import interp
from common.realtime import DT_MDL from common.realtime import DT_MDL
from selfdrive.hardware import TICI
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
TRAJECTORY_SIZE = 33 TRAJECTORY_SIZE = 33
# camera offset is meters from center car to camera # camera offset is meters from center car to camera
# model path is in the frame of the camera. Empirically # model path is in the frame of the camera
# the model knows the difference between TICI and EON
# so a path offset is not needed
PATH_OFFSET = 0.00 PATH_OFFSET = 0.00
if TICI: CAMERA_OFFSET = 0.04
CAMERA_OFFSET = 0.04
else:
CAMERA_OFFSET = 0.0
class LanePlanner: class LanePlanner:

@ -22,7 +22,7 @@ LOW_SPEED_FACTOR = 200
JERK_THRESHOLD = 0.2 JERK_THRESHOLD = 0.2
def set_torque_tune(tune, MAX_LAT_ACCEL=2.5, FRICTION=.1): def set_torque_tune(tune, MAX_LAT_ACCEL=2.5, FRICTION=0.01):
tune.init('torque') tune.init('torque')
tune.torque.useSteeringAngle = True tune.torque.useSteeringAngle = True
tune.torque.kp = 1.0 / MAX_LAT_ACCEL tune.torque.kp = 1.0 / MAX_LAT_ACCEL

@ -5,19 +5,18 @@ from common.realtime import Priority, config_realtime_process
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.controls.lib.longitudinal_planner import Planner from selfdrive.controls.lib.longitudinal_planner import Planner
from selfdrive.controls.lib.lateral_planner import LateralPlanner from selfdrive.controls.lib.lateral_planner import LateralPlanner
from selfdrive.hardware import TICI
import cereal.messaging as messaging import cereal.messaging as messaging
def plannerd_thread(sm=None, pm=None): def plannerd_thread(sm=None, pm=None):
config_realtime_process(5 if TICI else 2, Priority.CTRL_LOW) config_realtime_process(5, Priority.CTRL_LOW)
cloudlog.info("plannerd is waiting for CarParams") cloudlog.info("plannerd is waiting for CarParams")
params = Params() params = Params()
CP = car.CarParams.from_bytes(params.get("CarParams", block=True)) CP = car.CarParams.from_bytes(params.get("CarParams", block=True))
cloudlog.info("plannerd got CarParams: %s", CP.carName) cloudlog.info("plannerd got CarParams: %s", CP.carName)
use_lanelines = not params.get_bool('EndToEndToggle') use_lanelines = False
wide_camera = params.get_bool('WideCameraOnly') wide_camera = params.get_bool('WideCameraOnly')
cloudlog.event("e2e mode", on=use_lanelines) cloudlog.event("e2e mode", on=use_lanelines)

@ -11,7 +11,6 @@ from common.realtime import Ratekeeper, Priority, config_realtime_process
from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid
from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.hardware import TICI
class KalmanParams(): class KalmanParams():
@ -180,7 +179,7 @@ class RadarD():
# fuses camera and radar data for best lead detection # fuses camera and radar data for best lead detection
def radard_thread(sm=None, pm=None, can_sock=None): def radard_thread(sm=None, pm=None, can_sock=None):
config_realtime_process(5 if TICI else 2, Priority.CTRL_LOW) config_realtime_process(5, Priority.CTRL_LOW)
# wait for stats about the car to come in from controls # wait for stats about the car to come in from controls
cloudlog.info("radard is waiting for CarParams") cloudlog.info("radard is waiting for CarParams")

@ -0,0 +1,36 @@
#!/usr/bin/env python3
import argparse
import sys
from typing import List, Tuple
import unittest
from selfdrive.car.tests.routes import TestRoute
from selfdrive.car.tests.test_models import TestCarModel
def create_test_models_suite(routes: List[Tuple[str, TestRoute]], ci=False) -> unittest.TestSuite:
test_suite = unittest.TestSuite()
for car_model, test_route in routes:
# create new test case and discover tests
test_case_args = {"car_model": car_model, "test_route": test_route, "ci": ci}
CarModelTestCase = type("CarModelTestCase", (TestCarModel,), test_case_args)
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(CarModelTestCase))
return test_suite
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Test any route against common issues with a new car port. " +
"Uses selfdrive/car/tests/test_models.py")
parser.add_argument("route", help="Specify route to run tests on")
parser.add_argument("--car", help="Specify car model for test route")
parser.add_argument("--segment", type=int, nargs="?", help="Specify segment of route to test")
parser.add_argument("--ci", action="store_true", help="Attempt to get logs using openpilotci, need to specify car")
args = parser.parse_args()
if len(sys.argv) == 1:
parser.print_help()
sys.exit()
test_route = TestRoute(args.route, args.car, segment=args.segment)
test_suite = create_test_models_suite([(args.car, test_route)], ci=args.ci)
unittest.TextTestRunner().run(test_suite)

@ -6,6 +6,7 @@ from selfdrive.hardware.tici.hardware import Tici
from selfdrive.hardware.pc.hardware import Pc from selfdrive.hardware.pc.hardware import Pc
TICI = os.path.isfile('/TICI') TICI = os.path.isfile('/TICI')
AGNOS = TICI
PC = not TICI PC = not TICI

@ -2,6 +2,7 @@
#include <cstdlib> #include <cstdlib>
#include <fstream> #include <fstream>
#include "cereal/messaging/messaging.h"
// no-op base hw class // no-op base hw class
class HardwareNone { class HardwareNone {
@ -10,6 +11,10 @@ public:
static constexpr float MIN_VOLUME = 0.2; static constexpr float MIN_VOLUME = 0.2;
static std::string get_os_version() { return ""; } static std::string get_os_version() { return ""; }
static std::string get_name() { return ""; };
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::UNKNOWN; };
static int get_voltage() { return 0; };
static int get_current() { return 0; };
static void reboot() {} static void reboot() {}
static void poweroff() {} static void poweroff() {}
@ -21,4 +26,5 @@ public:
static bool PC() { return false; } static bool PC() { return false; }
static bool TICI() { return false; } static bool TICI() { return false; }
static bool AGNOS() { return false; }
}; };

@ -10,8 +10,11 @@
class HardwarePC : public HardwareNone { class HardwarePC : public HardwareNone {
public: public:
static std::string get_os_version() { return "openpilot for PC"; } static std::string get_os_version() { return "openpilot for PC"; }
static std::string get_name() { return "pc"; };
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; };
static bool PC() { return true; } static bool PC() { return true; }
static bool TICI() { return util::getenv("TICI", 0) == 1; } static bool TICI() { return util::getenv("TICI", 0) == 1; }
static bool AGNOS() { return util::getenv("TICI", 0) == 1; }
}; };
#define Hardware HardwarePC #define Hardware HardwarePC
#endif #endif

@ -1,10 +1,10 @@
[ [
{ {
"name": "boot", "name": "boot",
"url": "https://commadist.azureedge.net/agnosupdate/boot-0a86272196cb54607f8ba082f412fed4607baaf6ce3eb8dc8e950b4a1d763954.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/boot-bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f.img.xz",
"hash": "0a86272196cb54607f8ba082f412fed4607baaf6ce3eb8dc8e950b4a1d763954", "hash": "bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f",
"hash_raw": "0a86272196cb54607f8ba082f412fed4607baaf6ce3eb8dc8e950b4a1d763954", "hash_raw": "bb71f49294150c4233b89e2a10768a5a3de003203ecd02e3f845821b35cd409f",
"size": 14776320, "size": 14780416,
"sparse": false, "sparse": false,
"full_check": true, "full_check": true,
"has_ab": true "has_ab": true
@ -41,9 +41,9 @@
}, },
{ {
"name": "system", "name": "system",
"url": "https://commadist.azureedge.net/agnosupdate/system-9b0b534ed0c35c25850dbb73d3f052611f2e2c9db32410edc25d75fbcfc6c15e.img.xz", "url": "https://commadist.azureedge.net/agnosupdate/system-11fdbc9e8a9cd27f98346d7e1039bc5b3032d0e892ff95fa1258673ff1809bca.img.xz",
"hash": "b1f622f00037bbdb28c0a6016c0e42fa7f87e99591ff2417c757a67ff559b526", "hash": "45b4719a9e580617cf840036b24fb0dcd32491edd9654d8d74c28d91ff362d36",
"hash_raw": "9b0b534ed0c35c25850dbb73d3f052611f2e2c9db32410edc25d75fbcfc6c15e", "hash_raw": "11fdbc9e8a9cd27f98346d7e1039bc5b3032d0e892ff95fa1258673ff1809bca",
"size": 10737418240, "size": 10737418240,
"sparse": true, "sparse": true,
"full_check": false, "full_check": false,

@ -12,9 +12,15 @@ public:
static constexpr float MAX_VOLUME = 0.9; static constexpr float MAX_VOLUME = 0.9;
static constexpr float MIN_VOLUME = 0.2; static constexpr float MIN_VOLUME = 0.2;
static bool TICI() { return true; } static bool TICI() { return true; }
static bool AGNOS() { return true; }
static std::string get_os_version() { static std::string get_os_version() {
return "AGNOS " + util::read_file("/VERSION"); return "AGNOS " + util::read_file("/VERSION");
}; };
static std::string get_name() { return "tici"; };
static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::TICI; };
static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); };
static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); };
static void reboot() { std::system("sudo reboot"); }; static void reboot() { std::system("sudo reboot"); };
static void poweroff() { std::system("sudo poweroff"); }; static void poweroff() { std::system("sudo poweroff"); };

@ -16,11 +16,11 @@ class Proc:
power: float power: float
rtol: float = 0.05 rtol: float = 0.05
atol: float = 0.1 atol: float = 0.1
warmup: float = 3. warmup: float = 6.
PROCS = [ PROCS = [
Proc('camerad', 2.25), Proc('camerad', 2.15),
Proc('modeld', 0.95), Proc('modeld', 1.0),
Proc('dmonitoringmodeld', 0.25), Proc('dmonitoringmodeld', 0.25),
Proc('encoderd', 0.23), Proc('encoderd', 0.23),
] ]

@ -1,4 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import threading
import time
from typing import List from typing import List
import numpy as np import numpy as np
@ -8,7 +10,9 @@ from numpy.linalg import linalg
from cereal import log, messaging from cereal import log, messaging
from laika import AstroDog from laika import AstroDog
from laika.ephemeris import convert_ublox_ephem from laika.constants import SECS_IN_HR, SECS_IN_MIN
from laika.ephemeris import EphemerisType, convert_ublox_ephem
from laika.gps_time import GPSTime
from laika.helpers import ConstellationId from laika.helpers import ConstellationId
from laika.raw_gnss import GNSSMeasurement, calc_pos_fix, correct_measurements, process_measurements, read_raw_ublox from laika.raw_gnss import GNSSMeasurement, calc_pos_fix, correct_measurements, process_measurements, read_raw_ublox
from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind
@ -22,14 +26,18 @@ MAX_TIME_GAP = 10
class Laikad: class Laikad:
def __init__(self, use_internet): def __init__(self, valid_const=("GPS",), auto_update=False, valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV)):
self.astro_dog = AstroDog(use_internet=use_internet) self.astro_dog = AstroDog(valid_const=valid_const, use_internet=auto_update, valid_ephem_types=valid_ephem_types)
self.gnss_kf = GNSSKalman(GENERATED_DIR) self.gnss_kf = GNSSKalman(GENERATED_DIR)
self.latest_epoch_fetched = GPSTime(0, 0)
self.latest_time_msg = None
def process_ublox_msg(self, ublox_msg, ublox_mono_time: int): def process_ublox_msg(self, ublox_msg, ublox_mono_time: int):
if ublox_msg.which == 'measurementReport': if ublox_msg.which == 'measurementReport':
report = ublox_msg.measurementReport report = ublox_msg.measurementReport
new_meas = read_raw_ublox(report) new_meas = read_raw_ublox(report)
if report.gpsWeek > 0:
self.latest_time_msg = GPSTime(report.gpsWeek, report.rcvTow)
measurements = process_measurements(new_meas, self.astro_dog) measurements = process_measurements(new_meas, self.astro_dog)
pos_fix = calc_pos_fix(measurements, min_measurements=4) pos_fix = calc_pos_fix(measurements, min_measurements=4)
# To get a position fix a minimum of 5 measurements are needed. # To get a position fix a minimum of 5 measurements are needed.
@ -41,29 +49,29 @@ class Laikad:
t = ublox_mono_time * 1e-9 t = ublox_mono_time * 1e-9
self.update_localizer(pos_fix, t, corrected_measurements) self.update_localizer(pos_fix, t, corrected_measurements)
localizer_valid = self.localizer_valid(t) localizer_valid = self.localizer_valid(t)
ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() ecef_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist()
ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist() ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist()
pos_std = float(np.linalg.norm(self.gnss_kf.P[GStates.ECEF_POS])) pos_std = self.gnss_kf.P[GStates.ECEF_POS].flatten().tolist()
vel_std = float(np.linalg.norm(self.gnss_kf.P[GStates.ECEF_VELOCITY])) vel_std = self.gnss_kf.P[GStates.ECEF_VELOCITY].flatten().tolist()
bearing_deg, bearing_std = get_bearing_from_gnss(ecef_pos, ecef_vel, vel_std) bearing_deg, bearing_std = get_bearing_from_gnss(ecef_pos, ecef_vel, vel_std)
meas_msgs = [create_measurement_msg(m) for m in corrected_measurements] meas_msgs = [create_measurement_msg(m) for m in corrected_measurements]
dat = messaging.new_message("gnssMeasurements") dat = messaging.new_message("gnssMeasurements")
measurement_msg = log.GnssMeasurements.Measurement.new_message measurement_msg = log.LiveLocationKalman.Measurement.new_message
dat.gnssMeasurements = { dat.gnssMeasurements = {
"positionECEF": measurement_msg(value=ecef_pos, std=pos_std, valid=localizer_valid), "positionECEF": measurement_msg(value=ecef_pos, std=pos_std, valid=localizer_valid),
"velocityECEF": measurement_msg(value=ecef_vel, std=vel_std, valid=localizer_valid), "velocityECEF": measurement_msg(value=ecef_vel, std=vel_std, valid=localizer_valid),
"bearingDeg": measurement_msg(value=[bearing_deg], std=bearing_std, valid=localizer_valid), "bearingDeg": measurement_msg(value=[bearing_deg], std=[bearing_std], valid=localizer_valid),
"ubloxMonoTime": ublox_mono_time, "ubloxMonoTime": ublox_mono_time,
"correctedMeasurements": meas_msgs "correctedMeasurements": meas_msgs
} }
return dat return dat
elif ublox_msg.which == 'ephemeris': elif ublox_msg.which == 'ephemeris':
ephem = convert_ublox_ephem(ublox_msg.ephemeris) ephem = convert_ublox_ephem(ublox_msg.ephemeris)
self.astro_dog.add_ephem(ephem, self.astro_dog.orbits) self.astro_dog.add_ephems([ephem], self.astro_dog.nav)
# elif ublox_msg.which == 'ionoData': # elif ublox_msg.which == 'ionoData':
# todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them. # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them.
@ -77,7 +85,7 @@ class Laikad:
filter_time = self.gnss_kf.filter.filter_time filter_time = self.gnss_kf.filter.filter_time
if filter_time is None: if filter_time is None:
cloudlog.info("Init gnss kalman filter") cloudlog.info("Init gnss kalman filter")
elif (t - filter_time) > MAX_TIME_GAP: elif abs(t - filter_time) > MAX_TIME_GAP:
cloudlog.error("Time gap of over 10s detected, gnss kalman reset") cloudlog.error("Time gap of over 10s detected, gnss kalman reset")
else: else:
cloudlog.error("Gnss kalman filter state is nan") cloudlog.error("Gnss kalman filter state is nan")
@ -90,8 +98,7 @@ class Laikad:
def localizer_valid(self, t: float): def localizer_valid(self, t: float):
filter_time = self.gnss_kf.filter.filter_time filter_time = self.gnss_kf.filter.filter_time
return filter_time is not None and (t - filter_time) < MAX_TIME_GAP and \ return filter_time is not None and (t - filter_time) < MAX_TIME_GAP and all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))
all(np.isfinite(self.gnss_kf.x[GStates.ECEF_POS]))
def init_gnss_localizer(self, est_pos): def init_gnss_localizer(self, est_pos):
x_initial, p_initial_diag = np.copy(GNSSKalman.x_initial), np.copy(np.diagonal(GNSSKalman.P_initial)) x_initial, p_initial_diag = np.copy(GNSSKalman.x_initial), np.copy(np.diagonal(GNSSKalman.P_initial))
@ -100,6 +107,22 @@ class Laikad:
self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag) self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag)
def orbit_thread(self, end_event: threading.Event):
while not end_event.is_set():
if self.latest_time_msg:
self.fetch_orbits(self.latest_time_msg)
time.sleep(0.1)
def fetch_orbits(self, t: GPSTime):
if self.latest_epoch_fetched < t + SECS_IN_MIN:
cloudlog.info("Start to download/parse orbits")
orbit_ephems = self.astro_dog.download_parse_orbit_data(t, skip_before_epoch=t - 2 * SECS_IN_HR)
if len(orbit_ephems) > 0:
cloudlog.info(f"downloaded and parsed correctly new orbits {len(orbit_ephems)}, Constellations:{set([e.prn[0] for e in orbit_ephems])}")
self.astro_dog.add_ephems(orbit_ephems, self.astro_dog.orbits)
latest_orbit = max(orbit_ephems, key=lambda e: e.epoch) # type: ignore
self.latest_epoch_fetched = latest_orbit.epoch
def create_measurement_msg(meas: GNSSMeasurement): def create_measurement_msg(meas: GNSSMeasurement):
c = log.GnssMeasurements.CorrectedMeasurement.new_message() c = log.GnssMeasurements.CorrectedMeasurement.new_message()
@ -136,7 +159,7 @@ def get_bearing_from_gnss(ecef_pos, ecef_vel, vel_std):
ned_vel = np.einsum('ij,j ->i', converter.ned_from_ecef_matrix, ecef_vel) ned_vel = np.einsum('ij,j ->i', converter.ned_from_ecef_matrix, ecef_vel)
bearing = np.arctan2(ned_vel[1], ned_vel[0]) bearing = np.arctan2(ned_vel[1], ned_vel[0])
bearing_std = np.arctan2(vel_std, np.linalg.norm(ned_vel)) bearing_std = np.arctan2(np.linalg.norm(vel_std), np.linalg.norm(ned_vel))
return float(np.rad2deg(bearing)), float(bearing_std) return float(np.rad2deg(bearing)), float(bearing_std)
@ -144,16 +167,22 @@ def main():
sm = messaging.SubMaster(['ubloxGnss']) sm = messaging.SubMaster(['ubloxGnss'])
pm = messaging.PubMaster(['gnssMeasurements']) pm = messaging.PubMaster(['gnssMeasurements'])
laikad = Laikad(use_internet=True) laikad = Laikad()
while True: end_event = threading.Event()
sm.update() threading.Thread(target=laikad.orbit_thread, args=(end_event,)).start()
try:
if sm.updated['ubloxGnss']: while not end_event.is_set():
ublox_msg = sm['ubloxGnss'] sm.update()
msg = laikad.process_ublox_msg(ublox_msg, sm.logMonoTime['ubloxGnss'])
if msg is not None: if sm.updated['ubloxGnss']:
pm.send('gnssMeasurements', msg) ublox_msg = sm['ubloxGnss']
msg = laikad.process_ublox_msg(ublox_msg, sm.logMonoTime['ubloxGnss'])
if msg is not None:
pm.send('gnssMeasurements', msg)
except (KeyboardInterrupt, SystemExit):
end_event.set()
raise
if __name__ == "__main__": if __name__ == "__main__":

@ -2,9 +2,10 @@
import unittest import unittest
from datetime import datetime from datetime import datetime
from laika.ephemeris import EphemerisType
from laika.gps_time import GPSTime from laika.gps_time import GPSTime
from laika.helpers import ConstellationId from laika.helpers import ConstellationId
from laika.raw_gnss import GNSSMeasurement from laika.raw_gnss import GNSSMeasurement, read_raw_ublox
from selfdrive.locationd.laikad import Laikad, create_measurement_msg from selfdrive.locationd.laikad import Laikad, create_measurement_msg
from selfdrive.test.openpilotci import get_url from selfdrive.test.openpilotci import get_url
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
@ -17,11 +18,11 @@ def get_log(segs=range(0)):
return [m for m in logs if m.which() == 'ubloxGnss'] return [m for m in logs if m.which() == 'ubloxGnss']
def process_msgs(lr, laikad: Laikad): def verify_messages(lr, laikad):
good_msgs = [] good_msgs = []
for m in lr: for m in lr:
msg = laikad.process_ublox_msg(m.ubloxGnss, m.logMonoTime) msg = laikad.process_ublox_msg(m.ubloxGnss, m.logMonoTime)
if msg is not None: if msg is not None and len(msg.gnssMeasurements.correctedMeasurements) > 0:
good_msgs.append(msg) good_msgs.append(msg)
return good_msgs return good_msgs
@ -42,32 +43,63 @@ class TestLaikad(unittest.TestCase):
self.assertEqual(msg.constellationId, 'gps') self.assertEqual(msg.constellationId, 'gps')
def test_laika_online(self): def test_laika_online(self):
# Set to offline forces to use ephemeris messages laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT)
laikad = Laikad(use_internet=True) correct_msgs = verify_messages(self.logs, laikad)
msgs = process_msgs(self.logs, laikad)
correct_msgs = [m for m in msgs if len(m.gnssMeasurements.correctedMeasurements) > 0] correct_msgs_expected = 560
self.assertEqual(correct_msgs_expected, len(correct_msgs))
self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
def test_laika_online_nav_only(self):
laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV)
correct_msgs = verify_messages(self.logs, laikad)
correct_msgs_expected = 560 correct_msgs_expected = 560
self.assertEqual(correct_msgs_expected, len(correct_msgs)) self.assertEqual(correct_msgs_expected, len(correct_msgs))
self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
def test_laika_offline(self): def test_laika_offline(self):
# Set to offline forces to use ephemeris messages # Set auto_update to false forces to use ephemeris messages
laikad = Laikad(use_internet=False) laikad = Laikad(auto_update=False)
msgs = process_msgs(self.logs, laikad) correct_msgs = verify_messages(self.logs, laikad)
correct_msgs = [m for m in msgs if len(m.gnssMeasurements.correctedMeasurements) > 0]
self.assertEqual(256, len(correct_msgs)) self.assertEqual(256, len(correct_msgs))
self.assertEqual(256, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) self.assertEqual(256, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
def test_laika_offline_ephem_at_start(self): def test_laika_offline_ephem_at_start(self):
# Test offline but process ephemeris msgs of segment first # Test offline but process ephemeris msgs of segment first
laikad = Laikad(use_internet=False) laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV)
ephemeris_logs = [m for m in self.logs if m.ubloxGnss.which() == 'ephemeris'] ephemeris_logs = [m for m in self.logs if m.ubloxGnss.which() == 'ephemeris']
msgs = process_msgs(ephemeris_logs+self.logs, laikad) correct_msgs = verify_messages(ephemeris_logs+self.logs, laikad)
correct_msgs = [m for m in msgs if len(m.gnssMeasurements.correctedMeasurements) > 0]
self.assertEqual(554, len(correct_msgs)) self.assertEqual(554, len(correct_msgs))
self.assertGreaterEqual(554, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) self.assertGreaterEqual(554, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
def test_laika_get_orbits(self):
laikad = Laikad(auto_update=False)
first_gps_time = None
for m in self.logs:
if m.ubloxGnss.which == 'measurementReport':
new_meas = read_raw_ublox(m.ubloxGnss.measurementReport)
if len(new_meas) != 0:
first_gps_time = new_meas[0].recv_time
break
# Pretend thread has loaded the orbits on startup by using the time of the first gps message.
laikad.fetch_orbits(first_gps_time)
self.assertEqual(31, len(laikad.astro_dog.orbits.keys()))
correct_msgs = verify_messages(self.logs, laikad)
correct_msgs_expected = 560
self.assertEqual(correct_msgs_expected, len(correct_msgs))
self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid]))
@unittest.skip("Use to debug live data")
def test_laika_get_orbits_now(self):
laikad = Laikad(auto_update=False)
laikad.fetch_orbits(GPSTime.from_datetime(datetime.utcnow()))
print(laikad.latest_epoch_fetched.as_datetime())
print(min(laikad.astro_dog.orbits[list(laikad.astro_dog.orbits.keys())[0]], key=lambda e: e.epoch).epoch.as_datetime())
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -8,7 +8,7 @@
static kj::Array<capnp::word> build_boot_log() { static kj::Array<capnp::word> build_boot_log() {
std::vector<std::string> bootlog_commands; std::vector<std::string> bootlog_commands;
if (Hardware::TICI()) { if (Hardware::AGNOS()) {
bootlog_commands.push_back("journalctl"); bootlog_commands.push_back("journalctl");
bootlog_commands.push_back("sudo nvme smart-log --output-format=json /dev/nvme0"); bootlog_commands.push_back("sudo nvme smart-log --output-format=json /dev/nvme0");
} }

@ -133,7 +133,7 @@ void encoderd_thread() {
} }
int main() { int main() {
if (Hardware::TICI()) { if (!Hardware::PC()) {
int ret; int ret;
ret = util::set_realtime_priority(52); ret = util::set_realtime_priority(52);
assert(ret == 0); assert(ret == 0);

@ -33,12 +33,7 @@ kj::Array<capnp::word> logger_build_init_data() {
MessageBuilder msg; MessageBuilder msg;
auto init = msg.initEvent().initInitData(); auto init = msg.initEvent().initInitData();
if (Hardware::TICI()) { init.setDeviceType(Hardware::get_device_type());
init.setDeviceType(cereal::InitData::DeviceType::TICI);
} else {
init.setDeviceType(cereal::InitData::DeviceType::PC);
}
init.setVersion(COMMA_VERSION); init.setVersion(COMMA_VERSION);
std::ifstream cmdline_stream("/proc/cmdline"); std::ifstream cmdline_stream("/proc/cmdline");

@ -267,7 +267,7 @@ void loggerd_thread() {
} }
int main(int argc, char** argv) { int main(int argc, char** argv) {
if (Hardware::TICI()) { if (!Hardware::PC()) {
int ret; int ret;
ret = util::set_core_affinity({0, 1, 2, 3}); ret = util::set_core_affinity({0, 1, 2, 3});
assert(ret == 0); assert(ret == 0);
@ -279,4 +279,4 @@ int main(int argc, char** argv) {
loggerd_thread(); loggerd_thread();
return 0; return 0;
} }

@ -33,8 +33,8 @@
#endif #endif
constexpr int MAIN_FPS = 20; constexpr int MAIN_FPS = 20;
const int MAIN_BITRATE = Hardware::TICI() ? 10000000 : 5000000; const int MAIN_BITRATE = 10000000;
const int DCAM_BITRATE = Hardware::TICI() ? MAIN_BITRATE : 2500000; const int DCAM_BITRATE = MAIN_BITRATE;
#define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead #define NO_CAMERA_PATIENCE 500 // fall back to time-based rotation if all cameras are dead
@ -89,8 +89,8 @@ const LogCameraInfo cameras_logged[] = {
.bitrate = MAIN_BITRATE, .bitrate = MAIN_BITRATE,
.is_h265 = true, .is_h265 = true,
.has_qcamera = false, .has_qcamera = false,
.enable = Hardware::TICI(), .enable = true,
.record = Hardware::TICI(), .record = true,
.frame_width = 1928, .frame_width = 1928,
.frame_height = 1208, .frame_height = 1208,
}, },
@ -102,6 +102,6 @@ const LogCameraInfo qcam_info = {
.is_h265 = false, .is_h265 = false,
.enable = true, .enable = true,
.record = true, .record = true,
.frame_width = Hardware::TICI() ? 526 : 480, .frame_width = 526,
.frame_height = Hardware::TICI() ? 330 : 360 // keep pixel count the same? .frame_height = 330,
}; };

@ -15,14 +15,12 @@ from cereal.services import service_list
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.params import Params from common.params import Params
from common.timeout import Timeout from common.timeout import Timeout
from selfdrive.hardware import TICI
from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.config import ROOT
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
from selfdrive.version import get_version from selfdrive.version import get_version
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
from cereal.visionipc import VisionIpcServer, VisionStreamType from cereal.visionipc import VisionIpcServer, VisionStreamType
from common.transformations.camera import eon_f_frame_size, tici_f_frame_size, \ from common.transformations.camera import tici_f_frame_size, tici_d_frame_size, tici_e_frame_size
eon_d_frame_size, tici_d_frame_size, tici_e_frame_size
SentinelType = log.Sentinel.SentinelType SentinelType = log.Sentinel.SentinelType
@ -108,20 +106,15 @@ class TestLoggerd(unittest.TestCase):
os.environ["LOGGERD_TEST"] = "1" os.environ["LOGGERD_TEST"] = "1"
Params().put("RecordFront", "1") Params().put("RecordFront", "1")
expected_files = {"rlog", "qlog", "qcamera.ts", "fcamera.hevc", "dcamera.hevc"} expected_files = {"rlog", "qlog", "qcamera.ts", "fcamera.hevc", "dcamera.hevc", "ecamera.hevc"}
streams = [(VisionStreamType.VISION_STREAM_ROAD, (*tici_f_frame_size, 2048*2346, 2048, 2048*1216) if TICI else eon_f_frame_size, "roadCameraState"), streams = [(VisionStreamType.VISION_STREAM_ROAD, (*tici_f_frame_size, 2048*2346, 2048, 2048*1216), "roadCameraState"),
(VisionStreamType.VISION_STREAM_DRIVER, (*tici_d_frame_size, 2048*2346, 2048, 2048*1216) if TICI else eon_d_frame_size, "driverCameraState")] (VisionStreamType.VISION_STREAM_DRIVER, (*tici_d_frame_size, 2048*2346, 2048, 2048*1216), "driverCameraState"),
if TICI: (VisionStreamType.VISION_STREAM_WIDE_ROAD, (*tici_e_frame_size, 2048*2346, 2048, 2048*1216), "wideRoadCameraState")]
expected_files.add("ecamera.hevc")
streams.append((VisionStreamType.VISION_STREAM_WIDE_ROAD, (*tici_e_frame_size, 2048*2346, 2048, 2048*1216), "wideRoadCameraState"))
pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"]) pm = messaging.PubMaster(["roadCameraState", "driverCameraState", "wideRoadCameraState"])
vipc_server = VisionIpcServer("camerad") vipc_server = VisionIpcServer("camerad")
for stream_type, frame_spec, _ in streams: for stream_type, frame_spec, _ in streams:
if TICI: vipc_server.create_buffers_with_sizes(stream_type, 40, False, *(frame_spec))
vipc_server.create_buffers_with_sizes(stream_type, 40, False, *(frame_spec))
else:
vipc_server.create_buffers(stream_type, 40, False, *(frame_spec))
vipc_server.start_listener() vipc_server.start_listener()
for _ in range(5): for _ in range(5):
@ -134,10 +127,7 @@ class TestLoggerd(unittest.TestCase):
fps = 20.0 fps = 20.0
for n in range(1, int(num_segs*length*fps)+1): for n in range(1, int(num_segs*length*fps)+1):
for stream_type, frame_spec, state in streams: for stream_type, frame_spec, state in streams:
if TICI: dat = np.empty(frame_spec[2], dtype=np.uint8)
dat = np.empty(frame_spec[2], dtype=np.uint8)
else:
dat = np.empty(int(frame_spec[0]*frame_spec[1]*3/2), dtype=np.uint8)
vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps) vipc_server.send(stream_type, dat[:].flatten().tobytes(), n, n/fps, n/fps)
camera_state = messaging.new_message(state) camera_state = messaging.new_message(state)

@ -10,14 +10,14 @@ from pathlib import Path
from common.basedir import BASEDIR from common.basedir import BASEDIR
from common.spinner import Spinner from common.spinner import Spinner
from common.text_window import TextWindow from common.text_window import TextWindow
from selfdrive.hardware import TICI from selfdrive.hardware import AGNOS
from selfdrive.swaglog import cloudlog, add_file_handler from selfdrive.swaglog import cloudlog, add_file_handler
from selfdrive.version import is_dirty from selfdrive.version import is_dirty
MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9
CACHE_DIR = Path("/data/scons_cache" if TICI else "/tmp/scons_cache") CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache")
TOTAL_SCONS_NODES = 2405 TOTAL_SCONS_NODES = 2035
MAX_BUILD_PROGRESS = 100 MAX_BUILD_PROGRESS = 100
PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt'))

@ -2,7 +2,7 @@ import os
from cereal import car from cereal import car
from common.params import Params from common.params import Params
from selfdrive.hardware import TICI, PC from selfdrive.hardware import PC
from selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess from selfdrive.manager.process import PythonProcess, NativeProcess, DaemonProcess
WEBCAM = os.getenv("USE_WEBCAM") is not None WEBCAM = os.getenv("USE_WEBCAM") is not None
@ -21,16 +21,16 @@ procs = [
DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"),
# due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption
NativeProcess("camerad", "selfdrive/camerad", ["./camerad"], unkillable=True, callback=driverview), NativeProcess("camerad", "selfdrive/camerad", ["./camerad"], unkillable=True, callback=driverview),
NativeProcess("clocksd", "selfdrive/clocksd", ["./clocksd"]), NativeProcess("clocksd", "system/clocksd", ["./clocksd"]),
NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview),
NativeProcess("logcatd", "selfdrive/logcatd", ["./logcatd"]), NativeProcess("logcatd", "system/logcatd", ["./logcatd"]),
NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]),
NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging),
NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]),
NativeProcess("proclogd", "selfdrive/proclogd", ["./proclogd"]), NativeProcess("proclogd", "system/proclogd", ["./proclogd"]),
NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC), NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC),
NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)),
NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if TICI else None)), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)),
NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], offroad=True), NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], offroad=True),
NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]),
NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False),
@ -45,7 +45,7 @@ procs = [
PythonProcess("plannerd", "selfdrive.controls.plannerd"), PythonProcess("plannerd", "selfdrive.controls.plannerd"),
PythonProcess("radard", "selfdrive.controls.radard"), PythonProcess("radard", "selfdrive.controls.radard"),
PythonProcess("thermald", "selfdrive.thermald.thermald", offroad=True), PythonProcess("thermald", "selfdrive.thermald.thermald", offroad=True),
PythonProcess("timezoned", "selfdrive.timezoned", enabled=TICI, offroad=True), PythonProcess("timezoned", "selfdrive.timezoned", enabled=not PC, offroad=True),
PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, offroad=True), PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, offroad=True),
PythonProcess("updated", "selfdrive.updated", enabled=not PC, onroad=False, offroad=True), PythonProcess("updated", "selfdrive.updated", enabled=not PC, onroad=False, offroad=True),
PythonProcess("uploader", "selfdrive.loggerd.uploader", offroad=True), PythonProcess("uploader", "selfdrive.loggerd.uploader", offroad=True),

@ -5,7 +5,7 @@ import time
import unittest import unittest
import selfdrive.manager.manager as manager import selfdrive.manager.manager as manager
from selfdrive.hardware import TICI, HARDWARE from selfdrive.hardware import AGNOS, HARDWARE
from selfdrive.manager.process import DaemonProcess from selfdrive.manager.process import DaemonProcess
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
@ -50,7 +50,7 @@ class TestManager(unittest.TestCase):
self.assertTrue(state.running, f"{p} not running") self.assertTrue(state.running, f"{p} not running")
exit_code = managed_processes[p].stop(retry=False) exit_code = managed_processes[p].stop(retry=False)
if (TICI and p in ['ui',]): if (AGNOS and p in ['ui',]):
# TODO: make Qt UI exit gracefully # TODO: make Qt UI exit gracefully
continue continue

@ -163,7 +163,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
if (Hardware::TICI()) { if (!Hardware::PC()) {
int ret; int ret;
ret = util::set_realtime_priority(54); ret = util::set_realtime_priority(54);
assert(ret == 0); assert(ret == 0);

@ -23,64 +23,8 @@ To view the architecture of the ONNX networks, you can use [netron](https://netr
* The recurrent state vector that is fed back into the GRU for temporal context : 512 * The recurrent state vector that is fed back into the GRU for temporal context : 512
### Supercombo output format (Full size: 6472 x float32) ### Supercombo output format (Full size: XXX x float32)
* **plan** Read [here](https://github.com/commaai/openpilot/blob/90af436a121164a51da9fa48d093c29f738adf6a/selfdrive/modeld/models/driving.h#L236) for more.
* 5 potential desired plan predictions : 4955 = 5 * 991
* predicted mean and standard deviation of the following values at 33 timesteps : 990 = 2 * 33 * 15
* x,y,z position in current frame (meters)
* x,y,z velocity in local frame (meters/s)
* x,y,z acceleration local frame (meters/(s*s))
* roll, pitch , yaw in current frame (radians)
* roll, pitch , yaw rates in local frame (radians/s)
* probability[^1] of this plan hypothesis being the most likely: 1
* **lanelines**
* 4 lanelines (outer left, left, right, and outer right): 528 = 4 * 132
* predicted mean and standard deviation for the following values at 33 x positions : 132 = 2 * 33 * 2
* y position in current frame (meters)
* z position in current frame (meters)
* **laneline probabilties**
* 2 probabilities[^1] that each of the 4 lanelines exists : 8 = 4 * 2
* deprecated probability
* used probability
* **road-edges**
* 2 road-edges (left and right): 264 = 2 * 132
* predicted mean and standard deviation for the following values at 33 x positions : 132 = 2 * 33 * 2
* y position in current frame (meters)
* z position in current frame (meters)
* **leads**
* 2 hypotheses for potential lead cars : 102 = 2 * 51
* predicted mean and stadard deviation for the following values at 0,2,4,6,8,10s : 48 = 2 * 6 * 4
* x position of lead in current frame (meters)
* y position of lead in current frame (meters)
* speed of lead (meters/s)
* acceleration of lead(meters/(s*s))
* probabilities[^1] this hypothesis is the most likely hypothesis at 0s, 2s or 4s from now : 3
* **lead probabilities**
* probability[^1] that there is a lead car at 0s, 2s, 4s from now : 3 = 1 * 3
* **desire state**
* probability[^1] that the model thinks it is executing each of the 8 potential desire actions : 8
* **meta** [^2]
* Various metadata about the scene : 80 = 1 + 35 + 12 + 32
* Probability[^1] that openpilot is engaged : 1
* Probabilities[^1] of various things happening between now and 2,4,6,8,10s : 35 = 5 * 7
* Disengage of openpilot with gas pedal
* Disengage of openpilot with brake pedal
* Override of openpilot steering
* 3m/(s*s) of deceleration
* 4m/(s*s) of deceleration
* 5m/(s*s) of deceleration
* Probabilities[^1] of left or right blinker being active at 0,2,4,6,8,10s : 12 = 6 * 2
* Probabilities[^1] that each of the 8 desires is being executed at 0,2,4,6s : 32 = 4 * 8
* **pose** [^2]
* predicted mean and standard deviation of current translation and rotation rates : 12 = 2 * 6
* x,y,z velocity in current frame (meters/s)
* roll, pitch , yaw rates in current frame (radians/s)
* **recurrent state**
* The recurrent state vector that is fed back into the GRU for temporal context : 512
[^1]: All probabilities are in logits, so you need to apply sigmoid or softmax functions to get actual probabilities
[^2]: These outputs come directly from the vision blocks, they do not have access to temporal state or the desire input
## Driver Monitoring Model ## Driver Monitoring Model

@ -69,22 +69,13 @@ void crop_nv12_to_yuv(uint8_t *raw, int stride, int uv_offset, uint8_t *y, uint8
} }
DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height, int stride, int uv_offset, float *calib) { DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height, int stride, int uv_offset, float *calib) {
Rect crop_rect; const int cropped_height = tici_dm_crop::width / 1.33;
if (width == TICI_CAM_WIDTH) { Rect crop_rect = {width / 2 - tici_dm_crop::width / 2 + tici_dm_crop::x_offset,
const int cropped_height = tici_dm_crop::width / 1.33; height / 2 - cropped_height / 2 + tici_dm_crop::y_offset,
crop_rect = {width / 2 - tici_dm_crop::width / 2 + tici_dm_crop::x_offset, cropped_height / 2,
height / 2 - cropped_height / 2 + tici_dm_crop::y_offset, cropped_height};
cropped_height / 2, if (!s->is_rhd) {
cropped_height}; crop_rect.x += tici_dm_crop::width - crop_rect.w;
if (!s->is_rhd) {
crop_rect.x += tici_dm_crop::width - crop_rect.w;
}
} else {
const int adapt_width = 372;
crop_rect = {0, 0, adapt_width, height};
if (!s->is_rhd) {
crop_rect.x += width - crop_rect.w;
}
} }
int resized_width = MODEL_WIDTH; int resized_width = MODEL_WIDTH;
@ -108,31 +99,16 @@ DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_
auto [resized_buf, resized_u, resized_v] = get_yuv_buf(s->resized_buf, resized_width, resized_height); auto [resized_buf, resized_u, resized_v] = get_yuv_buf(s->resized_buf, resized_width, resized_height);
uint8_t *resized_y = resized_buf; uint8_t *resized_y = resized_buf;
libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBilinear; libyuv::FilterMode mode = libyuv::FilterModeEnum::kFilterBilinear;
if (Hardware::TICI()) { libyuv::I420Scale(cropped_y, crop_rect.w,
libyuv::I420Scale(cropped_y, crop_rect.w, cropped_u, crop_rect.w / 2,
cropped_u, crop_rect.w / 2, cropped_v, crop_rect.w / 2,
cropped_v, crop_rect.w / 2, crop_rect.w, crop_rect.h,
crop_rect.w, crop_rect.h, resized_y, resized_width,
resized_y, resized_width, resized_u, resized_width / 2,
resized_u, resized_width / 2, resized_v, resized_width / 2,
resized_v, resized_width / 2, resized_width, resized_height,
resized_width, resized_height, mode);
mode);
} else {
const int source_height = 0.7*resized_height;
const int extra_height = (resized_height - source_height) / 2;
const int extra_width = (resized_width - source_height / 2) / 2;
const int source_width = source_height / 2 + extra_width;
libyuv::I420Scale(cropped_y, crop_rect.w,
cropped_u, crop_rect.w / 2,
cropped_v, crop_rect.w / 2,
crop_rect.w, crop_rect.h,
resized_y + extra_height * resized_width, resized_width,
resized_u + extra_height / 2 * resized_width / 2, resized_width / 2,
resized_v + extra_height / 2 * resized_width / 2, resized_width / 2,
source_width, source_height,
mode);
}
int yuv_buf_len = (MODEL_WIDTH/2) * (MODEL_HEIGHT/2) * 6; // Y|u|v -> y|y|y|y|u|v int yuv_buf_len = (MODEL_WIDTH/2) * (MODEL_HEIGHT/2) * 6; // Y|u|v -> y|y|y|y|u|v
float *net_input_buf = get_buffer(s->net_input_buf, yuv_buf_len); float *net_input_buf = get_buffer(s->net_input_buf, yuv_buf_len);

@ -59,7 +59,10 @@ if __name__ == "__main__":
options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
provider = 'CPUExecutionProvider' provider = 'CPUExecutionProvider'
print("Onnx selected provider: ", [provider], file=sys.stderr) try:
ort_session = ort.InferenceSession(sys.argv[1], options, providers=[provider]) print("Onnx selected provider: ", [provider], file=sys.stderr)
print("Onnx using ", ort_session.get_providers(), file=sys.stderr) ort_session = ort.InferenceSession(sys.argv[1], options, providers=[provider])
run_loop(ort_session) print("Onnx using ", ort_session.get_providers(), file=sys.stderr)
run_loop(ort_session)
except KeyboardInterrupt:
pass

@ -3,7 +3,6 @@ from math import atan2
from cereal import car from cereal import car
from common.numpy_fast import interp from common.numpy_fast import interp
from common.realtime import DT_DMON from common.realtime import DT_DMON
from selfdrive.hardware import TICI
from common.filter_simple import FirstOrderFilter from common.filter_simple import FirstOrderFilter
from common.stat_live import RunningStatFilter from common.stat_live import RunningStatFilter
@ -16,7 +15,7 @@ EventName = car.CarEvent.EventName
# ****************************************************************************************** # ******************************************************************************************
class DRIVER_MONITOR_SETTINGS(): class DRIVER_MONITOR_SETTINGS():
def __init__(self, TICI=TICI, DT_DMON=DT_DMON): def __init__(self):
self._DT_DMON = DT_DMON self._DT_DMON = DT_DMON
# ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2 # ref (page15-16): https://eur-lex.europa.eu/legal-content/EN/TXT/PDF/?uri=CELEX:42018X1947&rid=2
self._AWARENESS_TIME = 30. # passive wheeltouch total timeout self._AWARENESS_TIME = 30. # passive wheeltouch total timeout
@ -27,15 +26,15 @@ class DRIVER_MONITOR_SETTINGS():
self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6. self._DISTRACTED_PROMPT_TIME_TILL_TERMINAL = 6.
self._FACE_THRESHOLD = 0.5 self._FACE_THRESHOLD = 0.5
self._PARTIAL_FACE_THRESHOLD = 0.8 if TICI else 0.45 self._PARTIAL_FACE_THRESHOLD = 0.8
self._EYE_THRESHOLD = 0.65 if TICI else 0.6 self._EYE_THRESHOLD = 0.65
self._SG_THRESHOLD = 0.925 if TICI else 0.91 self._SG_THRESHOLD = 0.925
self._BLINK_THRESHOLD = 0.8 if TICI else 0.55 self._BLINK_THRESHOLD = 0.8
self._BLINK_THRESHOLD_SLACK = 0.9 if TICI else 0.7 self._BLINK_THRESHOLD_SLACK = 0.9
self._BLINK_THRESHOLD_STRICT = self._BLINK_THRESHOLD self._BLINK_THRESHOLD_STRICT = self._BLINK_THRESHOLD
self._EE_THRESH11 = 0.75 if TICI else 0.4 self._EE_THRESH11 = 0.75
self._EE_THRESH12 = 3.25 if TICI else 2.45 self._EE_THRESH12 = 3.25
self._EE_THRESH21 = 0.01 self._EE_THRESH21 = 0.01
self._EE_THRESH22 = 0.35 self._EE_THRESH22 = 0.35

@ -2,18 +2,24 @@ from __future__ import annotations
import json import json
import math import math
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple, Union, cast
from common.conversions import Conversions
from common.numpy_fast import clip from common.numpy_fast import clip
from common.params import Params from common.params import Params
EARTH_MEAN_RADIUS = 6371007.2 EARTH_MEAN_RADIUS = 6371007.2
SPEED_CONVERSIONS = {
'km/h': Conversions.KPH_TO_MS,
'mph': Conversions.MPH_TO_MS,
}
class Coordinate: class Coordinate:
def __init__(self, latitude: float, longitude: float) -> None: def __init__(self, latitude: float, longitude: float) -> None:
self.latitude = latitude self.latitude = latitude
self.longitude = longitude self.longitude = longitude
self.annotations: Dict[str, float] = {}
@classmethod @classmethod
def from_mapbox_tuple(cls, t: Tuple[float, float]) -> Coordinate: def from_mapbox_tuple(cls, t: Tuple[float, float]) -> Coordinate:
@ -116,6 +122,12 @@ def string_to_direction(direction: str) -> str:
return 'none' return 'none'
def maxspeed_to_ms(maxspeed: Dict[str, Union[str, float]]) -> float:
unit = cast(str, maxspeed['unit'])
speed = cast(float, maxspeed['speed'])
return SPEED_CONVERSIONS[unit] * speed
def parse_banner_instructions(instruction: Any, banners: Any, distance_to_maneuver: float = 0.0) -> None: def parse_banner_instructions(instruction: Any, banners: Any, distance_to_maneuver: float = 0.0) -> None:
if not len(banners): if not len(banners):
return return

@ -10,11 +10,11 @@ from cereal import log
from common.api import Api from common.api import Api
from common.params import Params from common.params import Params
from common.realtime import Ratekeeper from common.realtime import Ratekeeper
from selfdrive.swaglog import cloudlog
from selfdrive.navd.helpers import (Coordinate, coordinate_from_param, from selfdrive.navd.helpers import (Coordinate, coordinate_from_param,
distance_along_geometry, distance_along_geometry, maxspeed_to_ms,
minimum_distance, minimum_distance,
parse_banner_instructions) parse_banner_instructions)
from selfdrive.swaglog import cloudlog
REROUTE_DISTANCE = 25 REROUTE_DISTANCE = 25
MANEUVER_TRANSITION_THRESHOLD = 10 MANEUVER_TRANSITION_THRESHOLD = 10
@ -110,7 +110,7 @@ class RouteEngine:
params = { params = {
'access_token': self.mapbox_token, 'access_token': self.mapbox_token,
# 'annotations': 'maxspeed', 'annotations': 'maxspeed',
'geometries': 'geojson', 'geometries': 'geojson',
'overview': 'full', 'overview': 'full',
'steps': 'true', 'steps': 'true',
@ -131,9 +131,25 @@ class RouteEngine:
self.route = r['routes'][0]['legs'][0]['steps'] self.route = r['routes'][0]['legs'][0]['steps']
self.route_geometry = [] self.route_geometry = []
maxspeed_idx = 0
maxspeeds = r['routes'][0]['legs'][0]['annotation']['maxspeed']
# Convert coordinates # Convert coordinates
for step in self.route: for step in self.route:
self.route_geometry.append([Coordinate.from_mapbox_tuple(c) for c in step['geometry']['coordinates']]) coords = []
for c in step['geometry']['coordinates']:
coord = Coordinate.from_mapbox_tuple(c)
# Last step does not have maxspeed
if (maxspeed_idx < len(maxspeeds)) and ('unknown' not in maxspeeds[maxspeed_idx]):
coord.annotations['maxspeed'] = maxspeed_to_ms(maxspeeds[maxspeed_idx])
coords.append(coord)
maxspeed_idx += 1
self.route_geometry.append(coords)
maxspeed_idx -= 1 # Every segment ends with the same coordinate as the start of the next
self.step_idx = 0 self.step_idx = 0
else: else:
@ -179,6 +195,23 @@ class RouteEngine:
msg.navInstruction.timeRemaining = total_time msg.navInstruction.timeRemaining = total_time
msg.navInstruction.timeRemainingTypical = total_time_typical msg.navInstruction.timeRemainingTypical = total_time_typical
# Speed limit
closest_idx, closest = min(enumerate(geometry), key=lambda p: p[1].distance_to(self.last_position))
if closest_idx > 0:
# If we are not past the closest point, show previous
if along_geometry < distance_along_geometry(geometry, geometry[closest_idx]):
closest = geometry[closest_idx - 1]
if 'maxspeed' in closest.annotations:
msg.navInstruction.speedLimit = closest.annotations['maxspeed']
# Speed limit sign type
if 'speedLimitSign' in step:
if step['speedLimitSign'] == 'mutcd':
msg.navInstruction.speedLimitSign = log.NavInstruction.SpeedLimitSign.mutcd
elif step['speedLimitSign'] == 'vienna':
msg.navInstruction.speedLimitSign = log.NavInstruction.SpeedLimitSign.vienna
self.pm.send('navInstruction', msg) self.pm.send('navInstruction', msg)
# Transition to next route segment # Transition to next route segment

@ -23,4 +23,4 @@ class OpenpilotPrefix(object):
os.remove(symlink_path) os.remove(symlink_path)
shutil.rmtree(self.msgq_path, ignore_errors=True) shutil.rmtree(self.msgq_path, ignore_errors=True)
del os.environ['OPENPILOT_PREFIX'] del os.environ['OPENPILOT_PREFIX']
return True return False

@ -10,9 +10,8 @@ import cereal.messaging as messaging
from cereal.visionipc import VisionIpcServer, VisionStreamType from cereal.visionipc import VisionIpcServer, VisionStreamType
from common.spinner import Spinner from common.spinner import Spinner
from common.timeout import Timeout from common.timeout import Timeout
from common.transformations.camera import get_view_frame_from_road_frame, eon_f_frame_size, tici_f_frame_size, \ from common.transformations.camera import get_view_frame_from_road_frame, tici_f_frame_size, tici_d_frame_size
eon_d_frame_size, tici_d_frame_size from selfdrive.hardware import PC
from selfdrive.hardware import PC, TICI
from selfdrive.manager.process_config import managed_processes from selfdrive.manager.process_config import managed_processes
from selfdrive.test.openpilotci import BASE_URL, get_url from selfdrive.test.openpilotci import BASE_URL, get_url
from selfdrive.test.process_replay.compare_logs import compare_logs, save_log from selfdrive.test.process_replay.compare_logs import compare_logs, save_log
@ -23,14 +22,14 @@ from tools.lib.logreader import LogReader
TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"
SEGMENT = 0 SEGMENT = 0
MAX_FRAMES = 20 if PC else 1300
SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0"))
VIPC_STREAM = {"roadCameraState": VisionStreamType.VISION_STREAM_ROAD, "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER, VIPC_STREAM = {"roadCameraState": VisionStreamType.VISION_STREAM_ROAD, "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER,
"wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD} "wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD}
def get_log_fn(ref_commit, test_route):
def get_log_fn(ref_commit, test_route, tici=True): return f"{test_route}_model_tici_{ref_commit}.bz2"
return f"{test_route}_{'model_tici' if tici else 'model'}_{ref_commit}.bz2"
def replace_calib(msg, calib): def replace_calib(msg, calib):
@ -41,12 +40,15 @@ def replace_calib(msg, calib):
def model_replay(lr, frs): def model_replay(lr, frs):
spinner = Spinner() if not PC:
spinner.update("starting model replay") spinner = Spinner()
spinner.update("starting model replay")
else:
spinner = None
vipc_server = VisionIpcServer("camerad") vipc_server = VisionIpcServer("camerad")
vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *(tici_f_frame_size if TICI else eon_f_frame_size)) vipc_server.create_buffers(VisionStreamType.VISION_STREAM_ROAD, 40, False, *(tici_f_frame_size))
vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size if TICI else eon_d_frame_size)) vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size))
vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *(tici_f_frame_size)) vipc_server.create_buffers(VisionStreamType.VISION_STREAM_WIDE_ROAD, 40, False, *(tici_f_frame_size))
vipc_server.start_listener() vipc_server.start_listener()
@ -76,7 +78,7 @@ def model_replay(lr, frs):
for cam_msgs in zip_longest(msgs['roadCameraState'], msgs['wideRoadCameraState'], msgs['driverCameraState']): for cam_msgs in zip_longest(msgs['roadCameraState'], msgs['wideRoadCameraState'], msgs['driverCameraState']):
# need a pair of road/wide msgs # need a pair of road/wide msgs
if TICI and None in (cam_msgs[0], cam_msgs[1]): if None in (cam_msgs[0], cam_msgs[1]):
break break
for msg in cam_msgs: for msg in cam_msgs:
@ -107,7 +109,7 @@ def model_replay(lr, frs):
recv = None recv = None
if msg.which() in ('roadCameraState', 'wideRoadCameraState'): if msg.which() in ('roadCameraState', 'wideRoadCameraState'):
if not TICI or min(frame_idxs['roadCameraState'], frame_idxs['wideRoadCameraState']) > recv_cnt['modelV2']: if min(frame_idxs['roadCameraState'], frame_idxs['wideRoadCameraState']) > recv_cnt['modelV2']:
recv = "modelV2" recv = "modelV2"
elif msg.which() == 'driverCameraState': elif msg.which() == 'driverCameraState':
recv = "driverState" recv = "driverState"
@ -118,14 +120,19 @@ def model_replay(lr, frs):
recv_cnt[recv] += 1 recv_cnt[recv] += 1
log_msgs.append(messaging.recv_one(sm.sock[recv])) log_msgs.append(messaging.recv_one(sm.sock[recv]))
spinner.update("replaying models: road %d/%d, driver %d/%d" % (frame_idxs['roadCameraState'], if spinner:
frs['roadCameraState'].frame_count, frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count)) spinner.update("replaying models: road %d/%d, driver %d/%d" % (frame_idxs['roadCameraState'],
frs['roadCameraState'].frame_count, frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count))
if any(frame_idxs[c] >= frs[c].frame_count for c in frame_idxs.keys()): if any(frame_idxs[c] >= frs[c].frame_count for c in frame_idxs.keys()) or frame_idxs['roadCameraState'] == MAX_FRAMES:
break break
else:
print(f'Received {frame_idxs["roadCameraState"]} frames')
finally: finally:
spinner.close() if spinner:
spinner.close()
managed_processes['modeld'].stop() managed_processes['modeld'].stop()
managed_processes['dmonitoringmodeld'].stop() managed_processes['dmonitoringmodeld'].stop()
@ -144,9 +151,8 @@ if __name__ == "__main__":
frs = { frs = {
'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="fcamera")), 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="fcamera")),
'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="dcamera")), 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="dcamera")),
'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="ecamera"))
} }
if TICI:
frs['wideRoadCameraState'] = FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="ecamera"))
# run replay # run replay
log_msgs = model_replay(lr, frs) log_msgs = model_replay(lr, frs)
@ -156,9 +162,9 @@ if __name__ == "__main__":
if not update: if not update:
with open(ref_commit_fn) as f: with open(ref_commit_fn) as f:
ref_commit = f.read().strip() ref_commit = f.read().strip()
log_fn = get_log_fn(ref_commit, TEST_ROUTE, tici=TICI) log_fn = get_log_fn(ref_commit, TEST_ROUTE)
try: try:
cmp_log = LogReader(BASE_URL + log_fn) cmp_log = list(LogReader(BASE_URL + log_fn))[:2*MAX_FRAMES]
ignore = [ ignore = [
'logMonoTime', 'logMonoTime',
@ -167,7 +173,8 @@ if __name__ == "__main__":
'driverState.modelExecutionTime', 'driverState.modelExecutionTime',
'driverState.dspExecutionTime' 'driverState.dspExecutionTime'
] ]
tolerance = None if not PC else 1e-3 # TODO this tolerence is absurdly large
tolerance = 5e-1 if PC else None
results: Any = {TEST_ROUTE: {}} results: Any = {TEST_ROUTE: {}}
results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore)
diff1, diff2, failed = format_diff(results, ref_commit) diff1, diff2, failed = format_diff(results, ref_commit)
@ -182,13 +189,13 @@ if __name__ == "__main__":
failed = True failed = True
# upload new refs # upload new refs
if update or failed: if (update or failed) and not PC:
from selfdrive.test.openpilotci import upload_file from selfdrive.test.openpilotci import upload_file
print("Uploading new refs") print("Uploading new refs")
new_commit = get_commit() new_commit = get_commit()
log_fn = get_log_fn(new_commit, TEST_ROUTE, tici=TICI) log_fn = get_log_fn(new_commit, TEST_ROUTE)
save_log(log_fn, log_msgs) save_log(log_fn, log_msgs)
try: try:
upload_file(log_fn, os.path.basename(log_fn)) upload_file(log_fn, os.path.basename(log_fn))

@ -242,14 +242,17 @@ CONFIGS = [
pub_sub={ pub_sub={
"can": ["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"], "can": ["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"],
"deviceState": [], "pandaStates": [], "peripheralState": [], "liveCalibration": [], "driverMonitoringState": [], "longitudinalPlan": [], "lateralPlan": [], "liveLocationKalman": [], "liveParameters": [], "radarState": [], "deviceState": [], "pandaStates": [], "peripheralState": [], "liveCalibration": [], "driverMonitoringState": [], "longitudinalPlan": [], "lateralPlan": [], "liveLocationKalman": [], "liveParameters": [], "radarState": [],
"modelV2": [], "driverCameraState": [], "roadCameraState": [], "managerState": [], "testJoystick": [], "modelV2": [], "driverCameraState": [], "roadCameraState": [], "wideRoadCameraState": [], "managerState": [], "testJoystick": [],
}, },
ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"],
init_callback=fingerprint, init_callback=fingerprint,
should_recv_callback=controlsd_rcv_callback, should_recv_callback=controlsd_rcv_callback,
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
fake_pubsubmaster=True, fake_pubsubmaster=True,
submaster_config={'ignore_avg_freq': ['radarState', 'longitudinalPlan']} submaster_config={
'ignore_avg_freq': ['radarState', 'longitudinalPlan', 'driverCameraState', 'driverMonitoringState'], # dcam is expected at 20 Hz
'ignore_alive': ['wideRoadCameraState'], # TODO: Add to regen
}
), ),
ProcessConfig( ProcessConfig(
proc_name="radard", proc_name="radard",

@ -1 +1 @@
0956446adfa91506f0a3d88f893e041bfb2890c1 0e18bb3317437f2cad6d0a5782a9222eda655d58

@ -3,8 +3,8 @@ import bz2
import os import os
import time import time
import multiprocessing import multiprocessing
from tqdm import tqdm
import argparse import argparse
from tqdm import tqdm
# run DM procs # run DM procs
os.environ["USE_WEBCAM"] = "1" os.environ["USE_WEBCAM"] = "1"
@ -24,7 +24,6 @@ from tools.lib.route import Route
from tools.lib.framereader import FrameReader from tools.lib.framereader import FrameReader
from tools.lib.logreader import LogReader from tools.lib.logreader import LogReader
def replay_panda_states(s, msgs): def replay_panda_states(s, msgs):
pm = messaging.PubMaster([s, 'peripheralState']) pm = messaging.PubMaster([s, 'peripheralState'])
rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None) rk = Ratekeeper(service_list[s].frequency, print_delay_threshold=None)
@ -118,7 +117,7 @@ def replay_service(s, msgs):
rk.keep_time() rk.keep_time()
def replay_cameras(lr, frs): def replay_cameras(lr, frs, disable_tqdm=False):
eon_cameras = [ eon_cameras = [
("roadCameraState", DT_MDL, eon_f_frame_size, VisionStreamType.VISION_STREAM_ROAD, True), ("roadCameraState", DT_MDL, eon_f_frame_size, VisionStreamType.VISION_STREAM_ROAD, True),
("driverCameraState", DT_DMON, eon_d_frame_size, VisionStreamType.VISION_STREAM_DRIVER, False), ("driverCameraState", DT_DMON, eon_d_frame_size, VisionStreamType.VISION_STREAM_DRIVER, False),
@ -163,7 +162,7 @@ def replay_cameras(lr, frs):
if fr is not None: if fr is not None:
print(f"Decompressing frames {s}") print(f"Decompressing frames {s}")
frames = [] frames = []
for i in tqdm(range(fr.frame_count)): for i in tqdm(range(fr.frame_count), disable=disable_tqdm):
img = fr.get(i, pix_fmt='nv12')[0] img = fr.get(i, pix_fmt='nv12')[0]
frames.append(img.flatten().tobytes()) frames.append(img.flatten().tobytes())
@ -177,7 +176,7 @@ def replay_cameras(lr, frs):
return vs, p return vs, p
def regen_segment(lr, frs=None, outdir=FAKEDATA): def regen_segment(lr, frs=None, outdir=FAKEDATA, disable_tqdm=False):
lr = list(lr) lr = list(lr)
if frs is None: if frs is None:
frs = dict() frs = dict()
@ -207,7 +206,7 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA):
elif msg.which() == 'liveCalibration': elif msg.which() == 'liveCalibration':
params.put("CalibrationParams", msg.as_builder().to_bytes()) params.put("CalibrationParams", msg.as_builder().to_bytes())
vs, cam_procs = replay_cameras(lr, frs) vs, cam_procs = replay_cameras(lr, frs, disable_tqdm=disable_tqdm)
fake_daemons = { fake_daemons = {
'sensord': [ 'sensord': [
@ -219,7 +218,7 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA):
multiprocessing.Process(target=replay_panda_states, args=('pandaStates', lr)), multiprocessing.Process(target=replay_panda_states, args=('pandaStates', lr)),
], ],
'managerState': [ 'managerState': [
multiprocessing.Process(target=replay_manager_state, args=('managerState', lr)), multiprocessing.Process(target=replay_manager_state, args=('managerState', lr)),
], ],
'thermald': [ 'thermald': [
multiprocessing.Process(target=replay_device_state, args=('deviceState', lr)), multiprocessing.Process(target=replay_device_state, args=('deviceState', lr)),
@ -236,13 +235,13 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA):
time.sleep(5) time.sleep(5)
# start procs up # start procs up
ignore = list(fake_daemons.keys()) + ['ui', 'manage_athenad', 'uploader'] ignore = list(fake_daemons.keys()) + ['ui', 'manage_athenad', 'uploader', 'soundd']
ensure_running(managed_processes.values(), started=True, params=Params(), CP=car.CarParams(), not_run=ignore) ensure_running(managed_processes.values(), started=True, params=Params(), CP=car.CarParams(), not_run=ignore)
for procs in fake_daemons.values(): for procs in fake_daemons.values():
for p in procs: for p in procs:
p.start() p.start()
for _ in tqdm(range(60)): for _ in tqdm(range(60), disable=disable_tqdm):
# ensure all procs are running # ensure all procs are running
for d, procs in fake_daemons.items(): for d, procs in fake_daemons.items():
for p in procs: for p in procs:
@ -268,7 +267,7 @@ def regen_segment(lr, frs=None, outdir=FAKEDATA):
return seg_path return seg_path
def regen_and_save(route, sidx, upload=False, use_route_meta=False): def regen_and_save(route, sidx, upload=False, use_route_meta=False, outdir=FAKEDATA, disable_tqdm=False):
if use_route_meta: if use_route_meta:
r = Route(args.route) r = Route(args.route)
lr = LogReader(r.log_paths()[args.seg]) lr = LogReader(r.log_paths()[args.seg])
@ -276,7 +275,7 @@ def regen_and_save(route, sidx, upload=False, use_route_meta=False):
else: else:
lr = LogReader(f"cd:/{route.replace('|', '/')}/{sidx}/rlog.bz2") lr = LogReader(f"cd:/{route.replace('|', '/')}/{sidx}/rlog.bz2")
fr = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/fcamera.hevc") fr = FrameReader(f"cd:/{route.replace('|', '/')}/{sidx}/fcamera.hevc")
rpath = regen_segment(lr, {'roadCameraState': fr}) rpath = regen_segment(lr, {'roadCameraState': fr}, outdir=outdir, disable_tqdm=disable_tqdm)
# compress raw rlog before uploading # compress raw rlog before uploading
with open(os.path.join(rpath, "rlog"), "rb") as f: with open(os.path.join(rpath, "rlog"), "rb") as f:
@ -294,7 +293,7 @@ def regen_and_save(route, sidx, upload=False, use_route_meta=False):
print("\n\n", "*"*30, "\n\n") print("\n\n", "*"*30, "\n\n")
print("New route:", relr, "\n") print("New route:", relr, "\n")
if upload: if upload:
upload_route(relr) upload_route(relr, exclude_patterns=['*.hevc', ])
return relr return relr

@ -1,22 +1,36 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse
import concurrent.futures
import os
import random
from tqdm import tqdm
from selfdrive.test.process_replay.helpers import OpenpilotPrefix
from selfdrive.test.process_replay.regen import regen_and_save from selfdrive.test.process_replay.regen import regen_and_save
from selfdrive.test.process_replay.test_processes import original_segments as segments from selfdrive.test.process_replay.test_processes import FAKEDATA, original_segments as segments
if __name__ == "__main__": def regen_job(segment):
new_segments = [] with OpenpilotPrefix():
for segment in segments:
route = segment[1].rsplit('--', 1)[0] route = segment[1].rsplit('--', 1)[0]
sidx = int(segment[1].rsplit('--', 1)[1]) sidx = int(segment[1].rsplit('--', 1)[1])
print("Regen", route, sidx) fake_dongle_id = 'regen' + ''.join(random.choice('0123456789ABCDEF') for i in range(11))
relr = regen_and_save(route, sidx, upload=True, use_route_meta=False) try:
relr = regen_and_save(route, sidx, upload=True, use_route_meta=False, outdir=os.path.join(FAKEDATA, fake_dongle_id), disable_tqdm=True)
relr = '|'.join(relr.split('/')[-2:])
return f' ("{segment[0]}", "{relr}"), '
except Exception as e:
return f" {segment} failed: {str(e)}"
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate new segments from old ones")
parser.add_argument("-j", "--jobs", type=int, default=1)
args = parser.parse_args()
print("\n\n", "*"*30, "\n\n") with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool:
print("New route:", relr, "\n") p = list(pool.map(regen_job, segments))
relr = relr.replace('/', '|') msg = "Copy these new segments into test_processes.py:"
new_segments.append(f' ("{segment[0]}", "{relr}"), ') for seg in tqdm(p, desc="Generating segments"):
print() msg += "\n" + str(seg)
print() print()
print() print()
print('COPY THIS INTO test_processes.py') print(msg)
for seg in new_segments:
print(seg)

@ -59,9 +59,9 @@ REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
def run_test_process(data): def run_test_process(data):
segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data segment, cfg, args, cur_log_fn, ref_log_path, lr_dat = data
lr = LogReader.from_bytes(lr_dat)
res = None res = None
if not args.upload_only: 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, args.ignore_fields, args.ignore_msgs)
# save logs so we can upload when updating refs # save logs so we can upload when updating refs
save_log(cur_log_fn, log_msgs) save_log(cur_log_fn, log_msgs)
@ -160,7 +160,7 @@ if __name__ == "__main__":
tested_cars = set(args.whitelist_cars) - set(args.blacklist_cars) tested_cars = set(args.whitelist_cars) - set(args.blacklist_cars)
tested_cars = {c.upper() for c in tested_cars} tested_cars = {c.upper() for c in tested_cars}
full_test = all(len(x) == 0 for x in (args.whitelist_procs, args.whitelist_cars, args.blacklist_procs, args.blacklist_cars, args.ignore_fields, args.ignore_msgs)) full_test = (tested_procs == all_procs) and (tested_cars == all_cars) and all(len(x) == 0 for x in (args.ignore_fields, args.ignore_msgs))
upload = args.update_refs or args.upload_only upload = args.update_refs or args.upload_only
os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True) os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True)
@ -181,8 +181,7 @@ if __name__ == "__main__":
# check to make sure all car brands are tested # check to make sure all car brands are tested
if full_test: if full_test:
tested_cars = {c.lower() for c, _ in segments} untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in tested_cars}
untested = (set(interface_names) - set(excluded_interfaces)) - tested_cars
assert len(untested) == 0, f"Cars missing routes: {str(untested)}" assert len(untested) == 0, f"Cars missing routes: {str(untested)}"
with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool:

@ -18,8 +18,12 @@ DEST_KEY = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci")
SOURCE_KEYS = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] SOURCE_KEYS = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES]
SERVICE = BlockBlobService(_DATA_ACCOUNT_CI, sas_token=DEST_KEY) SERVICE = BlockBlobService(_DATA_ACCOUNT_CI, sas_token=DEST_KEY)
def upload_route(path): def upload_route(path, exclude_patterns=None):
if exclude_patterns is None:
exclude_patterns = ['*/dcamera.hevc']
r, n = path.rsplit("--", 1) r, n = path.rsplit("--", 1)
r = '/'.join(r.split('/')[-2:]) # strip out anything extra in the path
destpath = f"{r}/{n}" destpath = f"{r}/{n}"
cmd = [ cmd = [
"azcopy", "azcopy",
@ -28,9 +32,7 @@ def upload_route(path):
f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{DEST_KEY}", f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{DEST_KEY}",
"--recursive=false", "--recursive=false",
"--overwrite=false", "--overwrite=false",
"--exclude-pattern=*/dcamera.hevc", ] + [f"--exclude-pattern={p}" for p in exclude_patterns]
"--exclude-pattern=*.mkv",
]
subprocess.check_call(cmd) subprocess.check_call(cmd)
def sync_to_ci_public(route): def sync_to_ci_public(route):

@ -17,7 +17,7 @@ from common.filter_simple import FirstOrderFilter
from common.params import Params from common.params import Params
from common.realtime import DT_TRML, sec_since_boot from common.realtime import DT_TRML, sec_since_boot
from selfdrive.controls.lib.alertmanager import set_offroad_alert from selfdrive.controls.lib.alertmanager import set_offroad_alert
from selfdrive.hardware import HARDWARE, TICI from selfdrive.hardware import HARDWARE, TICI, AGNOS
from selfdrive.loggerd.config import get_available_percent from selfdrive.loggerd.config import get_available_percent
from selfdrive.statsd import statlog from selfdrive.statsd import statlog
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
@ -31,7 +31,7 @@ NetworkStrength = log.DeviceState.NetworkStrength
CURRENT_TAU = 15. # 15s time constant CURRENT_TAU = 15. # 15s time constant
TEMP_TAU = 5. # 5s time constant TEMP_TAU = 5. # 5s time constant
DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect so you get an alert DISCONNECT_TIMEOUT = 5. # wait 5 seconds before going offroad after disconnect so you get an alert
PANDA_STATES_TIMEOUT = int(1000 * 2.5 * DT_TRML) # 2.5x the expected pandaState frequency PANDA_STATES_TIMEOUT = int(1000 * 1.5 * DT_TRML) # 1.5x the expected pandaState frequency
ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp']) ThermalBand = namedtuple("ThermalBand", ['min_temp', 'max_temp'])
HardwareState = namedtuple("HardwareState", ['network_type', 'network_metered', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps']) HardwareState = namedtuple("HardwareState", ['network_type', 'network_metered', 'network_strength', 'network_info', 'nvme_temps', 'modem_temps'])
@ -113,7 +113,7 @@ def hw_state_thread(end_event, hw_queue):
modem_temps = prev_hw_state.modem_temps modem_temps = prev_hw_state.modem_temps
# Log modem version once # Log modem version once
if TICI and ((modem_version is None) or (modem_nv is None)): if AGNOS and ((modem_version is None) or (modem_nv is None)):
modem_version = HARDWARE.get_modem_version() # pylint: disable=assignment-from-none modem_version = HARDWARE.get_modem_version() # pylint: disable=assignment-from-none
modem_nv = HARDWARE.get_modem_nv() # pylint: disable=assignment-from-none modem_nv = HARDWARE.get_modem_nv() # pylint: disable=assignment-from-none
@ -134,7 +134,7 @@ def hw_state_thread(end_event, hw_queue):
except queue.Full: except queue.Full:
pass pass
if TICI and (hw_state.network_info is not None) and (hw_state.network_info.get('state', None) == "REGISTERED"): if AGNOS and (hw_state.network_info is not None) and (hw_state.network_info.get('state', None) == "REGISTERED"):
registered_count += 1 registered_count += 1
else: else:
registered_count = 0 registered_count = 0
@ -220,6 +220,11 @@ def thermald_thread(end_event, hw_queue):
if TICI: if TICI:
fan_controller = TiciFanController() fan_controller = TiciFanController()
elif (sec_since_boot() - sm.rcv_time['pandaStates']/1e9) > DISCONNECT_TIMEOUT:
if onroad_conditions["ignition"]:
onroad_conditions["ignition"] = False
cloudlog.error("panda timed out onroad")
try: try:
last_hw_state = hw_queue.get_nowait() last_hw_state = hw_queue.get_nowait()
except queue.Empty: except queue.Empty:

@ -9,7 +9,7 @@ import requests
from timezonefinder import TimezoneFinder from timezonefinder import TimezoneFinder
from common.params import Params from common.params import Params
from selfdrive.hardware import TICI from selfdrive.hardware import AGNOS
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
@ -20,7 +20,7 @@ def set_timezone(valid_timezones, timezone):
cloudlog.debug(f"Setting timezone to {timezone}") cloudlog.debug(f"Setting timezone to {timezone}")
try: try:
if TICI: if AGNOS:
tzpath = os.path.join("/usr/share/zoneinfo/", timezone) tzpath = os.path.join("/usr/share/zoneinfo/", timezone)
subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \ subprocess.check_call(f'sudo su -c "ln -snf {tzpath} /data/etc/tmptime && \
mv /data/etc/tmptime /data/etc/localtime"', shell=True) mv /data/etc/tmptime /data/etc/localtime"', shell=True)

@ -10,15 +10,12 @@ import glob
from typing import NoReturn from typing import NoReturn
from common.file_helpers import mkdirs_exists_ok from common.file_helpers import mkdirs_exists_ok
from selfdrive.hardware import TICI
from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.config import ROOT
import selfdrive.sentry as sentry import selfdrive.sentry as sentry
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.version import get_commit from selfdrive.version import get_commit
MAX_SIZE = 100000 * 10 # mal size is 40-100k, allow up to 1M MAX_SIZE = 1_000_000 * 100 # allow up to 100M
if TICI:
MAX_SIZE = MAX_SIZE * 100 # Allow larger size for tici since files contain coredump
MAX_TOMBSTONE_FN_LEN = 62 # 85 - 23 ("<dongle id>/crash/") MAX_TOMBSTONE_FN_LEN = 62 # 85 - 23 ("<dongle id>/crash/")
TOMBSTONE_DIR = "/data/tombstones/" TOMBSTONE_DIR = "/data/tombstones/"

@ -63,12 +63,10 @@ void DriverViewScene::paintEvent(QPaintEvent* event) {
// blackout // blackout
const QColor bg(0, 0, 0, 140); const QColor bg(0, 0, 0, 140);
const QRect& blackout_rect = Hardware::TICI() ? rect() : rect2; const QRect& blackout_rect = rect();
p.fillRect(blackout_rect.adjusted(0, 0, valid_rect.left() - blackout_rect.right(), 0), bg); p.fillRect(blackout_rect.adjusted(0, 0, valid_rect.left() - blackout_rect.right(), 0), bg);
p.fillRect(blackout_rect.adjusted(valid_rect.right() - blackout_rect.left(), 0, 0, 0), bg); p.fillRect(blackout_rect.adjusted(valid_rect.right() - blackout_rect.left(), 0, 0, 0), bg);
if (Hardware::TICI()) { p.fillRect(blackout_rect.adjusted(valid_rect.left()-blackout_rect.left()+1, 0, valid_rect.right()-blackout_rect.right()-1, -valid_rect.height()*7/10), bg); // top dz
p.fillRect(blackout_rect.adjusted(valid_rect.left()-blackout_rect.left()+1, 0, valid_rect.right()-blackout_rect.right()-1, -valid_rect.height()*7/10), bg); // top dz
}
// face bounding box // face bounding box
cereal::DriverState::Reader driver_state = sm["driverState"].getDriverState(); cereal::DriverState::Reader driver_state = sm["driverState"].getDriverState();

@ -57,12 +57,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
"Upload data from the driver facing camera and help improve the driver monitoring algorithm.", "Upload data from the driver facing camera and help improve the driver monitoring algorithm.",
"../assets/offroad/icon_monitoring.png", "../assets/offroad/icon_monitoring.png",
}, },
{
"EndToEndToggle",
"\U0001f96c Disable use of lanelines (Alpha) \U0001f96c",
"In this mode openpilot will ignore lanelines and just drive how it thinks a human would.",
"../assets/offroad/icon_road.png",
},
{ {
"DisengageOnAccelerator", "DisengageOnAccelerator",
"Disengage On Accelerator Pedal", "Disengage On Accelerator Pedal",
@ -95,9 +89,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) {
auto toggle = new ParamControl(param, title, desc, icon, this); auto toggle = new ParamControl(param, title, desc, icon, this);
bool locked = params.getBool((param + "Lock").toStdString()); bool locked = params.getBool((param + "Lock").toStdString());
toggle->setEnabled(!locked); toggle->setEnabled(!locked);
if (!locked) {
connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled);
}
addItem(toggle); addItem(toggle);
} }
} }
@ -162,7 +153,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
power_layout->addWidget(poweroff_btn); power_layout->addWidget(poweroff_btn);
QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff);
if (Hardware::TICI()) { if (!Hardware::PC()) {
connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible); connect(uiState(), &UIState::offroadTransition, poweroff_btn, &QPushButton::setVisible);
} }

@ -311,24 +311,19 @@ void NvgWindow::drawLaneLines(QPainter &painter, const UIState *s) {
// paint path // paint path
QLinearGradient bg(0, height(), 0, height() / 4); QLinearGradient bg(0, height(), 0, height() / 4);
if (scene.end_to_end) { const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation();
const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation(); float orientation_future = 0;
float orientation_future = 0; if (orientation.getZ().size() > 16) {
if (orientation.getZ().size() > 16) { orientation_future = std::abs(orientation.getZ()[16]); // 2.5 seconds
orientation_future = std::abs(orientation.getZ()[16]); // 2.5 seconds
}
// straight: 112, in turns: 70
float curve_hue = fmax(70, 112 - (orientation_future * 420));
// FIXME: painter.drawPolygon can be slow if hue is not rounded
curve_hue = int(curve_hue * 100 + 0.5) / 100;
bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
bg.setColorAt(0.75 / 1.5, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.35));
bg.setColorAt(1.0, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.0));
} else {
bg.setColorAt(0, whiteColor());
bg.setColorAt(1, whiteColor(0));
} }
// straight: 112, in turns: 70
float curve_hue = fmax(70, 112 - (orientation_future * 420));
// FIXME: painter.drawPolygon can be slow if hue is not rounded
curve_hue = int(curve_hue * 100 + 0.5) / 100;
bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4));
bg.setColorAt(0.75 / 1.5, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.35));
bg.setColorAt(1.0, QColor::fromHslF(curve_hue / 360., 1.0, 0.68, 0.0));
painter.setBrush(bg); painter.setBrush(bg);
painter.drawPolygon(scene.track_vertices.v, scene.track_vertices.cnt); painter.drawPolygon(scene.track_vertices.v, scene.track_vertices.cnt);

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

Loading…
Cancel
Save