Merge remote-tracking branch 'upstream/master' into fw_query_consistent

pull/29712/head
Shane Smiskol 2 years ago
commit af55338fec
  1. 2
      .github/workflows/docs.yaml
  2. 26
      .github/workflows/selfdrive_tests.yaml
  3. 1
      .gitignore
  4. 3
      .gitmodules
  5. 6
      .pre-commit-config.yaml
  6. 10
      Jenkinsfile
  7. 6
      RELEASES.md
  8. 2
      cereal
  9. 2
      common/basedir.py
  10. 10
      common/file_helpers.py
  11. 2
      common/kalman/simple_kalman.py
  12. 2
      common/params.cc
  13. 1
      common/params.h
  14. 1
      common/params_pyx.pyx
  15. 1
      common/prefix.py
  16. 30
      common/retry.py
  17. 0
      common/swaglog.py
  18. 2
      common/time.py
  19. 46
      common/xattr.py
  20. 19
      conftest.py
  21. 17
      docs/CARS.md
  22. 8
      docs/c_docs.rst
  23. 5
      launch_chffrplus.sh
  24. 2
      launch_env.sh
  25. 2
      opendbc
  26. 2
      panda
  27. 99
      poetry.lock
  28. 11
      pyproject.toml
  29. 1
      release/build_devel.sh
  30. 47
      release/files_common
  31. 2
      scripts/apply-pr.sh
  32. 39
      scripts/build_small.sh
  33. 27
      scripts/retry.sh
  34. BIN
      selfdrive/assets/sounds/warning_immediate.wav
  35. 7
      selfdrive/athena/athenad.py
  36. 2
      selfdrive/athena/manage_athenad.py
  37. 2
      selfdrive/athena/registration.py
  38. 2
      selfdrive/athena/tests/test_athenad.py
  39. 25
      selfdrive/athena/tests/test_athenad_ping.py
  40. 10
      selfdrive/boardd/boardd.cc
  41. 2
      selfdrive/boardd/pandad.py
  42. 2
      selfdrive/boardd/tests/test_pandad.py
  43. 2
      selfdrive/car/car_helpers.py
  44. 2
      selfdrive/car/disable_ecu.py
  45. 2
      selfdrive/car/ecu_addrs.py
  46. 6
      selfdrive/car/ford/values.py
  47. 2
      selfdrive/car/fw_versions.py
  48. 3
      selfdrive/car/hyundai/interface.py
  49. 14
      selfdrive/car/hyundai/values.py
  50. 2
      selfdrive/car/isotp_parallel_query.py
  51. 1
      selfdrive/car/mazda/values.py
  52. 1
      selfdrive/car/tests/routes.py
  53. 13
      selfdrive/car/tests/test_models.py
  54. 23
      selfdrive/car/toyota/carcontroller.py
  55. 3
      selfdrive/car/toyota/interface.py
  56. 4
      selfdrive/car/toyota/tests/test_toyota.py
  57. 14
      selfdrive/car/toyota/toyotacan.py
  58. 3
      selfdrive/car/toyota/values.py
  59. 2
      selfdrive/car/vin.py
  60. 8
      selfdrive/car/volkswagen/values.py
  61. 72
      selfdrive/controls/controlsd.py
  62. 2
      selfdrive/controls/lib/latcontrol.py
  63. 2
      selfdrive/controls/lib/latcontrol_angle.py
  64. 2
      selfdrive/controls/lib/latcontrol_pid.py
  65. 2
      selfdrive/controls/lib/latcontrol_torque.py
  66. 2
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  67. 2
      selfdrive/controls/lib/longitudinal_planner.py
  68. 4
      selfdrive/controls/lib/tests/test_latcontrol.py
  69. 2
      selfdrive/controls/plannerd.py
  70. 3
      selfdrive/controls/radard.py
  71. 2
      selfdrive/debug/auto_fingerprint.py
  72. 4
      selfdrive/debug/count_events.py
  73. 6
      selfdrive/debug/run_process_on_route.py
  74. 28
      selfdrive/locationd/calibrationd.py
  75. 2
      selfdrive/locationd/helpers.py
  76. 2
      selfdrive/locationd/models/car_kf.py
  77. 2
      selfdrive/locationd/paramsd.py
  78. 1
      selfdrive/locationd/test/test_locationd.py
  79. 2
      selfdrive/locationd/torqued.py
  80. 2
      selfdrive/manager/build.py
  81. 6
      selfdrive/manager/manager.py
  82. 2
      selfdrive/manager/process.py
  83. 3
      selfdrive/manager/process_config.py
  84. 4
      selfdrive/modeld/dmonitoringmodeld.py
  85. 2
      selfdrive/modeld/modeld.py
  86. 2
      selfdrive/modeld/navmodeld.py
  87. 13
      selfdrive/monitoring/dmonitoringd.py
  88. 6
      selfdrive/navd/navd.py
  89. 2
      selfdrive/sentry.py
  90. 2
      selfdrive/statsd.py
  91. 2
      selfdrive/test/cpp_harness.py
  92. 2
      selfdrive/test/docker_build.sh
  93. 2
      selfdrive/test/helpers.py
  94. 16
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  95. 2
      selfdrive/test/process_replay/README.md
  96. 37
      selfdrive/test/process_replay/conftest.py
  97. 150
      selfdrive/test/process_replay/helpers.py
  98. 2
      selfdrive/test/process_replay/migration.py
  99. 20
      selfdrive/test/process_replay/process_replay.py
  100. 2
      selfdrive/test/process_replay/ref_commit
  101. Some files were not shown because too many files have changed in this diff Show More

@ -32,7 +32,7 @@ jobs:
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
- name: Build docs - name: Build docs
run: | run: |
${{ env.RUN }} "apt update && apt install -y doxygen && cd docs && make html" ${{ env.RUN }} "apt update && apt install -y doxygen && cd docs && make -j$(nproc) html"
- uses: actions/checkout@v4 - uses: actions/checkout@v4
if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot'

@ -208,20 +208,30 @@ jobs:
run: | run: |
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
- name: Run replay - name: Run replay
id: run-replay
timeout-minutes: 30 timeout-minutes: 30
run: | run: |
${{ env.RUN }} "CI=1 $PYTEST -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --long-diff && \ ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \
chmod -R 777 /tmp/comma_download_cache" chmod -R 777 /tmp/comma_download_cache && \
coverage combine && \
coverage xml"
- name: Print diff
id: print-diff
if: always()
run: cat selfdrive/test/process_replay/diff.txt
- uses: actions/upload-artifact@v3
if: always()
continue-on-error: true
with:
name: process_replay_diff.txt
path: selfdrive/test/process_replay/diff.txt
- name: Upload reference logs
if: ${{ failure() && steps.print-diff.outcome == 'success' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
run: |
${{ env.RUN }} "unset PYTHONWARNINGS && 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@v3 uses: codecov/codecov-action@v3
with: with:
name: ${{ github.job }} name: ${{ github.job }}
- name: Upload reference logs
if: ${{ failure() && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }}
run: |
${{ env.RUN }} "unset PYTHONWARNINGS && CI=1 AZURE_TOKEN='$AZURE_TOKEN' \
pytest -n auto --dist=loadscope selfdrive/test/process_replay/test_processes.py --upload-only"
regen: regen:
name: regen name: regen

1
.gitignore vendored

@ -46,7 +46,6 @@ selfdrive/boardd/boardd
selfdrive/logcatd/logcatd selfdrive/logcatd/logcatd
selfdrive/mapd/default_speeds_by_region.json selfdrive/mapd/default_speeds_by_region.json
system/proclogd/proclogd system/proclogd/proclogd
selfdrive/ui/_ui
selfdrive/ui/translations/alerts_generated.h selfdrive/ui/translations/alerts_generated.h
selfdrive/ui/translations/tmp selfdrive/ui/translations/tmp
selfdrive/test/longitudinal_maneuvers/out selfdrive/test/longitudinal_maneuvers/out

3
.gitmodules vendored

@ -13,6 +13,9 @@
[submodule "body"] [submodule "body"]
path = body path = body
url = ../../commaai/body.git url = ../../commaai/body.git
[submodule "teleoprtc_repo"]
path = teleoprtc_repo
url = ../../commaai/teleoprtc
[submodule "tinygrad"] [submodule "tinygrad"]
path = tinygrad_repo path = tinygrad_repo
url = https://github.com/geohot/tinygrad.git url = https://github.com/geohot/tinygrad.git

@ -26,7 +26,7 @@ repos:
rev: v2.2.6 rev: v2.2.6
hooks: hooks:
- id: codespell - id: codespell
exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' exclude: '^(third_party/)|(body/)|(cereal/)|(panda/)|(opendbc/)|(rednose/)|(rednose_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)'
args: args:
# if you've got a short variable name that's getting flagged, add it here # if you've got a short variable name that's getting flagged, add it here
- -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints
@ -39,12 +39,12 @@ repos:
language: system language: system
types: [python] types: [python]
args: ['--explicit-package-bases', '--local-partial-types'] args: ['--explicit-package-bases', '--local-partial-types']
exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)'
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.6 rev: v0.1.6
hooks: hooks:
- id: ruff - id: ruff
exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)'
- repo: local - repo: local
hooks: hooks:
- id: cppcheck - id: cppcheck

10
Jenkinsfile vendored

@ -14,7 +14,8 @@ export GIT_BRANCH=${env.GIT_BRANCH}
export GIT_COMMIT=${env.GIT_COMMIT} export GIT_COMMIT=${env.GIT_COMMIT}
export AZURE_TOKEN='${env.AZURE_TOKEN}' export AZURE_TOKEN='${env.AZURE_TOKEN}'
export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}'
export PYTEST_ADDOPTS="-c selfdrive/test/pytest-tici.ini --rootdir ." # only use 1 thread for tici tests since most require HIL
export PYTEST_ADDOPTS="-n 0"
export GIT_SSH_COMMAND="ssh -i /data/gitkey" export GIT_SSH_COMMAND="ssh -i /data/gitkey"
@ -157,7 +158,7 @@ node {
// tici tests // tici tests
'onroad tests': { 'onroad tests': {
deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [ deviceStage("onroad", "tici-needs-can", ["SKIP_COPY=1"], [
["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR ./build_devel.sh"], ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR $SOURCE_DIR/scripts/retry.sh ./build_devel.sh"],
["build openpilot", "cd selfdrive/manager && ./build.py"], ["build openpilot", "cd selfdrive/manager && ./build.py"],
["check dirty", "release/check-dirty.sh"], ["check dirty", "release/check-dirty.sh"],
["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"],
@ -233,9 +234,8 @@ node {
'car tests': { 'car tests': {
pcStage("car tests") { pcStage("car tests") {
sh label: "build", script: "selfdrive/manager/build.py" sh label: "build", script: "selfdrive/manager/build.py"
sh label: "test_models.py", script: "INTERNAL_SEG_CNT=250 INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt FILEREADER_CACHE=1 \ sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=100 INTERNAL_SEG_CNT=250 FILEREADER_CACHE=1 \
pytest -n auto --dist=loadscope selfdrive/car/tests/test_models.py" INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt pytest test_models.py test_car_interfaces.py"
sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n auto --dist=load selfdrive/car/tests/test_car_interfaces.py"
} }
}, },

@ -1,5 +1,9 @@
Version 0.9.6 (2023-XX-XX) Version 0.9.6 (2023-12-14)
======================== ========================
* AGNOS 9
* comma body streaming and controls over WebRTC
* Toyota RAV4 2023 support
* Toyota RAV4 Hybrid 2023 support
Version 0.9.5 (2023-11-17) Version 0.9.5 (2023-11-17)
======================== ========================

@ -1 +1 @@
Subproject commit 2cb2bfb0154874775e56715ecf81f0e6b00538e9 Subproject commit d11688a90a1cebacb3fe00dcfbba5f27551b2d89

@ -1,4 +1,4 @@
import os import os
BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../"))

@ -4,16 +4,6 @@ import tempfile
from atomicwrites import AtomicWriter from atomicwrites import AtomicWriter
def mkdirs_exists_ok(path):
if path.startswith(('http://', 'https://')):
raise ValueError('URL path')
try:
os.makedirs(path)
except OSError:
if not os.path.isdir(path):
raise
def rm_not_exists_ok(path): def rm_not_exists_ok(path):
try: try:
os.remove(path) os.remove(path)

@ -9,4 +9,4 @@ def get_kalman_gain(dt, A, C, Q, R, iterations=100):
S = C.dot(P).dot(C.T) + R S = C.dot(P).dot(C.T) + R
K = P.dot(C.T).dot(np.linalg.inv(S)) K = P.dot(C.T).dot(np.linalg.inv(S))
P = (np.eye(len(P)) - K.dot(C)).dot(P) P = (np.eye(len(P)) - K.dot(C)).dot(P)
return K return K

@ -114,7 +114,7 @@ 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},
{"ExperimentalLongitudinalEnabled", PERSISTENT}, {"ExperimentalLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY},
{"ExperimentalMode", PERSISTENT}, {"ExperimentalMode", PERSISTENT},
{"ExperimentalModeConfirmed", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT},
{"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION},

@ -10,6 +10,7 @@ enum ParamKeyType {
CLEAR_ON_ONROAD_TRANSITION = 0x08, CLEAR_ON_ONROAD_TRANSITION = 0x08,
CLEAR_ON_OFFROAD_TRANSITION = 0x10, CLEAR_ON_OFFROAD_TRANSITION = 0x10,
DONT_LOG = 0x20, DONT_LOG = 0x20,
DEVELOPMENT_ONLY = 0x40,
ALL = 0xFFFFFFFF ALL = 0xFFFFFFFF
}; };

@ -11,6 +11,7 @@ cdef extern from "common/params.h":
CLEAR_ON_MANAGER_START CLEAR_ON_MANAGER_START
CLEAR_ON_ONROAD_TRANSITION CLEAR_ON_ONROAD_TRANSITION
CLEAR_ON_OFFROAD_TRANSITION CLEAR_ON_OFFROAD_TRANSITION
DEVELOPMENT_ONLY
ALL ALL
cdef cppclass c_Params "Params": cdef cppclass c_Params "Params":

@ -21,7 +21,6 @@ class OpenpilotPrefix:
except FileExistsError: except FileExistsError:
pass pass
os.makedirs(Paths.log_root(), exist_ok=True) os.makedirs(Paths.log_root(), exist_ok=True)
os.makedirs(Paths.download_cache_root(), exist_ok=True)
return self return self

@ -0,0 +1,30 @@
import time
import functools
from openpilot.common.swaglog import cloudlog
def retry(attempts=3, delay=1.0, ignore_failure=False):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(attempts):
try:
return func(*args, **kwargs)
except Exception:
cloudlog.exception(f"{func.__name__} failed, trying again")
time.sleep(delay)
if ignore_failure:
cloudlog.error(f"{func.__name__} failed after retry")
else:
raise Exception(f"{func.__name__} failed after retry")
return wrapper
return decorator
if __name__ == "__main__":
@retry(attempts=10)
def abc():
raise ValueError("abc failed :(")
abc()

@ -3,4 +3,4 @@ import datetime
MIN_DATE = datetime.datetime(year=2023, month=6, day=1) MIN_DATE = datetime.datetime(year=2023, month=6, day=1)
def system_time_valid(): def system_time_valid():
return datetime.datetime.now() > MIN_DATE return datetime.datetime.now() > MIN_DATE

@ -1,46 +0,0 @@
import os
from cffi import FFI
from typing import Any, List
# Workaround for the EON/termux build of Python having os.*xattr removed.
ffi = FFI()
ffi.cdef("""
int setxattr(const char *path, const char *name, const void *value, size_t size, int flags);
ssize_t getxattr(const char *path, const char *name, void *value, size_t size);
ssize_t listxattr(const char *path, char *list, size_t size);
int removexattr(const char *path, const char *name);
""")
libc = ffi.dlopen(None)
def setxattr(path, name, value, flags=0) -> None:
path = path.encode()
name = name.encode()
if libc.setxattr(path, name, value, len(value), flags) == -1:
raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: setxattr({path}, {name}, {value}, {flags})")
def getxattr(path, name, size=128):
path = path.encode()
name = name.encode()
value = ffi.new(f"char[{size}]")
l = libc.getxattr(path, name, value, size)
if l == -1:
# errno 61 means attribute hasn't been set
if ffi.errno == 61:
return None
raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: getxattr({path}, {name}, {size})")
return ffi.buffer(value)[:l]
def listxattr(path, size=128) -> List[Any]:
path = path.encode()
attrs = ffi.new(f"char[{size}]")
l = libc.listxattr(path, attrs, size)
if l == -1:
raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: listxattr({path}, {size})")
# attrs is b'\0' delimited values (so chop off trailing empty item)
return [a.decode() for a in ffi.buffer(attrs)[:l].split(b"\0")[0:-1]]
def removexattr(path, name) -> None:
path = path.encode()
name = name.encode()
if libc.removexattr(path, name) == -1:
raise OSError(ffi.errno, f"{os.strerror(ffi.errno)}: removexattr({path}, {name})")

@ -6,6 +6,12 @@ from openpilot.common.prefix import OpenpilotPrefix
from openpilot.system.hardware import TICI from openpilot.system.hardware import TICI
def pytest_sessionstart(session):
# TODO: fix tests and enable test order randomization
if session.config.pluginmanager.hasplugin('randomly'):
session.config.option.randomly_reorganize = False
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def openpilot_function_fixture(): def openpilot_function_fixture():
starting_env = dict(os.environ) starting_env = dict(os.environ)
@ -45,13 +51,14 @@ def pytest_collection_modifyitems(config, items):
item.add_marker(skipper) item.add_marker(skipper)
if "xdist_group_class_property" in item.keywords: if "xdist_group_class_property" in item.keywords:
class_property = item.get_closest_marker('xdist_group_class_property').args[0] class_property_name = item.get_closest_marker('xdist_group_class_property').args[0]
item.add_marker(pytest.mark.xdist_group(getattr(item.cls, class_property))) class_property_value = getattr(item.cls, class_property_name)
item.add_marker(pytest.mark.xdist_group(class_property_value))
@pytest.hookimpl(trylast=True) @pytest.hookimpl(trylast=True)
def pytest_configure(config): def pytest_configure(config):
config_line = ( config_line = (
"xdist_group_class_property: group tests by a property of the class that contains them" "xdist_group_class_property: group tests by a property of the class that contains them"
) )
config.addinivalue_line("markers", config_line) config.addinivalue_line("markers", config_line)

@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
# 268 Supported Cars # 271 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video| |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|<a href="##"><img width=2000></a>Hardware Needed<br>&nbsp;|Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@ -34,7 +34,7 @@ A supported vehicle is one that just works when you install a comma device. All
|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None||
|Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-22">Buy Here</a></sub></details>|| |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Bronco Sport 2021-22">Buy Here</a></sub></details>||
|Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>|| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Escape 2020-22">Buy Here</a></sub></details>||
|Ford|Explorer 2020-22|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-22">Buy Here</a></sub></details>|| |Ford|Explorer 2020-23|Co-Pilot360 Assist+|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Explorer 2020-23">Buy Here</a></sub></details>||
|Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>|| |Ford|Focus 2018[<sup>3</sup>](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Focus 2018">Buy Here</a></sub></details>||
|Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>|| |Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Kuga 2020-22">Buy Here</a></sub></details>||
|Ford|Maverick 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>|| |Ford|Maverick 2022|LARIAT Luxury|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Ford Q3 connector<br>- 1 RJ45 cable (7 ft)<br>- 1 angled mount (8 degrees)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Ford&model=Maverick 2022">Buy Here</a></sub></details>||
@ -73,6 +73,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>|| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Pilot 2016-22">Buy Here</a></sub></details>||
|Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 Honda Nidec connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Honda&model=Ridgeline 2017-23">Buy Here</a></sub></details>||
|Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera 2022">Buy Here</a></sub></details>|| |Hyundai|Azera 2022|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera 2022">Buy Here</a></sub></details>||
|Hyundai|Azera Hybrid 2019|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2019">Buy Here</a></sub></details>||
|Hyundai|Azera Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2020">Buy Here</a></sub></details>|| |Hyundai|Azera Hybrid 2020|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Azera Hybrid 2020">Buy Here</a></sub></details>||
|Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Custin 2023">Buy Here</a></sub></details>|| |Hyundai|Custin 2023|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Custin 2023">Buy Here</a></sub></details>||
|Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-18">Buy Here</a></sub></details>|| |Hyundai|Elantra 2017-18|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai B connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Elantra 2017-18">Buy Here</a></sub></details>||
@ -98,7 +99,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Kona Electric (with HDA II, Korea only) 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai R connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Electric (with HDA II, Korea only) 2023">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=U2fOCmcQ8hw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Hybrid 2020">Buy Here</a></sub></details>|<a href="https://youtu.be/0dwpAHiZgFo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai I connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Kona Hybrid 2020">Buy Here</a></sub></details>|<a href="https://youtu.be/0dwpAHiZgFo" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Palisade 2020-22|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai H connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Palisade 2020-22">Buy Here</a></sub></details>|<a href="https://youtu.be/TAnDqjF4fDY?t=456" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Santa Cruz 2022-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Cruz 2022-23">Buy Here</a></sub></details>|| |Hyundai|Santa Cruz 2022-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Cruz 2022-23">Buy Here</a></sub></details>||
|Hyundai|Santa Fe 2019-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe 2019-20">Buy Here</a></sub></details>|<a href="https://youtu.be/bjDR0YjM__s" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Santa Fe 2019-20|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai D connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe 2019-20">Buy Here</a></sub></details>|<a href="https://youtu.be/bjDR0YjM__s" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Santa Fe 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/VnHzSTygTS4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Santa Fe 2021-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe 2021-23">Buy Here</a></sub></details>|<a href="https://youtu.be/VnHzSTygTS4" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe Hybrid 2022-23">Buy Here</a></sub></details>|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Santa Fe Hybrid 2022-23">Buy Here</a></sub></details>||
@ -107,8 +108,8 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Hyundai|Sonata 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata 2020-23">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=ix63r9kE3Fw" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata Hybrid 2020-23">Buy Here</a></sub></details>|| |Hyundai|Sonata Hybrid 2020-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Sonata Hybrid 2020-23">Buy Here</a></sub></details>||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2021">Buy Here</a></sub></details>|| |Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2021">Buy Here</a></sub></details>||
|Hyundai|Tucson 2022[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2022">Buy Here</a></sub></details>|| |Hyundai|Tucson 2022[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2022">Buy Here</a></sub></details>||
|Hyundai|Tucson 2023[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2023">Buy Here</a></sub></details>|| |Hyundai|Tucson 2023[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson 2023">Buy Here</a></sub></details>||
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Diesel 2019">Buy Here</a></sub></details>|| |Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai L connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Diesel 2019">Buy Here</a></sub></details>||
|Hyundai|Tucson Hybrid 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Hybrid 2022-24">Buy Here</a></sub></details>|| |Hyundai|Tucson Hybrid 2022-24[<sup>6</sup>](#footnotes)|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Tucson Hybrid 2022-24">Buy Here</a></sub></details>||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Veloster 2019-20">Buy Here</a></sub></details>|| |Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai E connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Hyundai&model=Veloster 2019-20">Buy Here</a></sub></details>||
@ -143,7 +144,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Sorento 2021-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento 2021-23">Buy Here</a></sub></details>|| |Kia|Sorento 2021-23[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento 2021-23">Buy Here</a></sub></details>||
|Kia|Sorento Hybrid 2021-23[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento Hybrid 2021-23">Buy Here</a></sub></details>|| |Kia|Sorento Hybrid 2021-23[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento Hybrid 2021-23">Buy Here</a></sub></details>||
|Kia|Sorento Plug-in Hybrid 2022-23[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|| |Kia|Sorento Plug-in Hybrid 2022-23[<sup>6</sup>](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai A connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sorento Plug-in Hybrid 2022-23">Buy Here</a></sub></details>||
|Kia|Sportage 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sportage 2023">Buy Here</a></sub></details>|| |Kia|Sportage 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sportage 2023">Buy Here</a></sub></details>||
|Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sportage Hybrid 2023">Buy Here</a></sub></details>|| |Kia|Sportage Hybrid 2023[<sup>6</sup>](#footnotes)|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai N connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Sportage Hybrid 2023">Buy Here</a></sub></details>||
|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai C connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Stinger 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=MJ94qoofYw0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Stinger 2022-23">Buy Here</a></sub></details>|| |Kia|Stinger 2022-23|All|openpilot available[<sup>1</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 Hyundai K connector<br>- 1 RJ45 cable (7 ft)<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Kia&model=Stinger 2022-23">Buy Here</a></sub></details>||
@ -236,10 +237,12 @@ A supported vehicle is one that just works when you install a comma device. All
|Toyota|RAV4 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2017-18">Buy Here</a></sub></details>|| |Toyota|RAV4 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2017-18">Buy Here</a></sub></details>||
|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=wJxjDd42gGA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2019-21">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=wJxjDd42gGA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2022">Buy Here</a></sub></details>|| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2022">Buy Here</a></sub></details>||
|Toyota|RAV4 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 2023">Buy Here</a></sub></details>||
|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2016">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[<sup>2</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2017-18">Buy Here</a></sub></details>|<a href="https://youtu.be/LhT5VzJVfNI?t=26" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2019-21">Buy Here</a></sub></details>|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2019-21">Buy Here</a></sub></details>||
|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2022">Buy Here</a></sub></details>|<a href="https://youtu.be/U0nH9cnrFB0" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Toyota|RAV4 Hybrid 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=RAV4 Hybrid 2023">Buy Here</a></sub></details>||
|Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Toyota|Sienna 2018-20|All|openpilot available[<sup>2</sup>](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 RJ45 cable (7 ft)<br>- 1 Toyota A connector<br>- 1 comma 3X<br>- 1 comma power v2<br>- 1 harness box<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Toyota&model=Sienna 2018-20">Buy Here</a></sub></details>|<a href="https://www.youtube.com/watch?v=q1UPOo4Sh68" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon 2018-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
|Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Arteon eHybrid 2020-23">Buy Here</a></sub></details>|<a href="https://youtu.be/FAomFKPFlDA" target="_blank"><img height="18px" src="assets/icon-youtube.svg"></img></a>|
@ -273,7 +276,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont 2018-22">Buy Here</a></sub></details>|| |Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont 2018-22">Buy Here</a></sub></details>||
|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont Cross Sport 2021-22">Buy Here</a></sub></details>|| |Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont Cross Sport 2021-22">Buy Here</a></sub></details>||
|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont X 2021-22">Buy Here</a></sub></details>|| |Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Teramont X 2021-22">Buy Here</a></sub></details>||
|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan 2018-23">Buy Here</a></sub></details>|| |Volkswagen|Tiguan 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan 2018-24">Buy Here</a></sub></details>||
|Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan eHybrid 2021-23">Buy Here</a></sub></details>|| |Volkswagen|Tiguan eHybrid 2021-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Tiguan eHybrid 2021-23">Buy Here</a></sub></details>||
|Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Touran 2016-23">Buy Here</a></sub></details>|| |Volkswagen|Touran 2016-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[<sup>1,12</sup>](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|<details><summary>Parts</summary><sub>- 1 J533 connector<br>- 1 USB-C coupler<br>- 1 comma 3X<br>- 1 harness box<br>- 1 long OBD-C cable<br>- 1 mount<br>- 1 right angle OBD-C cable (1.5 ft)<br><a href="https://comma.ai/shop/comma-3x.html?make=Volkswagen&model=Touran 2016-23">Buy Here</a></sub></details>||

@ -29,8 +29,6 @@ camerad
^^^^^^^ ^^^^^^^
.. autodoxygenindex:: .. autodoxygenindex::
:project: system_camerad_cameras :project: system_camerad_cameras
.. autodoxygenindex::
:project: system_camerad_imgproc
locationd locationd
^^^^^^^^^ ^^^^^^^^^
@ -43,12 +41,6 @@ ui
.. autodoxygenindex:: .. autodoxygenindex::
:project: selfdrive_ui :project: selfdrive_ui
soundd
""""""
.. autodoxygenindex::
:project: selfdrive_ui_soundd
replay replay
"""""" """"""
.. autodoxygenindex:: .. autodoxygenindex::

@ -9,6 +9,11 @@ source "$BASEDIR/launch_env.sh"
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
function agnos_init { function agnos_init {
# wait longer for weston to come up
if [ -f "$BASEDIR/prebuilt" ]; then
sleep 5
fi
# TODO: move this to agnos # TODO: move this to agnos
sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta sudo rm -f /data/etc/NetworkManager/system-connections/*.nmmeta

@ -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="8.2" export AGNOS_VERSION="9.1"
fi fi
if [ -z "$PASSIVE" ]; then if [ -z "$PASSIVE" ]; then

@ -1 +1 @@
Subproject commit 2b96bcc45669cdd14f9c652b07ef32d6403630f6 Subproject commit 93b983d49a2d6d5c67e15ce7650f55e4f121485d

@ -1 +1 @@
Subproject commit b50455cc76a0f66aa6faf953dd3ea023783a4b3f Subproject commit a88fe8c883758951ba2854eb2f6726d5365df099

99
poetry.lock generated

@ -112,66 +112,39 @@ ifaddr = ">=0.2.0"
[[package]] [[package]]
name = "aiortc" name = "aiortc"
version = "1.5.0" version = "1.6.0"
description = "An implementation of WebRTC and ORTC" description = "An implementation of WebRTC and ORTC"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.8"
files = [ files = [
{file = "aiortc-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1d3f2d6cc22fae5ea57b0371895b7830e878b9e3705fd3742b3453cdfa0fd51"}, {file = "aiortc-1.6.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4675e04d8441797fef6c8a669b3a67d750670d1b897f08886072a084d743e07d"},
{file = "aiortc-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2eaf758b5e0bb16f22a9aeb8ab88eb947345f47e2e46cfca18b2815d44726c4e"}, {file = "aiortc-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:836c0686fca67f142c52e5af8043206c2bb702ad0ddcdc94ef19caf1c22f8d54"},
{file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76f6b30d7f39442ba7ac25d58114f077ead1460c5632d0c9e18179d01ad419"}, {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33f846abd753935881158751994a51f14e345762130688b19c26cab42c01266f"},
{file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a766052d93474e9bf4186465298b7c8fb9af062ef7f83ba33f191baa79dac1e"}, {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4bd45397945a5323bd077d43c702a3a991d75023f23649c1d18df5d80c221d"},
{file = "aiortc-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fec292636978ed50728f1ce9b7a9f0d7d2e38bd0b920bb53e091e5728b79e231"}, {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f93561b515ff478b068bb9b047d8b1e896c747dcf3ee465463047c51a7bea24d"},
{file = "aiortc-1.5.0-cp310-cp310-win32.whl", hash = "sha256:27e879b73377d4b94bd86e4c3e8cd8913905fdca1de90a9a4efb0d9d3779dbf4"}, {file = "aiortc-1.6.0-cp38-abi3-win32.whl", hash = "sha256:325f847397af2892aa051dc2880a75e9bd79f535cc05ec8f4538b5ed098b3c5d"},
{file = "aiortc-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a720d0dd53553f6dfc28a53bee2ffce4f13283b4cbbc7db548000054cc63a4f9"}, {file = "aiortc-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:98b118d53ae874126b2e9ec6bb1397ea169b85550c4bd5453e279507ff7f0cf9"},
{file = "aiortc-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5e8cbfce84badd9a8355819343570bbec1e4eef725996cad6aebe4cc3d03ae8"}, {file = "aiortc-1.6.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6677018833a5d648754c99c70e6c6f6d4f3942682cda07ed5afa73422f8a009a"},
{file = "aiortc-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7931512dbb2ff91fb78f5512ad9ca96546452d7bb627f61bd7393bf59ee48ad3"}, {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090936c719225e50cd4d66f476e6c17293a8062cf7687a1baa5080f3c90ec8b1"},
{file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6abeb857a98014fc97265891ebf4fd989987d2ee091e0844e3c8fc543b6e2f0"}, {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e60d5bc269d4d12f1f5f47e2c17aa3799f3b5c8b73fd6d8d246ddc11bb29776"},
{file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dead42dc3a31570fb6f5b94f9be9c78e28b1dc045f71489858116840f299862e"}, {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:607b9496b4a3c8cd9d32afb6d5bce07f9170831ec44a20ab8af54d53879aafc8"},
{file = "aiortc-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a1a8081ba6d7cabc5896d10462cb50f6db7a8ccf34e6aa3e6c4a0d2d5bc5db5"}, {file = "aiortc-1.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0a41f4c0b31e45548e7c7397ef1aecc4be49ab68afd8fa134c07581fe0b3a9c2"},
{file = "aiortc-1.5.0-cp311-cp311-win32.whl", hash = "sha256:cbd5d35bd34b22b8f711c708d266889c973c0dcb38da14a2a9f757266987a181"}, {file = "aiortc-1.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:64a9939016edbc8f300de6189983c983753827813ac9acd9b5be8ce61cc32684"},
{file = "aiortc-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:6749145e3d527ac98c80837d72fd832b0c403eded3546aeb7cec6f25592b4d5e"}, {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:813e7985665c94a0e3387b66e39dba6c751e5e588aedbca06d7e52068c6e37fb"},
{file = "aiortc-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50e8e8903cf55f6f2cda9b61c115fca8e444d48f299cdd071980a3b5cec594fa"}, {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672d0b4ad35c4d8f014f44a142aa55529ec82cfe2809226e1275e35a71fd4422"},
{file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15218a1b81f4fa1521f3b839eefdce638b34c46306e8eaf069cee7283fe8c838"}, {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1526a1904174bb11958b8f7e93f01f37f80df2190e5089f0501984bdef79595e"},
{file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bca7c7bbd3619296b5737a810dd0e2fc7f6264e767fca10e65a709a443bf39"}, {file = "aiortc-1.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cecfa5f462e73218cadc9acd8013cb3a0d9007a4515bceba6e7755d77bb80061"},
{file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8f1d88ae0f8b3047a279e4da06f09a35777cfbe0a9177ca8b053865a98a67912"}, {file = "aiortc-1.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:febca6773de18a6bb9e5569ae87c8be55ed184695f1f9fc99aa4744a7b0375f8"},
{file = "aiortc-1.5.0-cp37-cp37m-win32.whl", hash = "sha256:f86b68b182537022d4ada49a7723c7a56f39372d6fbc31a29f57315d335cdc29"}, {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5ba865be9708713397ec584ed1baeb2f15d2fa9c32594ce19a41ffa6e2517cb"},
{file = "aiortc-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4bbc2f2b97651f7aa6f5e82c69a22590901962454fc02617c4a559a1b51c21a"}, {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9759a3c00e46ba1c3499dbf5a8513ae37ba65b940a56b0e7fa5070478e9379f"},
{file = "aiortc-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7243bce7c3b95e47e56ddf961fbf6015702ddbbf3579b0bbf18c6173b6a6357a"}, {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9cff7868663d9d1c74e237b86e45126022466240439a5f63c3440e3acdf0305b"},
{file = "aiortc-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:883db8926deaf01fdcd32fbd74fcf055db63e968324ceff41d5a46ec86dff90c"}, {file = "aiortc-1.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9f20479d0dd06116ac81d332850fab874d83b561a73fd7252d218f55c6bd5b79"},
{file = "aiortc-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd663f67344e6fe240c6372f620988db5285c9b1b8336306e9fec76ffb4e5493"}, {file = "aiortc-1.6.0.tar.gz", hash = "sha256:08cffbcde3401b33731b1d4169b9ff860b0aaaca200b62e10ce5978238671ad7"},
{file = "aiortc-1.5.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe2766f5a7a8e10b445cbf83a510b791a88180c7b1f9adef3f730840fa208afc"},
{file = "aiortc-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ba212562025843e8d9faf66e6156b682148f8f9995a19e5c66e8ea802f3fa121"},
{file = "aiortc-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b7432c9c78e68811ee060ade8b0f867ac42a21677e4d1a9136bb88cd93ab8299"},
{file = "aiortc-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:2ac6e285d4035298f3025b5767dc8f8b0a5a81b2b8744aaa19c75a8fe76f3ad8"},
{file = "aiortc-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aa3c9306d892635dd9c38cc83c6ba67fb608c7da289f422d40f9542e104b7a0f"},
{file = "aiortc-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:55dab49a38a212556adadb85ea06f6041d2a9e537e01092f9160b21b186b5039"},
{file = "aiortc-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db93641b6f31315b8fd4c81e14881aef28fbb0700f220926f82909baedfa9888"},
{file = "aiortc-1.5.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f63fd1168df72498afe0ee06555cc86b8496115ef128519a01d1ea8e404784b8"},
{file = "aiortc-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e436f49617887f2009c6ada872c2da201e3c8010b387e7c1057eab229ae438c3"},
{file = "aiortc-1.5.0-cp39-cp39-win32.whl", hash = "sha256:6f23495d4e11610117d1bad8686d42d529168d463687a1a1e0bec795d1ec33ce"},
{file = "aiortc-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:76206601082e39fdb56d86221729f04f8bd79d65f9fd6b82121947eabf7efd6d"},
{file = "aiortc-1.5.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d3b2a3b4c120a73242ea0b843ecc3efeaea32861682c771e67f7f08f9d18fddc"},
{file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3d3f6511f2442f49dfaf4e69865b47e0d6d95440fee2f66e6a03a8b4fa1b28e3"},
{file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77ae221c734864c8749c27cc8add22d296ef3e06ae5f6982dbcbe2d0976b10e1"},
{file = "aiortc-1.5.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7732c825ee96e9bc7fb779a4008be768e7663f7f9bf0ab3cccdd412dd7f1c820"},
{file = "aiortc-1.5.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:56ffdd67161488c6d934b090a8c2d277bba8806906a3a18493f46b42976569c1"},
{file = "aiortc-1.5.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f73ba04ca3f331b0ddea0b4ff78424ba30bfd7a49d0b8bd926c75a66ad60f447"},
{file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69eedeec467bd7bcac7ace6ad398133e27f18eeae195a3ad0ffda74255a8b812"},
{file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e095e5fe22f5a2efd4e0657abec1fea7aca864cb32ae3f0816fbcd340a4f2b7"},
{file = "aiortc-1.5.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dffd6899a5d3db4356d2c17521021032468931ae168545b1ff4815764a5e2873"},
{file = "aiortc-1.5.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:af3eed686d621af93befd7e68bd73d6d8a8aa3e721e8fa3ce7e21b3225e37c38"},
{file = "aiortc-1.5.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:15e222a308dcfc44351bd9acff21723c8065cdcd75d6649d53b2986ada64b6be"},
{file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d9ecd61c42e6a78c089805a47542a68eeeec6ba98bf7a2e30cafa3d3f4e94a7f"},
{file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d839437c6000d77511ff1889133150f23fbc8a7365971260c45ce06ff007b0f"},
{file = "aiortc-1.5.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:025847ad6b8c5686f2895394e1de92c043e20e7d90c266de201eef1b1108c8df"},
{file = "aiortc-1.5.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:85583166ab9c9052d2539bee3ba05f27af7f7b93b15c2259c2fc1bd3de5b31d5"},
{file = "aiortc-1.5.0.tar.gz", hash = "sha256:82b4131d84f862e24e1c3550b73f78412cc9554140a2575577eb3f04675bbad2"},
] ]
[package.dependencies] [package.dependencies]
aioice = ">=0.9.0,<1.0.0" aioice = ">=0.9.0,<1.0.0"
av = ">=9.0.0,<11.0.0" av = ">=9.0.0,<12.0.0"
cffi = ">=1.0.0" cffi = ">=1.0.0"
cryptography = ">=2.2" cryptography = ">=2.2"
google-crc32c = ">=1.1" google-crc32c = ">=1.1"
@ -180,7 +153,7 @@ pylibsrtp = ">=0.5.6"
pyopenssl = ">=23.1.0" pyopenssl = ">=23.1.0"
[package.extras] [package.extras]
dev = ["aiohttp (>=3.7.0)", "coverage (>=5.0)", "numpy (>=1.19.0)"] dev = ["aiohttp (>=3.7.0)", "coverage[toml] (>=7.2.2)", "numpy (>=1.19.0)"]
[[package]] [[package]]
name = "aiosignal" name = "aiosignal"
@ -3336,8 +3309,6 @@ files = [
{file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"}, {file = "pygame-2.5.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e24d05184e4195fe5ebcdce8b18ecb086f00182b9ae460a86682d312ce8d31f"},
{file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"}, {file = "pygame-2.5.2-cp311-cp311-win32.whl", hash = "sha256:f02c1c7505af18d426d355ac9872bd5c916b27f7b0fe224749930662bea47a50"},
{file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"}, {file = "pygame-2.5.2-cp311-cp311-win_amd64.whl", hash = "sha256:6d58c8cf937815d3b7cdc0fa9590c5129cb2c9658b72d00e8a4568dea2ff1d42"},
{file = "pygame-2.5.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1a2a43802bb5e89ce2b3b775744e78db4f9a201bf8d059b946c61722840ceea8"},
{file = "pygame-2.5.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1c289f2613c44fe70a1e40769de4a49c5ab5a29b9376f1692bb1a15c9c1c9bfa"},
{file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:074aa6c6e110c925f7f27f00c7733c6303407edc61d738882985091d1eb2ef17"},
{file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe0228501ec616779a0b9c4299e837877783e18df294dd690b9ab0eed3d8aaab"},
{file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"}, {file = "pygame-2.5.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:31648d38ecdc2335ffc0e38fb18a84b3339730521505dac68514f83a1092e3f4"},
@ -3746,18 +3717,18 @@ colorama = "*"
pytest = ">=7.0" pytest = ">=7.0"
[[package]] [[package]]
name = "pytest-random-order" name = "pytest-randomly"
version = "1.1.0" version = "3.15.0"
description = "Randomise the order in which pytest tests are run with some control over the randomness" description = "Pytest plugin to randomly order tests and control random.seed."
optional = false optional = false
python-versions = ">=3.5.0" python-versions = ">=3.8"
files = [ files = [
{file = "pytest-random-order-1.1.0.tar.gz", hash = "sha256:dbe6debb9353a7af984cc9eddbeb3577dd4dbbcc1529a79e3d21f68ed9b45605"}, {file = "pytest_randomly-3.15.0-py3-none-any.whl", hash = "sha256:0516f4344b29f4e9cdae8bce31c4aeebf59d0b9ef05927c33354ff3859eeeca6"},
{file = "pytest_random_order-1.1.0-py3-none-any.whl", hash = "sha256:6cb1e59ab0f798bb0c3488c11ae0c70d7d3340306a466d28b28ccd8ef8c20b7e"}, {file = "pytest_randomly-3.15.0.tar.gz", hash = "sha256:b908529648667ba5e54723088edd6f82252f540cc340d748d1fa985539687047"},
] ]
[package.dependencies] [package.dependencies]
pytest = ">=3.0.0" pytest = "*"
[[package]] [[package]]
name = "pytest-subtests" name = "pytest-subtests"
@ -5038,4 +5009,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "~3.11" python-versions = "~3.11"
content-hash = "f0abc0f53443c3b98eb06fc4dd0ff736e39197bc68c79db9aeffb4736657b676" content-hash = "7f7244a89280a41937066ebf80aa64fcb375852484c9d034ea7f2de3eae4d90a"

@ -24,6 +24,7 @@ testpaths = [
"system/proclogd", "system/proclogd",
"system/tests", "system/tests",
"system/ubloxd", "system/ubloxd",
"system/webrtc",
"tools/lib/tests", "tools/lib/tests",
"tools/replay", "tools/replay",
"tools/cabana" "tools/cabana"
@ -43,6 +44,8 @@ exclude = [
"rednose_repo/", "rednose_repo/",
"tinygrad/", "tinygrad/",
"tinygrad_repo/", "tinygrad_repo/",
"teleoprtc/",
"teleoprtc_repo/",
"third_party/", "third_party/",
] ]
@ -148,7 +151,7 @@ pytest-subtests = "*"
pytest-xdist = "*" pytest-xdist = "*"
pytest-timeout = "*" pytest-timeout = "*"
pytest-timeouts = "*" pytest-timeouts = "*"
pytest-random-order = "*" pytest-randomly = "*"
ruff = "*" ruff = "*"
scipy = "*" scipy = "*"
sphinx = "*" sphinx = "*"
@ -178,7 +181,7 @@ build-backend = "poetry.core.masonry.api"
# https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml
[tool.ruff] [tool.ruff]
select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"]
ignore = ["W292", "E741", "E402", "C408", "ISC003", "B027", "B024"] ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"]
line-length = 160 line-length = 160
target-version="py311" target-version="py311"
exclude = [ exclude = [
@ -186,6 +189,8 @@ exclude = [
"opendbc", "opendbc",
"rednose_repo", "rednose_repo",
"tinygrad_repo", "tinygrad_repo",
"teleoprtc",
"teleoprtc_repo",
"third_party", "third_party",
] ]
flake8-implicit-str-concat.allow-multiline=false flake8-implicit-str-concat.allow-multiline=false
@ -197,4 +202,4 @@ flake8-implicit-str-concat.allow-multiline=false
"tools".msg = "Use openpilot.tools" "tools".msg = "Use openpilot.tools"
[tool.coverage.run] [tool.coverage.run]
concurrency = ["multiprocessing", "thread"] concurrency = ["multiprocessing", "thread"]

@ -22,6 +22,7 @@ pre-commit uninstall || true
echo "[-] bringing master-ci and devel in sync T=$SECONDS" echo "[-] bringing master-ci and devel in sync T=$SECONDS"
cd $TARGET_DIR cd $TARGET_DIR
git fetch --depth 1 origin master-ci git fetch --depth 1 origin master-ci
git fetch --depth 1 origin devel git fetch --depth 1 origin devel

@ -21,24 +21,8 @@ openpilot/**
common/.gitignore common/.gitignore
common/__init__.py common/__init__.py
common/conversions.py common/*.py
common/gpio.py common/*.pyx
common/realtime.py
common/timeout.py
common/ffi_wrapper.py
common/file_helpers.py
common/logging_extra.py
common/numpy_fast.py
common/params.py
common/params_pyx.pyx
common/profiler.py
common/basedir.py
common/dict_helpers.py
common/filter_simple.py
common/stat_live.py
common/spinner.py
common/text_window.py
common/time.py
common/kalman/.gitignore common/kalman/.gitignore
common/kalman/* common/kalman/*
@ -76,7 +60,6 @@ selfdrive/statsd.py
system/logmessaged.py system/logmessaged.py
system/micd.py system/micd.py
system/swaglog.py
system/version.py system/version.py
selfdrive/athena/__init__.py selfdrive/athena/__init__.py
@ -281,6 +264,12 @@ system/sensord/sensors/*.cc
system/sensord/sensors/*.h system/sensord/sensors/*.h
system/sensord/pigeond.py system/sensord/pigeond.py
system/webrtc/__init__.py
system/webrtc/webrtcd.py
system/webrtc/schema.py
system/webrtc/device/audio.py
system/webrtc/device/video.py
selfdrive/thermald/thermald.py selfdrive/thermald/thermald.py
selfdrive/thermald/power_monitoring.py selfdrive/thermald/power_monitoring.py
selfdrive/thermald/fan_controller.py selfdrive/thermald/fan_controller.py
@ -291,19 +280,14 @@ selfdrive/test/helpers.py
selfdrive/test/setup_device_ci.sh selfdrive/test/setup_device_ci.sh
selfdrive/test/test_onroad.py selfdrive/test/test_onroad.py
selfdrive/test/test_time_to_onroad.py selfdrive/test/test_time_to_onroad.py
selfdrive/test/pytest-tici.ini
selfdrive/ui/.gitignore selfdrive/ui/.gitignore
selfdrive/ui/SConscript selfdrive/ui/SConscript
selfdrive/ui/*.cc selfdrive/ui/*.cc
selfdrive/ui/*.h selfdrive/ui/*.h
selfdrive/ui/ui
selfdrive/ui/text selfdrive/ui/text
selfdrive/ui/spinner selfdrive/ui/spinner
selfdrive/ui/soundd/*.cc selfdrive/ui/soundd.py
selfdrive/ui/soundd/*.h
selfdrive/ui/soundd/soundd
selfdrive/ui/soundd/.gitignore
selfdrive/ui/translations/*.ts selfdrive/ui/translations/*.ts
selfdrive/ui/translations/languages.json selfdrive/ui/translations/languages.json
selfdrive/ui/update_translations.py selfdrive/ui/update_translations.py
@ -333,12 +317,11 @@ system/camerad/main.cc
system/camerad/snapshot/* system/camerad/snapshot/*
system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.h
system/camerad/cameras/camera_common.cc system/camerad/cameras/camera_common.cc
system/camerad/cameras/sensor2_i2c.h system/camerad/sensors/ar0231.cc
system/camerad/sensors/ar0231_registers.h
system/camerad/imgproc/conv.cl system/camerad/sensors/ox03c10.cc
system/camerad/imgproc/pool.cl system/camerad/sensors/ox03c10_registers.h
system/camerad/imgproc/utils.cc system/camerad/sensors/sensor.h
system/camerad/imgproc/utils.h
selfdrive/manager/__init__.py selfdrive/manager/__init__.py
selfdrive/manager/build.py selfdrive/manager/build.py
@ -444,6 +427,8 @@ third_party/qt5/larch64/bin/**
scripts/update_now.sh scripts/update_now.sh
scripts/stop_updater.sh scripts/stop_updater.sh
teleoprtc/**
rednose_repo/site_scons/site_tools/rednose_filter.py rednose_repo/site_scons/site_tools/rednose_filter.py
rednose/.gitignore rednose/.gitignore
rednose/** rednose/**

@ -8,4 +8,4 @@ fi
BASE="https://github.com/commaai/openpilot/pull/" BASE="https://github.com/commaai/openpilot/pull/"
PR_NUM="$(echo $1 | grep -o -E '[0-9]+')" PR_NUM="$(echo $1 | grep -o -E '[0-9]+')"
curl -L $BASE/$PR_NUM.patch | git apply curl -L $BASE/$PR_NUM.patch | git apply -3

@ -0,0 +1,39 @@
#!/bin/bash
set -ex
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)"
cd $DIR
# git clone --mirror
SRC=/tmp/openpilot.git/
OUT=/tmp/smallpilot/
echo "starting size $(du -hs .git/)"
rm -rf $OUT
cd $SRC
git remote update
# copy contents
#rsync -a --exclude='.git/' $DIR $OUT
cp -r $SRC $OUT
cd $OUT
# remove all tags
git tag -l | xargs git tag -d
# remove non-master branches
BRANCHES="release2 dashcam dashcam3 release3 devel master-ci nightly"
for branch in $BRANCHES; do
git branch -D $branch
git branch -D ${branch}-staging || true
done
#git gc
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now
echo "new one is $(du -hs .)"

@ -0,0 +1,27 @@
#!/bin/bash
function fail {
echo $1 >&2
exit 1
}
function retry {
local n=1
local max=3 # 3 retries before failure
local delay=5 # delay between retries, 5 seconds
while true; do
echo "Running command '$@' with retry, attempt $n/$max"
"$@" && break || {
if [[ $n -lt $max ]]; then
((n++))
sleep $delay;
else
fail "The command has failed after $n attempts."
fi
}
done
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
retry "$@"
fi

@ -36,14 +36,11 @@ from openpilot.common.params import Params
from openpilot.common.realtime import set_core_affinity from openpilot.common.realtime import set_core_affinity
from openpilot.system.hardware import HARDWARE, PC, AGNOS from openpilot.system.hardware import HARDWARE, PC, AGNOS
from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.system.loggerd.xattr_cache import getxattr, setxattr
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version
from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import Paths
# TODO: use socket constant when mypy recognizes this as a valid attribute
TCP_USER_TIMEOUT = 18
ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai')
HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4"))
LOCAL_PORT_WHITELIST = {8022} LOCAL_PORT_WHITELIST = {8022}
@ -760,7 +757,7 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None:
if onroad != onroad_prev: if onroad != onroad_prev:
onroad_prev = onroad onroad_prev = onroad
sock.setsockopt(socket.IPPROTO_TCP, TCP_USER_TIMEOUT, 16000 if onroad else 0) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3)

@ -5,7 +5,7 @@ from multiprocessing import Process
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.manager.process import launcher from openpilot.selfdrive.manager.process import launcher
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_version, is_dirty from openpilot.system.version import get_version, is_dirty
ATHENA_MGR_PID_PARAM = "AthenadPid" ATHENA_MGR_PID_PARAM = "AthenadPid"

@ -12,7 +12,7 @@ from openpilot.common.spinner import Spinner
from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, PC from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import Paths
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice" UNREGISTERED_DONGLE_ID = "UnregisteredDevice"

@ -46,8 +46,6 @@ class TestAthenadMethods(unittest.TestCase):
else: else:
os.unlink(p) os.unlink(p)
dispatcher["listUploadQueue"]() # ensure queue is empty at start
# *** test helpers *** # *** test helpers ***
@staticmethod @staticmethod

@ -3,8 +3,8 @@ import subprocess
import threading import threading
import time import time
import unittest import unittest
from typing import Callable, cast, Optional from typing import cast, Optional
from unittest.mock import MagicMock from unittest import mock
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.timeout import Timeout from openpilot.common.timeout import Timeout
@ -27,8 +27,6 @@ class TestAthenadPing(unittest.TestCase):
athenad: threading.Thread athenad: threading.Thread
exit_event: threading.Event exit_event: threading.Event
_create_connection: Callable
def _get_ping_time(self) -> Optional[str]: def _get_ping_time(self) -> Optional[str]:
return cast(Optional[str], self.params.get("LastAthenaPingTime", encoding="utf-8")) return cast(Optional[str], self.params.get("LastAthenaPingTime", encoding="utf-8"))
@ -38,15 +36,9 @@ class TestAthenadPing(unittest.TestCase):
def _received_ping(self) -> bool: def _received_ping(self) -> bool:
return self._get_ping_time() is not None return self._get_ping_time() is not None
@classmethod
def setUpClass(cls) -> None:
cls._create_connection = athenad.create_connection
athenad.create_connection = MagicMock(wraps=cls._create_connection)
@classmethod @classmethod
def tearDownClass(cls) -> None: def tearDownClass(cls) -> None:
wifi_radio(True) wifi_radio(True)
athenad.create_connection = cls._create_connection
def setUp(self) -> None: def setUp(self) -> None:
self.params = Params() self.params = Params()
@ -58,19 +50,18 @@ class TestAthenadPing(unittest.TestCase):
self.exit_event = threading.Event() self.exit_event = threading.Event()
self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,)) self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,))
athenad.create_connection.reset_mock()
def tearDown(self) -> None: def tearDown(self) -> None:
if self.athenad.is_alive(): if self.athenad.is_alive():
self.exit_event.set() self.exit_event.set()
self.athenad.join() self.athenad.join()
def assertTimeout(self, reconnect_time: float) -> None: @mock.patch('openpilot.selfdrive.athena.athenad.create_connection', autospec=True)
def assertTimeout(self, reconnect_time: float, mock_create_connection: mock.MagicMock) -> None:
self.athenad.start() self.athenad.start()
time.sleep(1) time.sleep(1)
athenad.create_connection.assert_called_once() mock_create_connection.assert_called_once()
athenad.create_connection.reset_mock() mock_create_connection.reset_mock()
# check normal behaviour # check normal behaviour
with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"): with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"):
@ -78,7 +69,7 @@ class TestAthenadPing(unittest.TestCase):
time.sleep(0.1) time.sleep(0.1)
print("ping received") print("ping received")
athenad.create_connection.assert_not_called() mock_create_connection.assert_not_called()
# websocket should attempt reconnect after short time # websocket should attempt reconnect after short time
with self.subTest("LTE: attempt reconnect"): with self.subTest("LTE: attempt reconnect"):
@ -86,7 +77,7 @@ class TestAthenadPing(unittest.TestCase):
print("waiting for reconnect attempt") print("waiting for reconnect attempt")
start_time = time.monotonic() start_time = time.monotonic()
with Timeout(reconnect_time, "no reconnect attempt"): with Timeout(reconnect_time, "no reconnect attempt"):
while not athenad.create_connection.called: while not mock_create_connection.called:
time.sleep(0.1) time.sleep(0.1)
print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s") print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s")

@ -1,11 +1,5 @@
#include "selfdrive/boardd/boardd.h" #include "selfdrive/boardd/boardd.h"
#include <sched.h>
#include <sys/cdefs.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
@ -13,10 +7,6 @@
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
#include <chrono> #include <chrono>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <future> #include <future>
#include <memory> #include <memory>
#include <thread> #include <thread>

@ -12,7 +12,7 @@ from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.boardd.set_time import set_time from openpilot.selfdrive.boardd.set_time import set_time
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
def get_expected_signature(panda: Panda) -> bytes: def get_expected_signature(panda: Panda) -> bytes:

@ -116,4 +116,4 @@ class TestPandad(unittest.TestCase):
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

@ -10,7 +10,7 @@ from openpilot.selfdrive.car.interfaces import get_interface_attr
from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars
from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN from openpilot.selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN
from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing from openpilot.selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
import cereal.messaging as messaging import cereal.messaging as messaging
from openpilot.selfdrive.car import gen_empty_fingerprint from openpilot.selfdrive.car import gen_empty_fingerprint

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
EXT_DIAG_REQUEST = b'\x10\x03' EXT_DIAG_REQUEST = b'\x10\x03'
EXT_DIAG_RESPONSE = b'\x50\x03' EXT_DIAG_RESPONSE = b'\x50\x03'

@ -8,7 +8,7 @@ from panda.python.uds import SERVICE_TYPE
from openpilot.selfdrive.car import make_can_msg from openpilot.selfdrive.car import make_can_msg
from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
def make_tester_present_msg(addr, bus, subaddr=None): def make_tester_present_msg(addr, bus, subaddr=None):

@ -88,7 +88,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = {
FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"), FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"),
], ],
CAR.EXPLORER_MK6: [ CAR.EXPLORER_MK6: [
FordCarInfo("Ford Explorer 2020-22"), FordCarInfo("Ford Explorer 2020-23"),
FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"),
], ],
CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"),
@ -185,6 +185,7 @@ FW_VERSIONS = {
b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'M1MC-14D003-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'M1MC-14D003-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'P1MC-14D003-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.abs, 0x760, None): [ (Ecu.abs, 0x760, None): [
b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-AJ\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -192,6 +193,7 @@ FW_VERSIONS = {
b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'L1MC-2D053-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
(Ecu.fwdRadar, 0x764, None): [ (Ecu.fwdRadar, 0x764, None): [
b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
@ -212,6 +214,8 @@ FW_VERSIONS = {
b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-AZD\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PB5A-14C204-DA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'LB5A-14C204-ATS\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.F_150_MK14: { CAR.F_150_MK14: {

@ -12,7 +12,7 @@ from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusTyp
from openpilot.selfdrive.car.interfaces import get_interface_attr from openpilot.selfdrive.car.interfaces import get_interface_attr
from openpilot.selfdrive.car.fingerprints import FW_VERSIONS from openpilot.selfdrive.car.fingerprints import FW_VERSIONS
from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
Ecu = car.CarParams.Ecu Ecu = car.CarParams.Ecu
ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa]

@ -260,8 +260,7 @@ class CarInterface(CarInterfaceBase):
if candidate in CANFD_CAR: if candidate in CANFD_CAR:
ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kpV = [0.1]
ret.longitudinalTuning.kiV = [0.0] ret.longitudinalTuning.kiV = [0.0]
ret.experimentalLongitudinalAvailable = (candidate in (HYBRID_CAR | EV_CAR) and candidate not in ret.experimentalLongitudinalAvailable = candidate not in (CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR)
(CANFD_UNSUPPORTED_LONGITUDINAL_CAR | CANFD_RADAR_SCC_CAR))
else: else:
ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kpV = [0.5]
ret.longitudinalTuning.kiV = [0.0] ret.longitudinalTuning.kiV = [0.0]

@ -159,7 +159,10 @@ class HyundaiCarInfo(CarInfo):
CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])),
CAR.AZERA_HEV_6TH_GEN: HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.AZERA_HEV_6TH_GEN: [
HyundaiCarInfo("Hyundai Azera Hybrid 2019", "All", car_parts=CarParts.common([CarHarness.hyundai_c])),
HyundaiCarInfo("Hyundai Azera Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_k])),
],
CAR.ELANTRA: [ CAR.ELANTRA: [
# TODO: 2017-18 could be Hyundai G # TODO: 2017-18 could be Hyundai G
HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])), HyundaiCarInfo("Hyundai Elantra 2017-18", min_enable_speed=19 * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_b])),
@ -556,18 +559,23 @@ FW_VERSIONS = {
CAR.AZERA_HEV_6TH_GEN: { CAR.AZERA_HEV_6TH_GEN: {
(Ecu.fwdCamera, 0x7C4, None): [ (Ecu.fwdCamera, 0x7C4, None): [
b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029', b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.02 99211-G8100 191029',
b'\xf1\x00IGH MFC AT KOR LHD 1.00 1.00 99211-G8000 180903',
], ],
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100', b'\xf1\x00IG MDPS C 1.00 1.00 56310M9600\x00 4IHSC100',
b'\xf1\x00IG MDPS C 1.00 1.01 56310M9350\x00 4IH8C101',
], ],
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [
b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ', b'\xf1\x00IGhe SCC FHCUP 1.00 1.00 99110-M9100 ',
b'\xf1\x00IGhe SCC FHCUP 1.00 1.01 99110-M9000 ',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`', b'\xf1\x006T7N0_C2\x00\x006T7VA051\x00\x00TIGSH24KA1\xc7\x85\xe2`',
b'\xf1\x006T7N0_C2\x00\x006T7Q2051\x00\x00TIG2H24KA2\x12@\x11\xb7',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H590051\x00\x00\x00\x00\x00\x00\x00\x00',
b'\xf1\x816H570051\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.HYUNDAI_GENESIS: { CAR.HYUNDAI_GENESIS: {
@ -1132,6 +1140,7 @@ FW_VERSIONS = {
b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ',
b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5500 ',
b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ', b'\xf1\x00CK__ SCC FHCUP 1.00 1.00 99110-J5600 ',
b'\xf1\x00CK__ SCC FHCUP 1.00 1.01 99110-J5100 ',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00',
@ -1141,17 +1150,20 @@ FW_VERSIONS = {
(Ecu.eps, 0x7d4, None): [ (Ecu.eps, 0x7d4, None): [
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503',
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503', b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5300 4C2CL503',
b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5320 4C2VL503',
b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504', b'\xf1\x00CK MDPS R 1.00 5.04 57700-J5520 4C4VL504',
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622',
b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT KOR LHD 1.00 1.00 99211-J5500 210622',
b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622', b'\xf1\x00CK MFC AT USA LHD 1.00 1.00 99211-J5500 210622',
b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 99211-J5000 201209',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6',
b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T33NH07\xdf\xf0\xc1',
b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2', b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00SCK0T25KH2B\xfbI\xe2',
b'\xf1\x00bcsh8p54 E31\x00\x00\x00\x00\x00\x00\x00TCK0T33NH0%g~\xd3',
], ],
}, },
CAR.PALISADE: { CAR.PALISADE: {

@ -3,7 +3,7 @@ from collections import defaultdict
from functools import partial from functools import partial
import cereal.messaging as messaging import cereal.messaging as messaging
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr

@ -132,6 +132,7 @@ FW_VERSIONS = {
b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2E-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',

@ -157,6 +157,7 @@ routes = [
CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN), CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN),
CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE),
CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN),
CarTestRoute("09559f1fcaed4704|2023-11-16--02-24-57", HYUNDAI.KIA_SPORTAGE_5TH_GEN), # openpilot longitudinal
CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA),
CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30), CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA_GT_I30),
CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021),

@ -3,6 +3,7 @@ import capnp
import os import os
import importlib import importlib
import pytest import pytest
import random
import unittest import unittest
from collections import defaultdict, Counter from collections import defaultdict, Counter
from typing import List, Optional, Tuple from typing import List, Optional, Tuple
@ -49,12 +50,10 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]:
with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f: with open(os.path.join(BASEDIR, INTERNAL_SEG_LIST), "r") as f:
seg_list = f.read().splitlines() seg_list = f.read().splitlines()
cnt = INTERNAL_SEG_CNT or len(seg_list) seg_list_grouped = [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)]
seg_list_iter = iter(seg_list[:cnt]) seg_list_grouped = random.sample(seg_list_grouped, INTERNAL_SEG_CNT or len(seg_list_grouped))
for platform, segment in seg_list_grouped:
for platform in seg_list_iter: segment_name = SegmentName(segment)
platform = platform[2:] # get rid of comment
segment_name = SegmentName(next(seg_list_iter))
test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform, test_cases.append((platform, CarTestRoute(segment_name.route_name.canonical_name, platform,
segment=segment_name.segment_num))) segment=segment_name.segment_num)))
return test_cases return test_cases
@ -382,7 +381,7 @@ class TestCarModelBase(unittest.TestCase):
@parameterized_class(('car_model', 'test_route'), get_test_cases()) @parameterized_class(('car_model', 'test_route'), get_test_cases())
@pytest.mark.xdist_group_class_property('car_model') @pytest.mark.xdist_group_class_property('test_route')
class TestCarModel(TestCarModelBase): class TestCarModel(TestCarModelBase):
pass pass

@ -21,8 +21,8 @@ MAX_USER_TORQUE = 500
# LTA limits # LTA limits
# EPS ignores commands above this angle and causes PCS to fault # EPS ignores commands above this angle and causes PCS to fault
MAX_STEER_ANGLE = 94.9461 # deg MAX_LTA_ANGLE = 94.9461 # deg
MAX_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes MAX_LTA_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes
class CarController: class CarController:
@ -71,25 +71,32 @@ class CarController:
apply_angle = actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg apply_angle = actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg
# Angular rate limit based on speed # Angular rate limit based on speed
apply_angle = apply_std_steer_angle_limits(apply_angle, self.last_angle, CS.out.vEgo, self.params) apply_angle = apply_std_steer_angle_limits(apply_angle, self.last_angle, CS.out.vEgoRaw, self.params)
if not lat_active: if not lat_active:
apply_angle = CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg apply_angle = CS.out.steeringAngleDeg + CS.out.steeringAngleOffsetDeg
self.last_angle = clip(apply_angle, -MAX_STEER_ANGLE, MAX_STEER_ANGLE) self.last_angle = clip(apply_angle, -MAX_LTA_ANGLE, MAX_LTA_ANGLE)
self.last_steer = apply_steer self.last_steer = apply_steer
# toyota can trace shows this message at 42Hz, with counter adding alternatively 1 and 2; # toyota can trace shows STEERING_LKA at 42Hz, with counter adding alternatively 1 and 2;
# sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed # sending it at 100Hz seem to allow a higher rate limit, as the rate limit seems imposed
# on consecutive messages # on consecutive messages
can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req)) can_sends.append(toyotacan.create_steer_command(self.packer, apply_steer, apply_steer_req))
# STEERING_LTA does not seem to allow more rate by sending faster, and may wind up easier
if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR: if self.frame % 2 == 0 and self.CP.carFingerprint in TSS2_CAR:
lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle lta_active = lat_active and self.CP.steerControlType == SteerControlType.angle
# cut steering torque with TORQUE_WIND_DOWN when either EPS torque or driver torque is above
# the threshold, to limit max lateral acceleration and for driver torque blending respectively.
full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and full_torque_condition = (abs(CS.out.steeringTorqueEps) < self.params.STEER_MAX and
abs(CS.out.steeringTorque) < MAX_DRIVER_TORQUE_ALLOWANCE) abs(CS.out.steeringTorque) < MAX_LTA_DRIVER_TORQUE_ALLOWANCE)
setme_x64 = 100 if lta_active and full_torque_condition else 0
can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.last_angle, lta_active, self.frame // 2, setme_x64)) # TORQUE_WIND_DOWN at 0 ramps down torque at roughly the max down rate of 1500 units/sec
torque_wind_down = 100 if lta_active and full_torque_condition else 0
can_sends.append(toyotacan.create_lta_steer_command(self.packer, self.CP.steerControlType, self.last_angle,
lta_active, self.frame // 2, torque_wind_down))
# *** gas and brake *** # *** gas and brake ***
if self.CP.enableGasInterceptor and CC.longActive: if self.CP.enableGasInterceptor and CC.longActive:

@ -28,12 +28,11 @@ class CarInterface(CarInterfaceBase):
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE
if candidate in ANGLE_CONTROL_CAR: if candidate in ANGLE_CONTROL_CAR:
ret.dashcamOnly = True
ret.steerControlType = SteerControlType.angle ret.steerControlType = SteerControlType.angle
ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA
# LTA control can be more delayed and winds up more often # LTA control can be more delayed and winds up more often
ret.steerActuatorDelay = 0.25 ret.steerActuatorDelay = 0.18
ret.steerLimitTimer = 0.8 ret.steerLimitTimer = 0.8
else: else:
CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning)

@ -17,6 +17,10 @@ class TestToyotaInterfaces(unittest.TestCase):
self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0)
self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0) self.assertTrue(len(RADAR_ACC_CAR - TSS2_CAR) == 0)
def test_lta_platforms(self):
# At this time, only RAV4 2023 is expected to use LTA/angle control
self.assertEqual(ANGLE_CONTROL_CAR, {CAR.RAV4_TSS2_2023})
def test_tss2_dbc(self): def test_tss2_dbc(self):
# We make some assumptions about TSS2 platforms, # We make some assumptions about TSS2 platforms,
# like looking up certain signals only in this DBC # like looking up certain signals only in this DBC

@ -1,3 +1,8 @@
from cereal import car
SteerControlType = car.CarParams.SteerControlType
def create_steer_command(packer, steer, steer_req): def create_steer_command(packer, steer, steer_req):
"""Creates a CAN message for the Toyota Steer Command.""" """Creates a CAN message for the Toyota Steer Command."""
@ -9,15 +14,16 @@ def create_steer_command(packer, steer, steer_req):
return packer.make_can_msg("STEERING_LKA", 0, values) return packer.make_can_msg("STEERING_LKA", 0, values)
def create_lta_steer_command(packer, steer_angle, steer_req, frame, setme_x64): def create_lta_steer_command(packer, steer_control_type, steer_angle, steer_req, frame, torque_wind_down):
"""Creates a CAN message for the Toyota LTA Steer Command.""" """Creates a CAN message for the Toyota LTA Steer Command."""
values = { values = {
"COUNTER": frame + 128, "COUNTER": frame + 128,
"SETME_X1": 1, "SETME_X1": 1, # suspected LTA feature availability
"SETME_X3": 3, # 1 for TSS 2.5 cars, 3 for TSS 2.0. Send based on whether we're using LTA for lateral control
"SETME_X3": 1 if steer_control_type == SteerControlType.angle else 3,
"PERCENTAGE": 100, "PERCENTAGE": 100,
"SETME_X64": setme_x64, "TORQUE_WIND_DOWN": torque_wind_down,
"ANGLE": 0, "ANGLE": 0,
"STEER_ANGLE_CMD": steer_angle, "STEER_ANGLE_CMD": steer_angle,
"STEER_REQUEST": steer_req, "STEER_REQUEST": steer_req,

@ -27,7 +27,8 @@ class CarControllerParams:
# Assuming a steering ratio of 13.7: # Assuming a steering ratio of 13.7:
# Limit to ~2.0 m/s^3 up (7.5 deg/s), ~3.5 m/s^3 down (13 deg/s) at 75 mph # Limit to ~2.0 m/s^3 up (7.5 deg/s), ~3.5 m/s^3 down (13 deg/s) at 75 mph
# Worst case, the low speed limits will allow ~4.0 m/s^3 up (15 deg/s) and ~4.9 m/s^3 down (18 deg/s) at 75 mph, # Worst case, the low speed limits will allow ~4.0 m/s^3 up (15 deg/s) and ~4.9 m/s^3 down (18 deg/s) at 75 mph,
# however the EPS has its own internal limits at all speeds which are less than that # however the EPS has its own internal limits at all speeds which are less than that:
# Observed internal torque rate limit on TSS 2.5 Camry and RAV4 is ~1500 units/sec up and down when using LTA
ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.3, 0.15]) ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.3, 0.15])
ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.36, 0.26]) ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.36, 0.26])

@ -5,7 +5,7 @@ import cereal.messaging as messaging
from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS from panda.python.uds import get_rx_addr_for_tx_addr, FUNCTIONAL_ADDRS
from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery
from openpilot.selfdrive.car.fw_query_definitions import StdQueries from openpilot.selfdrive.car.fw_query_definitions import StdQueries
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
VIN_UNKNOWN = "0" * 17 VIN_UNKNOWN = "0" * 17
VIN_RE = "[A-HJ-NPR-Z0-9]{17}" VIN_RE = "[A-HJ-NPR-Z0-9]{17}"

@ -239,7 +239,7 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = {
CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022-23"), CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022-23"),
CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]),
CAR.TIGUAN_MK2: [ CAR.TIGUAN_MK2: [
VWCarInfo("Volkswagen Tiguan 2018-23"), VWCarInfo("Volkswagen Tiguan 2018-24"),
VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"), VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"),
], ],
CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2016-23"), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2016-23"),
@ -843,6 +843,7 @@ FW_VERSIONS = {
b'\xf1\x8783A907115K \xf1\x890002', b'\xf1\x8783A907115K \xf1\x890002',
b'\xf1\x8704E906024AP\xf1\x891461', b'\xf1\x8704E906024AP\xf1\x891461',
b'\xf1\x8783A907115 \xf1\x890007', b'\xf1\x8783A907115 \xf1\x890007',
b'\xf1\x8783A907115Q \xf1\x890001',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x8709G927158DT\xf1\x893698', b'\xf1\x8709G927158DT\xf1\x893698',
@ -851,6 +852,7 @@ FW_VERSIONS = {
b'\xf1\x8709G927158GD\xf1\x893820', b'\xf1\x8709G927158GD\xf1\x893820',
b'\xf1\x8709G927158GM\xf1\x893936', b'\xf1\x8709G927158GM\xf1\x893936',
b'\xf1\x8709G927158GN\xf1\x893938', b'\xf1\x8709G927158GN\xf1\x893938',
b'\xf1\x8709G927158HB\xf1\x894069',
b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870D9300043 \xf1\x895202',
b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892001',
b'\xf1\x870DL300011N \xf1\x892012', b'\xf1\x870DL300011N \xf1\x892012',
@ -1265,6 +1267,7 @@ FW_VERSIONS = {
b'\xf1\x875NA907115E \xf1\x890003', b'\xf1\x875NA907115E \xf1\x890003',
b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x875NA907115E \xf1\x890005',
b'\xf1\x875NA906259E \xf1\x890003', b'\xf1\x875NA906259E \xf1\x890003',
b'\xf1\x8704E906027LD\xf1\x893433',
], ],
(Ecu.transmission, 0x7e1, None): [ (Ecu.transmission, 0x7e1, None): [
b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870D9300043 \xf1\x895202',
@ -1275,6 +1278,7 @@ FW_VERSIONS = {
b'\xf1\x870GC300014N \xf1\x892801', b'\xf1\x870GC300014N \xf1\x892801',
b'\xf1\x870GC300019H \xf1\x892806', b'\xf1\x870GC300019H \xf1\x892806',
b'\xf1\x870GC300046Q \xf1\x892802', b'\xf1\x870GC300046Q \xf1\x892802',
b'\xf1\x870D9300014S \xf1\x895201',
], ],
(Ecu.srs, 0x715, None): [ (Ecu.srs, 0x715, None): [
b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211',
@ -1284,6 +1288,7 @@ FW_VERSIONS = {
b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111',
b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00',
b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749', b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100010200--1121240749',
b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1213001211001205212111052100',
], ],
(Ecu.eps, 0x712, None): [ (Ecu.eps, 0x712, None): [
b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405',
@ -1299,6 +1304,7 @@ FW_VERSIONS = {
b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x872Q0907572T \xf1\x890383',
b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572AA\xf1\x890396',
b'\xf1\x872Q0907572AB\xf1\x890397', b'\xf1\x872Q0907572AB\xf1\x890397',
b'\xf1\x872Q0907572M \xf1\x890233',
], ],
}, },
CAR.SKODA_OCTAVIA_MK3: { CAR.SKODA_OCTAVIA_MK3: {

@ -13,8 +13,8 @@ import cereal.messaging as messaging
from cereal.visionipc import VisionIpcClient, VisionStreamType from cereal.visionipc import VisionIpcClient, VisionStreamType
from openpilot.common.conversions import Conversions as CV from openpilot.common.conversions import Conversions as CV
from panda import ALTERNATIVE_EXPERIENCE from panda import ALTERNATIVE_EXPERIENCE
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.version import is_release_branch, get_short_branch from openpilot.system.version import get_short_branch
from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp
from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can
from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET from openpilot.selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET
@ -64,15 +64,13 @@ class Controls:
# Setup sockets # Setup sockets
self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState', self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState',
'carControl', 'carEvents', 'carParams']) 'carControl', 'onroadEvents', 'carParams'])
self.sensor_packets = ["accelerometer", "gyroscope"] self.sensor_packets = ["accelerometer", "gyroscope"]
self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"]
can_timeout = None if os.environ.get('NO_CAN_TIMEOUT', False) else 20
self.can_sock = messaging.sub_sock('can', timeout=can_timeout)
self.log_sock = messaging.sub_sock('androidLog') self.log_sock = messaging.sub_sock('androidLog')
self.can_sock = messaging.sub_sock('can', timeout=20)
self.params = Params() self.params = Params()
ignore = self.sensor_packets + ['testJoystick'] ignore = self.sensor_packets + ['testJoystick']
@ -82,7 +80,7 @@ class Controls:
'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman',
'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters',
'testJoystick'] + self.camera_packets + self.sensor_packets, 'testJoystick'] + self.camera_packets + self.sensor_packets,
ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick']) ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick'], ignore_valid=['testJoystick', ])
if CI is None: if CI is None:
# wait for one pandaState and one CAN packet # wait for one pandaState and one CAN packet
@ -90,12 +88,13 @@ class Controls:
get_one_can(self.can_sock) get_one_can(self.can_sock)
num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates)
experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") and not is_release_branch() experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled")
self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas)
else: else:
self.CI, self.CP = CI, CI.CP self.CI, self.CP = CI, CI.CP
self.joystick_mode = self.params.get_bool("JoystickDebugMode") or self.CP.notCar self.joystick_enabled = self.params.get_bool("JoystickDebugMode")
self.joystick_mode = self.joystick_enabled or self.CP.notCar
# set alternative experiences from parameters # set alternative experiences from parameters
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
@ -115,8 +114,8 @@ class Controls:
car_recognized = self.CP.carName != 'mock' car_recognized = self.CP.carName != 'mock'
controller_available = self.CI.CC is not None and not passive and not self.CP.dashcamOnly controller_available = self.CI.CC is not None and not passive and not self.CP.dashcamOnly
self.read_only = not car_recognized or not controller_available or self.CP.dashcamOnly self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly
if self.read_only: if self.CP.passive:
safety_config = car.CarParams.SafetyConfig.new_message() safety_config = car.CarParams.SafetyConfig.new_message()
safety_config.safetyModel = car.CarParams.SafetyModel.noOutput safety_config.safetyModel = car.CarParams.SafetyModel.noOutput
self.CP.safetyConfigs = [safety_config] self.CP.safetyConfigs = [safety_config]
@ -133,7 +132,7 @@ class Controls:
put_nonblocking("CarParamsPersistent", cp_bytes) put_nonblocking("CarParamsPersistent", cp_bytes)
# cleanup old params # cleanup old params
if not self.CP.experimentalLongitudinalAvailable or is_release_branch(): if not self.CP.experimentalLongitudinalAvailable:
self.params.remove("ExperimentalLongitudinalEnabled") self.params.remove("ExperimentalLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl: if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode") self.params.remove("ExperimentalMode")
@ -179,8 +178,6 @@ class Controls:
self.v_cruise_helper = VCruiseHelper(self.CP) self.v_cruise_helper = VCruiseHelper(self.CP)
self.recalibrating_seen = False self.recalibrating_seen = False
# TODO: no longer necessary, aside from process replay
self.sm['liveParameters'].valid = True
self.can_log_mono_time = 0 self.can_log_mono_time = 0
self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0) self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0)
@ -193,7 +190,7 @@ class Controls:
set_offroad_alert("Offroad_CarUnrecognized", True) set_offroad_alert("Offroad_CarUnrecognized", True)
else: else:
set_offroad_alert("Offroad_NoFirmware", True) set_offroad_alert("Offroad_NoFirmware", True)
elif self.read_only: elif self.CP.passive:
self.events.add(EventName.dashcamMode, static=True) self.events.add(EventName.dashcamMode, static=True)
elif self.joystick_mode: elif self.joystick_mode:
self.events.add(EventName.joystickDebug, static=True) self.events.add(EventName.joystickDebug, static=True)
@ -214,7 +211,7 @@ class Controls:
self.state = State.enabled self.state = State.enabled
def update_events(self, CS): def update_events(self, CS):
"""Compute carEvents from carState""" """Compute onroadEvents from carState"""
self.events.clear() self.events.clear()
@ -229,7 +226,7 @@ class Controls:
return return
# no more events while in dashcam mode # no more events while in dashcam mode
if self.read_only: if self.CP.passive:
return return
# Block resume if cruise never previously enabled # Block resume if cruise never previously enabled
@ -374,16 +371,17 @@ class Controls:
else: else:
self.logged_comm_issue = None self.logged_comm_issue = None
if not self.sm['lateralPlan'].mpcSolutionValid: if not (self.CP.notCar and self.joystick_enabled):
self.events.add(EventName.plannerError) if not self.sm['lateralPlan'].mpcSolutionValid:
if not self.sm['liveLocationKalman'].posenetOK: self.events.add(EventName.plannerError)
self.events.add(EventName.posenetInvalid) if not self.sm['liveLocationKalman'].posenetOK:
if not self.sm['liveLocationKalman'].deviceStable: self.events.add(EventName.posenetInvalid)
self.events.add(EventName.deviceFalling) if not self.sm['liveLocationKalman'].deviceStable:
if not self.sm['liveLocationKalman'].inputsOK: self.events.add(EventName.deviceFalling)
self.events.add(EventName.locationdTemporaryError) if not self.sm['liveLocationKalman'].inputsOK:
if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): self.events.add(EventName.locationdTemporaryError)
self.events.add(EventName.paramsdTemporaryError) if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY):
self.events.add(EventName.paramsdTemporaryError)
# conservative HW alert. if the data or frequency are off, locationd will throw an error # conservative HW alert. if the data or frequency are off, locationd will throw an error
if any((self.sm.frame - self.sm.rcv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): if any((self.sm.frame - self.sm.rcv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets):
@ -444,7 +442,7 @@ class Controls:
if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams:
self.sm.ignore_alive.append('wideRoadCameraState') self.sm.ignore_alive.append('wideRoadCameraState')
if not self.read_only: if not self.CP.passive:
self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan'])
self.initialized = True self.initialized = True
@ -618,7 +616,7 @@ class Controls:
lat_plan.curvatures, lat_plan.curvatures,
lat_plan.curvatureRates) lat_plan.curvatureRates)
actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp,
self.last_actuators, self.steer_limited, self.desired_curvature, self.steer_limited, self.desired_curvature,
self.desired_curvature_rate, self.sm['liveLocationKalman']) self.desired_curvature_rate, self.sm['liveLocationKalman'])
actuators.curvature = self.desired_curvature actuators.curvature = self.desired_curvature
else: else:
@ -637,7 +635,7 @@ class Controls:
if CC.latActive: if CC.latActive:
steer = clip(joystick_axes[1], -1, 1) steer = clip(joystick_axes[1], -1, 1)
# max angle is 45 for angle-based cars, max curvature is 0.02 # max angle is 45 for angle-based cars, max curvature is 0.02
actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * 45., steer * -0.02 actuators.steer, actuators.steeringAngleDeg, actuators.curvature = steer, steer * 90., steer * -0.02
lac_log.active = self.active lac_log.active = self.active
lac_log.steeringAngleDeg = CS.steeringAngleDeg lac_log.steeringAngleDeg = CS.steeringAngleDeg
@ -749,7 +747,7 @@ class Controls:
if current_alert: if current_alert:
hudControl.visualAlert = current_alert.visual_alert hudControl.visualAlert = current_alert.visual_alert
if not self.read_only and self.initialized: if not self.CP.passive and self.initialized:
# send car controls over can # send car controls over can
now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9)
self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) self.last_actuators, can_sends = self.CI.apply(CC, now_nanos)
@ -825,16 +823,18 @@ class Controls:
cs_send.carState.events = car_events cs_send.carState.events = car_events
self.pm.send('carState', cs_send) self.pm.send('carState', cs_send)
# carEvents - logged every second or on change # onroadEvents - logged every second or on change
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
ce_send = messaging.new_message('carEvents', len(self.events)) ce_send = messaging.new_message('onroadEvents', len(self.events))
ce_send.carEvents = car_events ce_send.valid = True
self.pm.send('carEvents', ce_send) ce_send.onroadEvents = car_events
self.pm.send('onroadEvents', ce_send)
self.events_prev = self.events.names.copy() self.events_prev = self.events.names.copy()
# carParams - logged every 50 seconds (> 1 per segment) # carParams - logged every 50 seconds (> 1 per segment)
if (self.sm.frame % int(50. / DT_CTRL) == 0): if (self.sm.frame % int(50. / DT_CTRL) == 0):
cp_send = messaging.new_message('carParams') cp_send = messaging.new_message('carParams')
cp_send.valid = True
cp_send.carParams = self.CP cp_send.carParams = self.CP
self.pm.send('carParams', cp_send) self.pm.send('carParams', cp_send)
@ -862,7 +862,7 @@ class Controls:
self.update_events(CS) self.update_events(CS)
cloudlog.timestamp("Events updated") cloudlog.timestamp("Events updated")
if not self.read_only and self.initialized: if not self.CP.passive and self.initialized:
# Update control state # Update control state
self.state_transition(CS) self.state_transition(CS)
self.prof.checkpoint("State transition") self.prof.checkpoint("State transition")

@ -17,7 +17,7 @@ class LatControl(ABC):
self.steer_max = 1.0 self.steer_max = 1.0
@abstractmethod @abstractmethod
def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk):
pass pass
def reset(self): def reset(self):

@ -11,7 +11,7 @@ class LatControlAngle(LatControl):
super().__init__(CP, CI) super().__init__(CP, CI)
self.sat_check_min_speed = 5. self.sat_check_min_speed = 5.
def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk):
angle_log = log.ControlsState.LateralAngleState.new_message() angle_log = log.ControlsState.LateralAngleState.new_message()
if not active: if not active:

@ -17,7 +17,7 @@ class LatControlPID(LatControl):
super().reset() super().reset()
self.pid.reset() self.pid.reset()
def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk):
pid_log = log.ControlsState.LateralPIDState.new_message() pid_log = log.ControlsState.LateralPIDState.new_message()
pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringAngleDeg = float(CS.steeringAngleDeg)
pid_log.steeringRateDeg = float(CS.steeringRateDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg)

@ -36,7 +36,7 @@ class LatControlTorque(LatControl):
self.torque_params.latAccelOffset = latAccelOffset self.torque_params.latAccelOffset = latAccelOffset
self.torque_params.friction = friction self.torque_params.friction = friction
def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): def update(self, active, CS, VM, params, steer_limited, desired_curvature, desired_curvature_rate, llk):
pid_log = log.ControlsState.LateralTorqueState.new_message() pid_log = log.ControlsState.LateralTorqueState.new_message()
if not active: if not active:

@ -4,7 +4,7 @@ import time
import numpy as np import numpy as np
from cereal import log from cereal import log
from openpilot.common.numpy_fast import clip from openpilot.common.numpy_fast import clip
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
# WARNING: imports outside of constants will not trigger a rebuild # WARNING: imports outside of constants will not trigger a rebuild
from openpilot.selfdrive.modeld.constants import index_function from openpilot.selfdrive.modeld.constants import index_function
from openpilot.selfdrive.car.interfaces import ACCEL_MIN from openpilot.selfdrive.car.interfaces import ACCEL_MIN

@ -15,7 +15,7 @@ from openpilot.selfdrive.controls.lib.longcontrol import LongCtrlState
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC
from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N, get_speed_error
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
LON_MPC_STEP = 0.2 # first step is 0.2s LON_MPC_STEP = 0.2 # first step is 0.2s
A_CRUISE_MIN = -1.2 A_CRUISE_MIN = -1.2

@ -30,13 +30,11 @@ class TestLatControl(unittest.TestCase):
CS.vEgo = 30 CS.vEgo = 30
CS.steeringPressed = False CS.steeringPressed = False
last_actuators = car.CarControl.Actuators.new_message()
params = log.LiveParametersData.new_message() params = log.LiveParametersData.new_message()
llk = gen_llk() llk = gen_llk()
for _ in range(1000): for _ in range(1000):
_, _, lac_log = controller.update(True, CS, VM, params, last_actuators, False, 1, 0, llk) _, _, lac_log = controller.update(True, CS, VM, params, False, 1, 0, llk)
self.assertTrue(lac_log.saturated) self.assertTrue(lac_log.saturated)

@ -4,7 +4,7 @@ import numpy as np
from cereal import car from cereal import car
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import Priority, config_realtime_process from openpilot.common.realtime import Priority, config_realtime_process
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.constants import ModelConstants
from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner
from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner

@ -9,7 +9,7 @@ from cereal import messaging, log, car
from openpilot.common.numpy_fast import interp from openpilot.common.numpy_fast import interp
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.kalman.simple_kalman import KF1D from openpilot.common.kalman.simple_kalman import KF1D
@ -270,6 +270,7 @@ class RadarD:
# publish tracks for UI debugging (keep last) # publish tracks for UI debugging (keep last)
tracks_msg = messaging.new_message('liveTracks', len(self.tracks)) tracks_msg = messaging.new_message('liveTracks', len(self.tracks))
tracks_msg.valid = self.radar_state_valid
for index, tid in enumerate(sorted(self.tracks.keys())): for index, tid in enumerate(sorted(self.tracks.keys())):
tracks_msg.liveTracks[index] = { tracks_msg.liveTracks[index] = {
"trackId": tid, "trackId": tid,

@ -102,4 +102,4 @@ if __name__ == "__main__":
if not new_fw_versions: if not new_fw_versions:
print("No new fw versions found...") print("No new fw versions found...")
add_fw_versions(brand, platform, new_fw_versions) add_fw_versions(brand, platform, new_fw_versions)

@ -32,8 +32,8 @@ if __name__ == "__main__":
end_time = max(end_time, msg.logMonoTime) end_time = max(end_time, msg.logMonoTime)
start_time = min(start_time, msg.logMonoTime) start_time = min(start_time, msg.logMonoTime)
if msg.which() == 'carEvents': if msg.which() == 'onroadEvents':
for e in msg.carEvents: for e in msg.onroadEvents:
cnt_events[e.name] += 1 cnt_events[e.name] += 1
elif msg.which() == 'controlsState': elif msg.which() == 'controlsState':
if len(alerts) == 0 or alerts[-1][1] != msg.controlsState.alertType: if len(alerts) == 0 or alerts[-1][1] != msg.controlsState.alertType:

@ -10,6 +10,8 @@ from openpilot.tools.lib.helpers import save_log
if __name__ == "__main__": if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run process on route and create new logs", parser = argparse.ArgumentParser(description="Run process on route and create new logs",
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
parser.add_argument("--qlog", help="Use qlog instead of log", action="store_true")
parser.add_argument("--fingerprint", help="The fingerprint to use")
parser.add_argument("route", help="The route name to use") parser.add_argument("route", help="The route name to use")
parser.add_argument("process", help="The process to run") parser.add_argument("process", help="The process to run")
args = parser.parse_args() args = parser.parse_args()
@ -17,10 +19,10 @@ if __name__ == "__main__":
cfg = [c for c in CONFIGS if c.proc_name == args.process][0] cfg = [c for c in CONFIGS if c.proc_name == args.process][0]
route = Route(args.route) route = Route(args.route)
lr = MultiLogIterator(route.log_paths()) lr = MultiLogIterator(route.qlog_paths() if args.qlog else route.log_paths())
inputs = list(lr) inputs = list(lr)
outputs = replay_process(cfg, inputs) outputs = replay_process(cfg, inputs, fingerprint=args.fingerprint)
# Remove message generated by the process under test and merge in the new messages # Remove message generated by the process under test and merge in the new messages
produces = {o.which() for o in outputs} produces = {o.which() for o in outputs}

@ -18,7 +18,7 @@ from openpilot.common.conversions import Conversions as CV
from openpilot.common.params import Params, put_nonblocking from openpilot.common.params import Params, put_nonblocking
from openpilot.common.realtime import set_realtime_priority from openpilot.common.realtime import set_realtime_priority
from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot from openpilot.common.transformations.orientation import rot_from_euler, euler_from_rot
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS MIN_SPEED_FILTER = 15 * CV.MPH_TO_MS
MAX_VEL_ANGLE_STD = np.radians(0.25) MAX_VEL_ANGLE_STD = np.radians(0.25)
@ -164,7 +164,7 @@ class Calibrator:
write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5) write_this_cycle = (self.idx == 0) and (self.block_idx % (INPUTS_WANTED//5) == 5)
if self.param_put and write_this_cycle: if self.param_put and write_this_cycle:
put_nonblocking("CalibrationParams", self.get_msg().to_bytes()) put_nonblocking("CalibrationParams", self.get_msg(True).to_bytes())
def handle_v_ego(self, v_ego: float) -> None: def handle_v_ego(self, v_ego: float) -> None:
self.v_ego = v_ego self.v_ego = v_ego
@ -227,12 +227,13 @@ class Calibrator:
return new_rpy return new_rpy
def get_msg(self) -> capnp.lib.capnp._DynamicStructBuilder: def get_msg(self, valid: bool) -> capnp.lib.capnp._DynamicStructBuilder:
smooth_rpy = self.get_smooth_rpy() smooth_rpy = self.get_smooth_rpy()
msg = messaging.new_message('liveCalibration') msg = messaging.new_message('liveCalibration')
liveCalibration = msg.liveCalibration msg.valid = valid
liveCalibration = msg.liveCalibration
liveCalibration.validBlocks = self.valid_blocks liveCalibration.validBlocks = self.valid_blocks
liveCalibration.calStatus = self.cal_status liveCalibration.calStatus = self.cal_status
liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100)
@ -250,19 +251,16 @@ class Calibrator:
return msg return msg
def send_data(self, pm: messaging.PubMaster) -> None: def send_data(self, pm: messaging.PubMaster, valid: bool) -> None:
pm.send('liveCalibration', self.get_msg()) pm.send('liveCalibration', self.get_msg(valid))
def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn: def main() -> NoReturn:
gc.disable() gc.disable()
set_realtime_priority(1) set_realtime_priority(1)
if sm is None: pm = messaging.PubMaster(['liveCalibration'])
sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry']) sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
if pm is None:
pm = messaging.PubMaster(['liveCalibration'])
calibrator = Calibrator(param_put=True) calibrator = Calibrator(param_put=True)
@ -286,11 +284,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m
# 4Hz driven by cameraOdometry # 4Hz driven by cameraOdometry
if sm.frame % 5 == 0: if sm.frame % 5 == 0:
calibrator.send_data(pm) calibrator.send_data(pm, sm.all_checks())
def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn:
calibrationd_thread(sm, pm)
if __name__ == "__main__": if __name__ == "__main__":

@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Any
from cereal import log from cereal import log
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
class NPQueue: class NPQueue:

@ -7,7 +7,7 @@ import numpy as np
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.locationd.models.constants import ObservationKind from openpilot.selfdrive.locationd.models.constants import ObservationKind
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from rednose.helpers.kalmanfilter import KalmanFilter from rednose.helpers.kalmanfilter import KalmanFilter

@ -12,7 +12,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.numpy_fast import clip from openpilot.common.numpy_fast import clip
from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States
from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR from openpilot.selfdrive.locationd.models.constants import GENERATED_DIR
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s

@ -60,6 +60,7 @@ class TestLocationdProc(unittest.TestCase):
msg.cameraOdometry.trans = [0.0, 0.0, 0.0] msg.cameraOdometry.trans = [0.0, 0.0, 0.0]
msg.cameraOdometry.transStd = [0.0, 0.0, 0.0] msg.cameraOdometry.transStd = [0.0, 0.0, 0.0]
msg.logMonoTime = t msg.logMonoTime = t
msg.valid = True
return msg return msg
def test_params_gps(self): def test_params_gps(self):

@ -10,7 +10,7 @@ from cereal import car, log
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.realtime import config_realtime_process, DT_MDL
from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY
from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points_onexit from openpilot.selfdrive.locationd.helpers import PointBuckets, ParameterEstimator, cache_points_onexit

@ -9,7 +9,7 @@ from openpilot.common.basedir import BASEDIR
from openpilot.common.spinner import Spinner from openpilot.common.spinner import Spinner
from openpilot.common.text_window import TextWindow from openpilot.common.text_window import TextWindow
from openpilot.system.hardware import AGNOS from openpilot.system.hardware import AGNOS
from openpilot.system.swaglog import cloudlog, add_file_handler from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import is_dirty from openpilot.system.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

@ -19,7 +19,7 @@ from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_par
from openpilot.selfdrive.manager.process import ensure_running from openpilot.selfdrive.manager.process import ensure_running
from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.manager.process_config import managed_processes
from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID
from openpilot.system.swaglog import cloudlog, add_file_handler from openpilot.common.swaglog import cloudlog, add_file_handler
from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \
get_normalized_origin, terms_version, training_version, \ get_normalized_origin, terms_version, training_version, \
is_tested_branch, is_release_branch is_tested_branch, is_release_branch
@ -37,6 +37,8 @@ def manager_init() -> None:
params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START)
params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_ONROAD_TRANSITION)
params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION) params.clear_all(ParamKeyType.CLEAR_ON_OFFROAD_TRANSITION)
if is_release_branch():
params.clear_all(ParamKeyType.DEVELOPMENT_ONLY)
default_params: List[Tuple[str, Union[str, bytes]]] = [ default_params: List[Tuple[str, Union[str, bytes]]] = [
("CompletedTrainingVersion", "0"), ("CompletedTrainingVersion", "0"),
@ -169,7 +171,7 @@ def manager_thread() -> None:
cloudlog.debug(running) cloudlog.debug(running)
# send managerState # send managerState
msg = messaging.new_message('managerState') msg = messaging.new_message('managerState', valid=True)
msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()] msg.managerState.processes = [p.get_process_state_msg() for p in managed_processes.values()]
pm.send('managerState', msg) pm.send('managerState', msg)

@ -15,7 +15,7 @@ import cereal.messaging as messaging
import openpilot.selfdrive.sentry as sentry import openpilot.selfdrive.sentry as sentry
from openpilot.common.basedir import BASEDIR from openpilot.common.basedir import BASEDIR
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
WATCHDOG_FN = "/dev/shm/wd_" WATCHDOG_FN = "/dev/shm/wd_"
ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None

@ -60,7 +60,7 @@ procs = [
PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad),
NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC), NativeProcess("sensord", "system/sensord", ["./sensord"], only_onroad, enabled=not PC),
NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)), NativeProcess("ui", "selfdrive/ui", ["./ui"], always_run, watchdog_max_dt=(5 if not PC else None)),
NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], only_onroad), PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad),
NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad),
NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False),
PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad),
@ -84,6 +84,7 @@ procs = [
# debug procs # debug procs
NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar),
PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar),
PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar),
] ]

@ -11,7 +11,7 @@ from typing import Tuple, Dict
from cereal import messaging from cereal import messaging
from cereal.messaging import PubMaster, SubMaster from cereal.messaging import PubMaster, SubMaster
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import set_realtime_priority from openpilot.common.realtime import set_realtime_priority
from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime from openpilot.selfdrive.modeld.runners import ModelRunner, Runtime
@ -101,7 +101,7 @@ def fill_driver_state(msg, ds_result: DriverStateResult):
def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float): def get_driverstate_packet(model_output: np.ndarray, frame_id: int, location_ts: int, execution_time: float, dsp_execution_time: float):
model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents model_result = ctypes.cast(model_output.ctypes.data, ctypes.POINTER(DMonitoringModelResult)).contents
msg = messaging.new_message('driverStateV2') msg = messaging.new_message('driverStateV2', valid=True)
ds = msg.driverStateV2 ds = msg.driverStateV2
ds.frameId = frame_id ds.frameId = frame_id
ds.modelExecutionTime = execution_time ds.modelExecutionTime = execution_time

@ -9,7 +9,7 @@ from typing import Dict, Optional
from setproctitle import setproctitle from setproctitle import setproctitle
from cereal.messaging import PubMaster, SubMaster from cereal.messaging import PubMaster, SubMaster
from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import DT_MDL from openpilot.common.realtime import DT_MDL
from openpilot.common.numpy_fast import interp from openpilot.common.numpy_fast import interp

@ -10,7 +10,7 @@ from typing import Tuple, Dict
from cereal import messaging from cereal import messaging
from cereal.messaging import PubMaster, SubMaster from cereal.messaging import PubMaster, SubMaster
from cereal.visionipc import VisionIpcClient, VisionStreamType from cereal.visionipc import VisionIpcClient, VisionStreamType
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.realtime import set_realtime_priority from openpilot.common.realtime import set_realtime_priority
from openpilot.selfdrive.modeld.constants import ModelConstants from openpilot.selfdrive.modeld.constants import ModelConstants

@ -3,7 +3,6 @@ import gc
import cereal.messaging as messaging import cereal.messaging as messaging
from cereal import car from cereal import car
from cereal import log
from openpilot.common.params import Params, put_bool_nonblocking from openpilot.common.params import Params, put_bool_nonblocking
from openpilot.common.realtime import set_realtime_priority from openpilot.common.realtime import set_realtime_priority
from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.controls.lib.events import Events
@ -19,18 +18,12 @@ def dmonitoringd_thread():
driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected")) driver_status = DriverStatus(rhd_saved=Params().get_bool("IsRhdDetected"))
sm['liveCalibration'].calStatus = log.LiveCalibrationData.Status.invalid
sm['liveCalibration'].rpyCalib = [0, 0, 0]
sm['carState'].buttonEvents = []
sm['carState'].standstill = True
v_cruise_last = 0 v_cruise_last = 0
driver_engaged = False driver_engaged = False
# 10Hz <- dmonitoringmodeld # 10Hz <- dmonitoringmodeld
while True: while True:
sm.update() sm.update()
if not sm.updated['driverStateV2']: if not sm.updated['driverStateV2']:
continue continue
@ -48,7 +41,9 @@ def dmonitoringd_thread():
# Get data from dmonitoringmodeld # Get data from dmonitoringmodeld
events = Events() events = Events()
driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled)
if sm.all_checks():
driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled)
# Block engaging after max number of distrations # Block engaging after max number of distrations
if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \
@ -59,7 +54,7 @@ def dmonitoringd_thread():
driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill) driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill)
# build driverMonitoringState packet # build driverMonitoringState packet
dat = messaging.new_message('driverMonitoringState') dat = messaging.new_message('driverMonitoringState', valid=sm.all_checks())
dat.driverMonitoringState = { dat.driverMonitoringState = {
"events": events.to_msg(), "events": events.to_msg(),
"faceDetected": driver_status.face_detected, "faceDetected": driver_status.face_detected,

@ -15,7 +15,7 @@ from openpilot.selfdrive.navd.helpers import (Coordinate, coordinate_from_param,
distance_along_geometry, maxspeed_to_ms, distance_along_geometry, maxspeed_to_ms,
minimum_distance, minimum_distance,
parse_banner_instructions) parse_banner_instructions)
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
REROUTE_DISTANCE = 25 REROUTE_DISTANCE = 25
MANEUVER_TRANSITION_THRESHOLD = 10 MANEUVER_TRANSITION_THRESHOLD = 10
@ -196,7 +196,7 @@ class RouteEngine:
self.send_route() self.send_route()
def send_instruction(self): def send_instruction(self):
msg = messaging.new_message('navInstruction') msg = messaging.new_message('navInstruction', valid=True)
if self.step_idx is None: if self.step_idx is None:
msg.valid = False msg.valid = False
@ -302,7 +302,7 @@ class RouteEngine:
for path in self.route_geometry: for path in self.route_geometry:
coords += [c.as_dict() for c in path] coords += [c.as_dict() for c in path]
msg = messaging.new_message('navRoute') msg = messaging.new_message('navRoute', valid=True)
msg.navRoute.coordinates = coords msg.navRoute.coordinates = coords
self.pm.send('navRoute', msg) self.pm.send('navRoute', msg)

@ -6,7 +6,7 @@ from sentry_sdk.integrations.threading import ThreadingIntegration
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.athena.registration import is_registered_device from openpilot.selfdrive.athena.registration import is_registered_device
from openpilot.system.hardware import HARDWARE, PC from openpilot.system.hardware import HARDWARE, PC
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.version import get_branch, get_commit, get_origin, get_version, \ from openpilot.system.version import get_branch, get_commit, get_origin, get_version, \
is_comma_remote, is_dirty, is_tested_branch is_comma_remote, is_dirty, is_tested_branch

@ -10,7 +10,7 @@ from typing import NoReturn, Union, List, Dict
from openpilot.common.params import Params from openpilot.common.params import Params
from cereal.messaging import SubMaster from cereal.messaging import SubMaster
from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import Paths
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.common.file_helpers import atomic_write_in_dir
from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty from openpilot.system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty

@ -8,4 +8,4 @@ from openpilot.common.prefix import OpenpilotPrefix
with OpenpilotPrefix(): with OpenpilotPrefix():
ret = subprocess.call(sys.argv[1:]) ret = subprocess.call(sys.argv[1:])
exit(ret) exit(ret)

@ -17,7 +17,7 @@ fi
source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX" source $SCRIPT_DIR/docker_common.sh $1 "$TAG_SUFFIX"
DOCKER_BUILDKIT=1 docker buildx build --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR DOCKER_BUILDKIT=1 docker buildx build --pull --platform $PLATFORM --load --cache-to type=inline --cache-from type=registry,ref=$REMOTE_TAG -t $REMOTE_TAG -t $LOCAL_TAG -f $OPENPILOT_DIR/$DOCKER_FILE $OPENPILOT_DIR
if [ -n "$PUSH_IMAGE" ]; then if [ -n "$PUSH_IMAGE" ]; then
docker push $REMOTE_TAG docker push $REMOTE_TAG

@ -71,4 +71,4 @@ def with_processes(processes, init_time=0, ignore_stopped=None):
def noop(*args, **kwargs): def noop(*args, **kwargs):
pass pass

@ -1,10 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import itertools import itertools
import os
from parameterized import parameterized_class
import unittest import unittest
from parameterized import parameterized_class
from openpilot.common.params import Params
from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE from openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import STOP_DISTANCE
from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver from openpilot.selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver
@ -150,18 +148,6 @@ class LongitudinalControl(unittest.TestCase):
e2e: bool e2e: bool
force_decel: bool force_decel: bool
@classmethod
def setUpClass(cls):
os.environ['SIMULATION'] = "1"
os.environ['SKIP_FW_QUERY'] = "1"
os.environ['NO_CAN_TIMEOUT'] = "1"
def setUp(self):
params = Params()
params.clear_all()
params.put_bool("Passive", bool(os.getenv("PASSIVE")))
params.put_bool("OpenpilotEnabledToggle", True)
def test_maneuver(self): def test_maneuver(self):
for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}): for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}):
with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel): with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel):

@ -31,7 +31,7 @@ optional arguments:
--blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd) --blacklist-procs BLACKLIST_PROCS Blacklist given processes from the test (e.g. controlsd)
--blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA) --blacklist-cars BLACKLIST_CARS Blacklist given cars from the test (e.g. HONDA)
--ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. carState.events) --ignore-fields IGNORE_FIELDS Extra fields or msgs to ignore (e.g. carState.events)
--ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. carEvents) --ignore-msgs IGNORE_MSGS Msgs to ignore (e.g. onroadEvents)
--update-refs Updates reference logs using current commit --update-refs Updates reference logs using current commit
--upload-only Skips testing processes and uploads logs from previous test run --upload-only Skips testing processes and uploads logs from previous test run
``` ```

@ -1,37 +0,0 @@
import pytest
from openpilot.selfdrive.test.process_replay.helpers import ALL_PROCS
from openpilot.selfdrive.test.process_replay.test_processes import ALL_CARS
def pytest_addoption(parser: pytest.Parser):
parser.addoption("--whitelist-procs", type=str, nargs="*", default=ALL_PROCS,
help="Whitelist given processes from the test (e.g. controlsd)")
parser.addoption("--whitelist-cars", type=str, nargs="*", default=ALL_CARS,
help="Whitelist given cars from the test (e.g. HONDA)")
parser.addoption("--blacklist-procs", type=str, nargs="*", default=[],
help="Blacklist given processes from the test (e.g. controlsd)")
parser.addoption("--blacklist-cars", type=str, nargs="*", default=[],
help="Blacklist given cars from the test (e.g. HONDA)")
parser.addoption("--ignore-fields", type=str, nargs="*", default=[],
help="Extra fields or msgs to ignore (e.g. carState.events)")
parser.addoption("--ignore-msgs", type=str, nargs="*", default=[],
help="Msgs to ignore (e.g. carEvents)")
parser.addoption("--update-refs", action="store_true",
help="Updates reference logs using current commit")
parser.addoption("--upload-only", action="store_true",
help="Skips testing processes and uploads logs from previous test run")
parser.addoption("--long-diff", action="store_true",
help="Outputs diff in long format")
@pytest.fixture(scope="class", autouse=True)
def process_replay_test_arguments(request):
if hasattr(request.cls, "segment"): # check if a subclass of TestProcessReplayBase
request.cls.tested_procs = list(set(request.config.getoption("--whitelist-procs")) - set(request.config.getoption("--blacklist-procs")))
request.cls.tested_cars = list({c.upper() for c in set(request.config.getoption("--whitelist-cars")) - set(request.config.getoption("--blacklist-cars"))})
request.cls.ignore_fields = request.config.getoption("--ignore-fields")
request.cls.ignore_msgs = request.config.getoption("--ignore-msgs")
request.cls.upload_only = request.config.getoption("--upload-only")
request.cls.update_refs = request.config.getoption("--update-refs")
request.cls.long_diff = request.config.getoption("--long-diff")

@ -1,150 +0,0 @@
#!/usr/bin/env python3
import os
import sys
import unittest
from parameterized import parameterized
from typing import Optional, Union, List
from openpilot.selfdrive.test.openpilotci import get_url, upload_file
from openpilot.selfdrive.test.process_replay.compare_logs import compare_logs, format_process_diff
from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, PROC_REPLAY_DIR, FAKEDATA, replay_process
from openpilot.system.version import get_commit
from openpilot.tools.lib.filereader import FileReader
from openpilot.tools.lib.helpers import save_log
from openpilot.tools.lib.logreader import LogReader, LogIterable
BASE_URL = "https://commadataci.blob.core.windows.net/openpilotci/"
REF_COMMIT_FN = os.path.join(PROC_REPLAY_DIR, "ref_commit")
EXCLUDED_PROCS = {"modeld", "dmonitoringmodeld"}
def get_log_data(segment):
r, n = segment.rsplit("--", 1)
with FileReader(get_url(r, n)) as f:
return f.read()
ALL_PROCS = sorted({cfg.proc_name for cfg in CONFIGS if cfg.proc_name not in EXCLUDED_PROCS})
PROC_TO_CFG = {cfg.proc_name: cfg for cfg in CONFIGS}
cpu_count = os.cpu_count() or 1
class TestProcessReplayBase(unittest.TestCase):
"""
Base class that replays all processes within test_proceses from a segment,
and puts the log messages in self.log_msgs for analysis by other tests.
"""
segment: Optional[Union[str, LogIterable]] = None
tested_procs: List[str] = ALL_PROCS
@classmethod
def setUpClass(cls, create_logs=True):
if "Base" in cls.__name__:
raise unittest.SkipTest("skipping base class")
if isinstance(cls.segment, str):
cls.log_reader = LogReader.from_bytes(get_log_data(cls.segment))
else:
cls.log_reader = cls.segment
if create_logs:
cls._create_log_msgs()
@classmethod
def _run_replay(cls, cfg):
try:
return replay_process(cfg, cls.log_reader, disable_progress=True)
except Exception as e:
raise Exception(f"failed on segment: {cls.segment} \n{e}") from e
@classmethod
def _create_log_msgs(cls):
cls.log_msgs = {}
cls.proc_cfg = {}
for proc in cls.tested_procs:
cfg = PROC_TO_CFG[proc]
log_msgs = cls._run_replay(cfg)
cls.log_msgs[proc] = log_msgs
cls.proc_cfg[proc] = cfg
class TestProcessReplayDiffBase(TestProcessReplayBase):
"""
Base class for checking for diff between process outputs.
"""
update_refs = False
upload_only = False
long_diff = False
ignore_msgs: List[str] = []
ignore_fields: List[str] = []
def setUp(self):
super().setUp()
if self.upload_only:
raise unittest.SkipTest("skipping test, uploading only")
@classmethod
def setUpClass(cls):
super().setUpClass(not cls.upload_only)
if cls.long_diff:
cls.maxDiff = None
os.makedirs(os.path.dirname(FAKEDATA), exist_ok=True)
cls.cur_commit = get_commit()
cls.assertNotEqual(cls.cur_commit, None, "Couldn't get current commit")
cls.upload = cls.update_refs or cls.upload_only
try:
with open(REF_COMMIT_FN) as f:
cls.ref_commit = f.read().strip()
except FileNotFoundError:
print("Couldn't find reference commit")
sys.exit(1)
cls._create_ref_log_msgs()
@classmethod
def _create_ref_log_msgs(cls):
cls.ref_log_msgs = {}
for proc in cls.tested_procs:
cur_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.cur_commit}.bz2")
if cls.update_refs: # reference logs will not exist if routes were just regenerated
ref_log_path = get_url(*cls.segment.rsplit("--", 1))
else:
ref_log_fn = os.path.join(FAKEDATA, f"{cls.segment}_{proc}_{cls.ref_commit}.bz2")
ref_log_path = ref_log_fn if os.path.exists(ref_log_fn) else BASE_URL + os.path.basename(ref_log_fn)
if not cls.upload_only:
save_log(cur_log_fn, cls.log_msgs[proc])
cls.ref_log_msgs[proc] = list(LogReader(ref_log_path))
if cls.upload:
assert os.path.exists(cur_log_fn), f"Cannot find log to upload: {cur_log_fn}"
upload_file(cur_log_fn, os.path.basename(cur_log_fn))
os.remove(cur_log_fn)
@parameterized.expand(ALL_PROCS)
def test_process_diff(self, proc):
if proc not in self.tested_procs:
raise unittest.SkipTest(f"{proc} was not requested to be tested")
cfg = self.proc_cfg[proc]
log_msgs = self.log_msgs[proc]
ref_log_msgs = self.ref_log_msgs[proc]
diff = compare_logs(ref_log_msgs, log_msgs, self.ignore_fields + cfg.ignore, self.ignore_msgs)
diff_short, diff_long = format_process_diff(diff)
self.assertEqual(len(diff), 0, "\n" + diff_long if self.long_diff else diff_short)

@ -85,6 +85,7 @@ def migrate_peripheralState(lr):
continue continue
new_msg = messaging.new_message("peripheralState") new_msg = messaging.new_message("peripheralState")
new_msg.valid = msg.valid
new_msg.logMonoTime = msg.logMonoTime new_msg.logMonoTime = msg.logMonoTime
all_msg.append(new_msg.as_reader()) all_msg.append(new_msg.as_reader())
@ -149,6 +150,7 @@ def migrate_carParams(lr, old_logtime=False):
for msg in lr: for msg in lr:
if msg.which() == 'carParams': if msg.which() == 'carParams':
CP = messaging.new_message('carParams') CP = messaging.new_message('carParams')
CP.valid = True
CP.carParams = msg.carParams.as_builder() CP.carParams = msg.carParams.as_builder()
for car_fw in CP.carParams.carFw: for car_fw in CP.carParams.carFw:
car_fw.brand = CP.carParams.carName car_fw.brand = CP.carParams.carName

@ -442,8 +442,8 @@ def controlsd_config_callback(params, cfg, lr):
controlsState = msg.controlsState controlsState = msg.controlsState
if initialized: if initialized:
break break
elif msg.which() == "carEvents": elif msg.which() == "onroadEvents":
initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.carEvents] initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.onroadEvents]
assert controlsState is not None and initialized, "controlsState never initialized" assert controlsState is not None and initialized, "controlsState never initialized"
params.put("ReplayControlsState", controlsState.as_builder().to_bytes()) params.put("ReplayControlsState", controlsState.as_builder().to_bytes())
@ -465,8 +465,8 @@ CONFIGS = [
"modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState",
"testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope"
], ],
subs=["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"], subs=["controlsState", "carState", "carControl", "sendcan", "onroadEvents", "carParams"],
ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], ignore=["logMonoTime", "controlsState.startMonoTime", "controlsState.cumLagMs"],
config_callback=controlsd_config_callback, config_callback=controlsd_config_callback,
init_callback=controlsd_fingerprint_callback, init_callback=controlsd_fingerprint_callback,
should_recv_callback=controlsd_rcv_callback, should_recv_callback=controlsd_rcv_callback,
@ -478,7 +478,7 @@ CONFIGS = [
proc_name="radard", proc_name="radard",
pubs=["can", "carState", "modelV2"], pubs=["can", "carState", "modelV2"],
subs=["radarState", "liveTracks"], subs=["radarState", "liveTracks"],
ignore=["logMonoTime", "valid", "radarState.cumLagMs"], ignore=["logMonoTime", "radarState.cumLagMs"],
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=MessageBasedRcvCallback("can"), should_recv_callback=MessageBasedRcvCallback("can"),
main_pub="can", main_pub="can",
@ -487,7 +487,7 @@ CONFIGS = [
proc_name="plannerd", proc_name="plannerd",
pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"],
subs=["lateralPlan", "longitudinalPlan", "uiPlan"], subs=["lateralPlan", "longitudinalPlan", "uiPlan"],
ignore=["logMonoTime", "valid", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], ignore=["logMonoTime", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"],
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("modelV2"), should_recv_callback=FrequencyBasedRcvCallback("modelV2"),
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
@ -496,14 +496,14 @@ CONFIGS = [
proc_name="calibrationd", proc_name="calibrationd",
pubs=["carState", "cameraOdometry", "carParams"], pubs=["carState", "cameraOdometry", "carParams"],
subs=["liveCalibration"], subs=["liveCalibration"],
ignore=["logMonoTime", "valid"], ignore=["logMonoTime"],
should_recv_callback=calibration_rcv_callback, should_recv_callback=calibration_rcv_callback,
), ),
ProcessConfig( ProcessConfig(
proc_name="dmonitoringd", proc_name="dmonitoringd",
pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "controlsState"], pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "controlsState"],
subs=["driverMonitoringState"], subs=["driverMonitoringState"],
ignore=["logMonoTime", "valid"], ignore=["logMonoTime"],
should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"), should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"),
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
), ),
@ -514,7 +514,7 @@ CONFIGS = [
"liveCalibration", "carState", "carParams", "gpsLocation" "liveCalibration", "carState", "carParams", "gpsLocation"
], ],
subs=["liveLocationKalman"], subs=["liveLocationKalman"],
ignore=["logMonoTime", "valid"], ignore=["logMonoTime"],
config_callback=locationd_config_pubsub_callback, config_callback=locationd_config_pubsub_callback,
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,
), ),
@ -522,7 +522,7 @@ CONFIGS = [
proc_name="paramsd", proc_name="paramsd",
pubs=["liveLocationKalman", "carState"], pubs=["liveLocationKalman", "carState"],
subs=["liveParameters"], subs=["liveParameters"],
ignore=["logMonoTime", "valid"], ignore=["logMonoTime"],
init_callback=get_car_params_callback, init_callback=get_car_params_callback,
should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"), should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"),
tolerance=NUMPY_TOLERANCE, tolerance=NUMPY_TOLERANCE,

@ -1 +1 @@
dbea36698ba48429b201b138846165eb4c329b92 921222d49db204071f0a7006fc895690e1045b5d

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

Loading…
Cancel
Save