Merge remote-tracking branch 'upstream/master' into fuzzy-panda-carstate

pull/30443/head
Shane Smiskol 2 years ago
commit 55de2c173e
  1. 2
      .github/workflows/docs.yaml
  2. 2
      .github/workflows/selfdrive_tests.yaml
  3. 3
      .gitmodules
  4. 6
      .pre-commit-config.yaml
  5. 10
      Jenkinsfile
  6. 6
      RELEASES.md
  7. 2
      cereal
  8. 4
      common/api/__init__.py
  9. 7
      common/basedir.py
  10. 10
      common/file_helpers.py
  11. 2
      common/params.cc
  12. 1
      common/params.h
  13. 1
      common/params_pyx.pyx
  14. 30
      common/retry.py
  15. 0
      common/swaglog.py
  16. 46
      common/xattr.py
  17. 5
      conftest.py
  18. 9
      docs/CARS.md
  19. 8
      docs/c_docs.rst
  20. 2
      launch_env.sh
  21. 2
      opendbc
  22. 2
      panda
  23. 81
      poetry.lock
  24. 5
      pyproject.toml
  25. 1
      release/build_devel.sh
  26. 46
      release/files_common
  27. 27
      scripts/retry.sh
  28. BIN
      selfdrive/assets/sounds/warning_immediate.wav
  29. 14
      selfdrive/athena/athenad.py
  30. 2
      selfdrive/athena/manage_athenad.py
  31. 8
      selfdrive/athena/registration.py
  32. 2
      selfdrive/athena/tests/test_athenad.py
  33. 30
      selfdrive/athena/tests/test_athenad_ping.py
  34. 16
      selfdrive/athena/tests/test_registration.py
  35. 2
      selfdrive/boardd/pandad.py
  36. 2
      selfdrive/car/car_helpers.py
  37. 2
      selfdrive/car/disable_ecu.py
  38. 2
      selfdrive/car/ecu_addrs.py
  39. 7
      selfdrive/car/ford/values.py
  40. 2
      selfdrive/car/fw_versions.py
  41. 16
      selfdrive/car/hyundai/values.py
  42. 2
      selfdrive/car/isotp_parallel_query.py
  43. 23
      selfdrive/car/subaru/values.py
  44. 4
      selfdrive/car/tests/test_fw_fingerprint.py
  45. 9
      selfdrive/car/tests/test_models.py
  46. 2
      selfdrive/car/torque_data/params.yaml
  47. 23
      selfdrive/car/toyota/carcontroller.py
  48. 3
      selfdrive/car/toyota/interface.py
  49. 4
      selfdrive/car/toyota/tests/test_toyota.py
  50. 14
      selfdrive/car/toyota/toyotacan.py
  51. 3
      selfdrive/car/toyota/values.py
  52. 2
      selfdrive/car/vin.py
  53. 52
      selfdrive/controls/controlsd.py
  54. 2
      selfdrive/controls/lib/latcontrol.py
  55. 2
      selfdrive/controls/lib/latcontrol_angle.py
  56. 2
      selfdrive/controls/lib/latcontrol_pid.py
  57. 2
      selfdrive/controls/lib/latcontrol_torque.py
  58. 2
      selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py
  59. 2
      selfdrive/controls/lib/longitudinal_planner.py
  60. 4
      selfdrive/controls/lib/tests/test_latcontrol.py
  61. 2
      selfdrive/controls/plannerd.py
  62. 3
      selfdrive/controls/radard.py
  63. 4
      selfdrive/debug/count_events.py
  64. 26
      selfdrive/locationd/calibrationd.py
  65. 2
      selfdrive/locationd/helpers.py
  66. 2
      selfdrive/locationd/models/car_kf.py
  67. 2
      selfdrive/locationd/paramsd.py
  68. 1
      selfdrive/locationd/test/test_locationd.py
  69. 2
      selfdrive/locationd/torqued.py
  70. 2
      selfdrive/manager/build.py
  71. 6
      selfdrive/manager/manager.py
  72. 2
      selfdrive/manager/process.py
  73. 3
      selfdrive/manager/process_config.py
  74. 4
      selfdrive/modeld/dmonitoringmodeld.py
  75. 2
      selfdrive/modeld/modeld.py
  76. 2
      selfdrive/modeld/navmodeld.py
  77. 11
      selfdrive/monitoring/dmonitoringd.py
  78. 6
      selfdrive/navd/navd.py
  79. 2
      selfdrive/sentry.py
  80. 7
      selfdrive/statsd.py
  81. 2
      selfdrive/test/docker_build.sh
  82. 15
      selfdrive/test/longitudinal_maneuvers/test_longitudinal.py
  83. 2
      selfdrive/test/process_replay/README.md
  84. 2
      selfdrive/test/process_replay/conftest.py
  85. 2
      selfdrive/test/process_replay/migration.py
  86. 20
      selfdrive/test/process_replay/process_replay.py
  87. 2
      selfdrive/test/process_replay/ref_commit
  88. 5
      selfdrive/test/pytest-tici.ini
  89. 1
      selfdrive/test/setup_device_ci.sh
  90. 12
      selfdrive/test/test_onroad.py
  91. 4
      selfdrive/test/test_time_to_onroad.py
  92. 2
      selfdrive/thermald/fan_controller.py
  93. 2
      selfdrive/thermald/power_monitoring.py
  94. 4
      selfdrive/thermald/thermald.py
  95. 5
      selfdrive/tombstoned.py
  96. 6
      selfdrive/ui/SConscript
  97. 0
      selfdrive/ui/__init__.py
  98. 25
      selfdrive/ui/qt/widgets/controls.cc
  99. 20
      selfdrive/ui/qt/widgets/controls.h
  100. 161
      selfdrive/ui/soundd.py
  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'

@ -268,7 +268,7 @@ jobs:
${{ env.RUN }} "scons -j$(nproc)" ${{ env.RUN }} "scons -j$(nproc)"
# PYTHONWARNINGS triggers a SyntaxError in onnxruntime # PYTHONWARNINGS triggers a SyntaxError in onnxruntime
- name: Run model replay with ONNX - name: Run model replay with ONNX
timeout-minutes: 3 timeout-minutes: 4
run: | run: |
${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \
ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \

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 a3a6e4969e58875f7cdfc9e6cc6b1af3ee2392b5

@ -2,7 +2,7 @@ import jwt
import os import os
import requests import requests
from datetime import datetime, timedelta from datetime import datetime, timedelta
from openpilot.common.basedir import PERSIST from openpilot.system.hardware.hw import Paths
from openpilot.system.version import get_version from openpilot.system.version import get_version
API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com') API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
@ -10,7 +10,7 @@ API_HOST = os.getenv('API_HOST', 'https://api.commadotai.com')
class Api(): class Api():
def __init__(self, dongle_id): def __init__(self, dongle_id):
self.dongle_id = dongle_id self.dongle_id = dongle_id
with open(PERSIST+'/comma/id_rsa') as f: with open(Paths.persist_root()+'/comma/id_rsa') as f:
self.private_key = f.read() self.private_key = f.read()
def get(self, *args, **kwargs): def get(self, *args, **kwargs):

@ -1,11 +1,4 @@
import os import os
from pathlib import Path
from openpilot.system.hardware import PC
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__)), "../"))
if PC:
PERSIST = os.path.join(str(Path.home()), ".comma", "persist")
else:
PERSIST = "/persist"

@ -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)

@ -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":

@ -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()

@ -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})")

@ -45,8 +45,9 @@ 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)

@ -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>||
@ -99,7 +100,7 @@ A supported vehicle is one that just works when you install a comma device. All
|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)|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 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>|| |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>||
|Hyundai|Santa Fe Plug-in 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 Plug-in Hybrid 2022-23">Buy Here</a></sub></details>|| |Hyundai|Santa Fe Plug-in 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 Plug-in Hybrid 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>|

@ -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::

@ -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 5b0c73977f1428700d0344d52874a90a4c5168fb

@ -1 +1 @@
Subproject commit 09465a753c82fbf9d467cb0fa02201769f9b33e5 Subproject commit a5753a2077288b066e441dc2ba2f96d8062e5e49

81
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"

@ -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/",
] ]
@ -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

@ -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,7 +280,6 @@ 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
@ -300,10 +288,7 @@ selfdrive/ui/*.h
selfdrive/ui/ui 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 +318,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 +428,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/**

@ -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

@ -31,21 +31,16 @@ import cereal.messaging as messaging
from cereal import log from cereal import log
from cereal.services import SERVICE_LIST from cereal.services import SERVICE_LIST
from openpilot.common.api import Api from openpilot.common.api import Api
from openpilot.common.basedir import PERSIST
from openpilot.common.file_helpers import CallbackReader from openpilot.common.file_helpers import CallbackReader
from openpilot.common.params import Params 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.selfdrive.statsd import STATS_DIR from openpilot.common.swaglog import cloudlog
from openpilot.system.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}
@ -502,10 +497,10 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local
@dispatcher.add_method @dispatcher.add_method
def getPublicKey() -> Optional[str]: def getPublicKey() -> Optional[str]:
if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): if not os.path.isfile(Paths.persist_root() + '/comma/id_rsa.pub'):
return None return None
with open(PERSIST + '/comma/id_rsa.pub') as f: with open(Paths.persist_root() + '/comma/id_rsa.pub') as f:
return f.read() return f.read()
@ -641,6 +636,7 @@ def log_handler(end_event: threading.Event) -> None:
def stat_handler(end_event: threading.Event) -> None: def stat_handler(end_event: threading.Event) -> None:
STATS_DIR = Paths.stats_root()
while not end_event.is_set(): while not end_event.is_set():
last_scan = 0. last_scan = 0.
curr_scan = time.monotonic() curr_scan = time.monotonic()
@ -761,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"

@ -9,10 +9,10 @@ from datetime import datetime, timedelta
from openpilot.common.api import api_get from openpilot.common.api import api_get
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.common.spinner import Spinner from openpilot.common.spinner import Spinner
from openpilot.common.basedir import PERSIST
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.swaglog import cloudlog from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
UNREGISTERED_DONGLE_ID = "UnregisteredDevice" UNREGISTERED_DONGLE_ID = "UnregisteredDevice"
@ -32,7 +32,7 @@ def register(show_spinner=False) -> Optional[str]:
dongle_id: Optional[str] = params.get("DongleId", encoding='utf8') dongle_id: Optional[str] = params.get("DongleId", encoding='utf8')
needs_registration = None in (IMEI, HardwareSerial, dongle_id) needs_registration = None in (IMEI, HardwareSerial, dongle_id)
pubkey = Path(PERSIST+"/comma/id_rsa.pub") pubkey = Path(Paths.persist_root()+"/comma/id_rsa.pub")
if not pubkey.is_file(): if not pubkey.is_file():
dongle_id = UNREGISTERED_DONGLE_ID dongle_id = UNREGISTERED_DONGLE_ID
cloudlog.warning(f"missing public key: {pubkey}") cloudlog.warning(f"missing public key: {pubkey}")
@ -42,7 +42,7 @@ def register(show_spinner=False) -> Optional[str]:
spinner.update("registering device") spinner.update("registering device")
# Create registration token, in the future, this key will make JWTs directly # Create registration token, in the future, this key will make JWTs directly
with open(PERSIST+"/comma/id_rsa.pub") as f1, open(PERSIST+"/comma/id_rsa") as f2: with open(Paths.persist_root()+"/comma/id_rsa.pub") as f1, open(Paths.persist_root()+"/comma/id_rsa") as f2:
public_key = f1.read() public_key = f1.read()
private_key = f2.read() private_key = f2.read()

@ -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,38 +36,32 @@ 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.params = Params()
cls.dongle_id = cls.params.get("DongleId", encoding="utf-8")
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.dongle_id = self.params.get("DongleId", encoding="utf-8")
wifi_radio(True) wifi_radio(True)
self._clear_ping_time() self._clear_ping_time()
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"):
@ -77,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"):
@ -85,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,7 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import json import json
import os
import tempfile
import unittest import unittest
from Crypto.PublicKey import RSA from Crypto.PublicKey import RSA
from pathlib import Path from pathlib import Path
@ -10,6 +8,7 @@ from unittest import mock
from openpilot.common.params import Params from openpilot.common.params import Params
from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID
from openpilot.selfdrive.athena.tests.helpers import MockResponse from openpilot.selfdrive.athena.tests.helpers import MockResponse
from openpilot.system.hardware.hw import Paths
class TestRegistration(unittest.TestCase): class TestRegistration(unittest.TestCase):
@ -19,16 +18,11 @@ class TestRegistration(unittest.TestCase):
self.params = Params() self.params = Params()
self.params.clear_all() self.params.clear_all()
self.persist = tempfile.TemporaryDirectory() persist_dir = Path(Paths.persist_root()) / "comma"
os.mkdir(os.path.join(self.persist.name, "comma")) persist_dir.mkdir(parents=True, exist_ok=True)
self.priv_key = Path(os.path.join(self.persist.name, "comma/id_rsa"))
self.pub_key = Path(os.path.join(self.persist.name, "comma/id_rsa.pub"))
self.persist_patcher = mock.patch("openpilot.selfdrive.athena.registration.PERSIST", self.persist.name)
self.persist_patcher.start()
def tearDown(self): self.priv_key = persist_dir / "id_rsa"
self.persist_patcher.stop() self.pub_key = persist_dir / "id_rsa.pub"
self.persist.cleanup()
def _generate_keys(self): def _generate_keys(self):
self.pub_key.touch() self.pub_key.touch()

@ -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:

@ -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"),
@ -142,6 +142,7 @@ FW_VERSIONS = {
b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'N1PA-14C204-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
b'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00',
], ],
}, },
CAR.ESCAPE_MK4: { CAR.ESCAPE_MK4: {
@ -184,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',
@ -191,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',
@ -211,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]

@ -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])),
@ -191,7 +194,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = {
# TODO: this is the 2024 US MY, not yet released # TODO: this is the 2024 US MY, not yet released
CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw", CAR.KONA_EV_2ND_GEN: HyundaiCarInfo("Hyundai Kona Electric (with HDA II, Korea only) 2023", video_link="https://www.youtube.com/watch?v=U2fOCmcQ8hw",
car_parts=CarParts.common([CarHarness.hyundai_r])), car_parts=CarParts.common([CarHarness.hyundai_r])),
CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), CAR.SANTA_FE: HyundaiCarInfo("Hyundai Santa Fe 2019-20", "All", video_link="https://youtu.be/bjDR0YjM__s",
car_parts=CarParts.common([CarHarness.hyundai_d])),
CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4", CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-23", "All", video_link="https://youtu.be/VnHzSTygTS4",
car_parts=CarParts.common([CarHarness.hyundai_l])), car_parts=CarParts.common([CarHarness.hyundai_l])),
CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022-23", "All", car_parts=CarParts.common([CarHarness.hyundai_l])),
@ -555,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: {
@ -1912,7 +1921,8 @@ FW_VERSIONS = {
], ],
(Ecu.fwdCamera, 0x7c4, None): [ (Ecu.fwdCamera, 0x7c4, None): [
b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217',
] b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.10 99210-P2000 210406',
],
}, },
CAR.KIA_EV6: { CAR.KIA_EV6: {
(Ecu.fwdRadar, 0x7d0, None): [ (Ecu.fwdRadar, 0x7d0, None): [

@ -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

@ -126,7 +126,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = {
CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"),
CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"), CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"),
CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"), CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"),
CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022", "All", car_parts=CarParts.common([CarHarness.subaru_c])), CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022-23", "All", car_parts=CarParts.common([CarHarness.subaru_c])),
CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])),
} }
@ -141,12 +141,30 @@ FW_QUERY_CONFIG = FwQueryConfig(
Request( Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], [StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
), ),
# Some Eyesight modules fail on TESTER_PRESENT_REQUEST # Some Eyesight modules fail on TESTER_PRESENT_REQUEST
# TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars # TODO: check if this resolves the fingerprinting issue for the 2023 Ascent and other new Subaru cars
Request( Request(
[SUBARU_VERSION_REQUEST], [SUBARU_VERSION_REQUEST],
[SUBARU_VERSION_RESPONSE], [SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.fwdCamera],
),
# Non-OBD requests
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
bus=0,
logging=True,
),
Request(
[StdQueries.TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST],
[StdQueries.TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE],
whitelist_ecus=[Ecu.abs, Ecu.eps, Ecu.fwdCamera, Ecu.engine, Ecu.transmission],
bus=1,
logging=True,
obd_multiplexing=False,
), ),
], ],
) )
@ -671,7 +689,8 @@ FW_VERSIONS = {
b'=\xc04\x02', b'=\xc04\x02',
], ],
(Ecu.fwdCamera, 0x787, None): [ (Ecu.fwdCamera, 0x787, None): [
b'\x04!\x01\x1eD\x07!\x00\x04,' b'\x04!\x01\x1eD\x07!\x00\x04,',
b'\x04!\x08\x01.\x07!\x08\x022',
], ],
(Ecu.engine, 0x7e0, None): [ (Ecu.engine, 0x7e0, None): [
b'\xd5"a0\x07', b'\xd5"a0\x07',

@ -227,7 +227,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
@pytest.mark.timeout(60) @pytest.mark.timeout(60)
def test_fw_query_timing(self): def test_fw_query_timing(self):
total_ref_time = 6.07 total_ref_time = 6.41
brand_ref_times = { brand_ref_times = {
1: { 1: {
'body': 0.11, 'body': 0.11,
@ -237,7 +237,7 @@ class TestFwFingerprintTiming(unittest.TestCase):
'hyundai': 0.72, 'hyundai': 0.72,
'mazda': 0.2, 'mazda': 0.2,
'nissan': 0.4, 'nissan': 0.4,
'subaru': 0.2, 'subaru': 0.52,
'tesla': 0.2, 'tesla': 0.2,
'toyota': 1.6, 'toyota': 1.6,
'volkswagen': 0.2, 'volkswagen': 0.2,

@ -233,7 +233,7 @@ class TestCarModelBase(unittest.TestCase):
error_cnt += car.RadarData.Error.canError in rr.errors error_cnt += car.RadarData.Error.canError in rr.errors
self.assertEqual(error_cnt, 0) self.assertEqual(error_cnt, 0)
def test_panda_safety_rx_valid(self): def test_panda_safety_rx_checks(self):
raise unittest.SkipTest raise unittest.SkipTest
if self.CP.dashcamOnly: if self.CP.dashcamOnly:
self.skipTest("no need to check panda safety for dashcamOnly") self.skipTest("no need to check panda safety for dashcamOnly")
@ -269,6 +269,11 @@ class TestCarModelBase(unittest.TestCase):
self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}") self.assertFalse(len(failed_addrs), f"panda safety RX check failed: {failed_addrs}")
# ensure RX checks go invalid after small time with no traffic
self.safety.set_timer(int(t + (2*1e6)))
self.safety.safety_tick_current_safety_config()
self.assertFalse(self.safety.safety_config_valid())
def test_panda_safety_tx_cases(self, data=None): def test_panda_safety_tx_cases(self, data=None):
raise unittest.SkipTest raise unittest.SkipTest
"""Asserts we can tx common messages""" """Asserts we can tx common messages"""
@ -499,7 +504,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

@ -37,7 +37,7 @@ HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0
HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393]
HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593]
HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382]
HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] HYUNDAI TUCSON HYBRID 4TH GEN: [2.960174, 2.860284, 0.108745]
JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048]
JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003]
KIA EV6 2022: [3.2, 2.093457, 0.05] KIA EV6 2022: [3.2, 2.093457, 0.05]

@ -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}"

@ -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,6 +371,7 @@ class Controls:
else: else:
self.logged_comm_issue = None self.logged_comm_issue = None
if not (self.CP.notCar and self.joystick_enabled):
if not self.sm['lateralPlan'].mpcSolutionValid: if not self.sm['lateralPlan'].mpcSolutionValid:
self.events.add(EventName.plannerError) self.events.add(EventName.plannerError)
if not self.sm['liveLocationKalman'].posenetOK: if not self.sm['liveLocationKalman'].posenetOK:
@ -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,

@ -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:

@ -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:
sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
if pm is None:
pm = messaging.PubMaster(['liveCalibration']) pm = messaging.PubMaster(['liveCalibration'])
sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry'])
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,6 +41,8 @@ def dmonitoringd_thread():
# Get data from dmonitoringmodeld # Get data from dmonitoringmodeld
events = Events() events = Events()
if sm.all_checks():
driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) 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
@ -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

@ -9,11 +9,12 @@ 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.swaglog import cloudlog from openpilot.system.hardware.hw import Paths
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
from openpilot.system.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S from openpilot.system.loggerd.config import STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S
class METRIC_TYPE: class METRIC_TYPE:
@ -80,6 +81,8 @@ def main() -> NoReturn:
sock = ctx.socket(zmq.PULL) sock = ctx.socket(zmq.PULL)
sock.bind(STATS_SOCKET) sock.bind(STATS_SOCKET)
STATS_DIR = Paths.stats_root()
# initialize stats directory # initialize stats directory
Path(STATS_DIR).mkdir(parents=True, exist_ok=True) Path(STATS_DIR).mkdir(parents=True, exist_ok=True)

@ -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

@ -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,17 +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"
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
``` ```

@ -16,7 +16,7 @@ def pytest_addoption(parser: pytest.Parser):
parser.addoption("--ignore-fields", type=str, nargs="*", default=[], parser.addoption("--ignore-fields", type=str, nargs="*", default=[],
help="Extra fields or msgs to ignore (e.g. carState.events)") help="Extra fields or msgs to ignore (e.g. carState.events)")
parser.addoption("--ignore-msgs", type=str, nargs="*", default=[], parser.addoption("--ignore-msgs", type=str, nargs="*", default=[],
help="Msgs to ignore (e.g. carEvents)") help="Msgs to ignore (e.g. onroadEvents)")
parser.addoption("--update-refs", action="store_true", parser.addoption("--update-refs", action="store_true",
help="Updates reference logs using current commit") help="Updates reference logs using current commit")
parser.addoption("--upload-only", action="store_true", parser.addoption("--upload-only", action="store_true",

@ -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

@ -1,5 +0,0 @@
[pytest]
addopts = -Werror --strict-config --strict-markers
markers =
slow: tests that take awhile to run and can be skipped with -m 'not slow'
tici: tests that are only meant to run on the C3/C3X

@ -29,7 +29,6 @@ sudo abctl --set_success
# patch sshd config # patch sshd config
sudo mount -o rw,remount / sudo mount -o rw,remount /
echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//') | sudo tee /etc/hostname
sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config
sudo systemctl daemon-reload sudo systemctl daemon-reload
sudo systemctl restart ssh sudo systemctl restart ssh

@ -34,8 +34,8 @@ PROCS = {
"./encoderd": 17.0, "./encoderd": 17.0,
"./camerad": 14.5, "./camerad": 14.5,
"./locationd": 11.0, "./locationd": 11.0,
"./mapsd": 1.5, "./mapsd": (1.0, 10.0),
"selfdrive.controls.plannerd": 16.5, "selfdrive.controls.plannerd": 11.0,
"./_ui": 18.0, "./_ui": 18.0,
"selfdrive.locationd.paramsd": 9.0, "selfdrive.locationd.paramsd": 9.0,
"./sensord": 7.0, "./sensord": 7.0,
@ -46,7 +46,7 @@ PROCS = {
"selfdrive.thermald.thermald": 3.87, "selfdrive.thermald.thermald": 3.87,
"selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.calibrationd": 2.0,
"selfdrive.locationd.torqued": 5.0, "selfdrive.locationd.torqued": 5.0,
"./_soundd": (1.0, 65.0), "selfdrive.ui.soundd": 5.8,
"selfdrive.monitoring.dmonitoringd": 4.0, "selfdrive.monitoring.dmonitoringd": 4.0,
"./proclogd": 1.54, "./proclogd": 1.54,
"system.logmessaged": 0.2, "system.logmessaged": 0.2,
@ -408,7 +408,7 @@ class TestOnroad(unittest.TestCase):
def test_startup(self): def test_startup(self):
startup_alert = None startup_alert = None
for msg in self.lrs[0]: for msg in self.lrs[0]:
# can't use carEvents because the first msg can be dropped while loggerd is starting up # can't use onroadEvents because the first msg can be dropped while loggerd is starting up
if msg.which() == "controlsState": if msg.which() == "controlsState":
startup_alert = msg.controlsState.alertText1 startup_alert = msg.controlsState.alertText1
break break
@ -417,8 +417,8 @@ class TestOnroad(unittest.TestCase):
def test_engagable(self): def test_engagable(self):
no_entries = Counter() no_entries = Counter()
for m in self.service_msgs['carEvents']: for m in self.service_msgs['onroadEvents']:
for evt in m.carEvents: for evt in m.onroadEvents:
if evt.noEntry: if evt.noEntry:
no_entries[evt.name] += 1 no_entries[evt.name] += 1

@ -18,7 +18,7 @@ def test_time_to_onroad():
proc = subprocess.Popen(["python", manager_path]) proc = subprocess.Popen(["python", manager_path])
start_time = time.monotonic() start_time = time.monotonic()
sm = messaging.SubMaster(['controlsState', 'deviceState', 'carEvents']) sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents'])
try: try:
# wait for onroad # wait for onroad
with Timeout(20, "timed out waiting to go onroad"): with Timeout(20, "timed out waiting to go onroad"):
@ -40,7 +40,7 @@ def test_time_to_onroad():
# once we're enageable, must be for the next few seconds # once we're enageable, must be for the next few seconds
for _ in range(500): for _ in range(500):
sm.update(100) sm.update(100)
assert sm['controlsState'].engageable, f"events: {sm['carEvents']}" assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}"
finally: finally:
proc.terminate() proc.terminate()
if proc.wait(60) is None: if proc.wait(60) is None:

@ -3,7 +3,7 @@ from abc import ABC, abstractmethod
from openpilot.common.realtime import DT_TRML from openpilot.common.realtime import DT_TRML
from openpilot.common.numpy_fast import interp from openpilot.common.numpy_fast import interp
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.controls.lib.pid import PIDController from openpilot.selfdrive.controls.lib.pid import PIDController
class BaseFanController(ABC): class BaseFanController(ABC):

@ -4,7 +4,7 @@ from typing import Optional
from openpilot.common.params import Params, put_nonblocking from openpilot.common.params import Params, put_nonblocking
from openpilot.system.hardware import HARDWARE from openpilot.system.hardware import HARDWARE
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.statsd import statlog from openpilot.selfdrive.statsd import statlog
CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1))

@ -23,7 +23,7 @@ from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert
from openpilot.system.hardware import HARDWARE, TICI, AGNOS from openpilot.system.hardware import HARDWARE, TICI, AGNOS
from openpilot.system.loggerd.config import get_available_percent from openpilot.system.loggerd.config import get_available_percent
from openpilot.selfdrive.statsd import statlog from openpilot.selfdrive.statsd import statlog
from openpilot.system.swaglog import cloudlog from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring from openpilot.selfdrive.thermald.power_monitoring import PowerMonitoring
from openpilot.selfdrive.thermald.fan_controller import TiciFanController from openpilot.selfdrive.thermald.fan_controller import TiciFanController
from openpilot.system.version import terms_version, training_version from openpilot.system.version import terms_version, training_version
@ -81,7 +81,7 @@ def read_tz(x):
def read_thermal(thermal_config): def read_thermal(thermal_config):
dat = messaging.new_message('deviceState') dat = messaging.new_message('deviceState', valid=True)
dat.deviceState.cpuTempC = [read_tz(z) / thermal_config.cpu[1] for z in thermal_config.cpu[0]] dat.deviceState.cpuTempC = [read_tz(z) / thermal_config.cpu[1] for z in thermal_config.cpu[0]]
dat.deviceState.gpuTempC = [read_tz(z) / thermal_config.gpu[1] for z in thermal_config.gpu[0]] dat.deviceState.gpuTempC = [read_tz(z) / thermal_config.gpu[1] for z in thermal_config.gpu[0]]
dat.deviceState.memoryTempC = read_tz(thermal_config.mem[0]) / thermal_config.mem[1] dat.deviceState.memoryTempC = read_tz(thermal_config.mem[0]) / thermal_config.mem[1]

@ -9,10 +9,9 @@ import time
import glob import glob
from typing import NoReturn from typing import NoReturn
from openpilot.common.file_helpers import mkdirs_exists_ok
import openpilot.selfdrive.sentry as sentry import openpilot.selfdrive.sentry as sentry
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.version import get_commit from openpilot.system.version import get_commit
MAX_SIZE = 1_000_000 * 100 # allow up to 100M MAX_SIZE = 1_000_000 * 100 # allow up to 100M
@ -128,7 +127,7 @@ def report_tombstone_apport(fn):
new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN]
crashlog_dir = os.path.join(Paths.log_root(), "crash") crashlog_dir = os.path.join(Paths.log_root(), "crash")
mkdirs_exists_ok(crashlog_dir) os.makedirs(crashlog_dir, exist_ok=True)
# Files could be on different filesystems, copy, then delete # Files could be on different filesystems, copy, then delete
shutil.copy(fn, os.path.join(crashlog_dir, new_fn)) shutil.copy(fn, os.path.join(crashlog_dir, new_fn))

@ -70,12 +70,6 @@ qt_env.Command(assets, [assets_src, translations_assets_src], f"rcc $SOURCES -o
qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease]) qt_env.Depends(assets, Glob('#selfdrive/assets/*', exclude=[assets, assets_src, translations_assets_src, "#selfdrive/assets/assets.o"]) + [lrelease])
asset_obj = qt_env.Object("assets", assets) asset_obj = qt_env.Object("assets", assets)
# build soundd
qt_env.Program("soundd/_soundd", ["soundd/main.cc", "soundd/sound.cc"], LIBS=qt_libs)
if GetOption('extras'):
qt_env.Program("tests/playsound", "tests/playsound.cc", LIBS=base_libs)
qt_env.Program('tests/test_sound', ['tests/test_runner.cc', 'soundd/sound.cc', 'tests/test_sound.cc'], LIBS=qt_libs)
qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs)
# spinner and text window # spinner and text window

@ -114,3 +114,28 @@ void ElidedLabel::paintEvent(QPaintEvent *event) {
opt.initFrom(this); opt.initFrom(this);
style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole());
} }
// ParamControl
ParamControl::ParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent)
: ToggleControl(title, desc, icon, false, parent) {
key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, this, &ParamControl::toggleClicked);
}
void ParamControl::toggleClicked(bool state) {
auto do_confirm = [this]() {
QString content("<body><h2 style=\"text-align: center;\">" + title_label->text() + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + getDescription() + "</p></body>");
return ConfirmationDialog(content, tr("Enable"), tr("Cancel"), true, this).exec();
};
bool confirmed = store_confirm && params.getBool(key + "Confirmed");
if (!confirm || confirmed || !state || do_confirm()) {
if (store_confirm && state) params.putBool(key + "Confirmed", true);
params.putBool(key, state);
setIcon(state);
} else {
toggle.togglePosition();
}
}

@ -144,24 +144,7 @@ class ParamControl : public ToggleControl {
Q_OBJECT Q_OBJECT
public: public:
ParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { ParamControl(const QString &param, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr);
key = param.toStdString();
QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) {
QString content("<body><h2 style=\"text-align: center;\">" + title + "</h2><br>"
"<p style=\"text-align: center; margin: 0 128px; font-size: 50px;\">" + getDescription() + "</p></body>");
ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this);
bool confirmed = store_confirm && params.getBool(key + "Confirmed");
if (!confirm || confirmed || !state || dialog.exec()) {
if (store_confirm && state) params.putBool(key + "Confirmed", true);
params.putBool(key, state);
setIcon(state);
} else {
toggle.togglePosition();
}
});
}
void setConfirmation(bool _confirm, bool _store_confirm) { void setConfirmation(bool _confirm, bool _store_confirm) {
confirm = _confirm; confirm = _confirm;
store_confirm = _store_confirm; store_confirm = _store_confirm;
@ -184,6 +167,7 @@ public:
} }
private: private:
void toggleClicked(bool state);
void setIcon(bool state) { void setIcon(bool state) {
if (state && !active_icon_pixmap.isNull()) { if (state && !active_icon_pixmap.isNull()) {
icon_label->setPixmap(active_icon_pixmap); icon_label->setPixmap(active_icon_pixmap);

@ -0,0 +1,161 @@
import math
import numpy as np
import time
import wave
from typing import Dict, Optional, Tuple
from cereal import car, messaging
from openpilot.common.basedir import BASEDIR
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.system import micd
from openpilot.system.hardware import TICI
from openpilot.common.realtime import Ratekeeper
from openpilot.common.swaglog import cloudlog
SAMPLE_RATE = 48000
MAX_VOLUME = 1.0
MIN_VOLUME = 0.1
CONTROLS_TIMEOUT = 5 # 5 seconds
FILTER_DT = 1. / (micd.SAMPLE_RATE / micd.FFT_SAMPLES)
AMBIENT_DB = 30 # DB where MIN_VOLUME is applied
DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied
AudibleAlert = car.CarControl.HUDControl.AudibleAlert
sound_list: Dict[int, Tuple[str, Optional[int], float]] = {
# AudibleAlert, file name, play count (none for infinite)
AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME),
AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME),
AudibleAlert.refuse: ("refuse.wav", 1, MAX_VOLUME),
AudibleAlert.prompt: ("prompt.wav", 1, MAX_VOLUME),
AudibleAlert.promptRepeat: ("prompt.wav", None, MAX_VOLUME),
AudibleAlert.promptDistracted: ("prompt_distracted.wav", None, MAX_VOLUME),
AudibleAlert.warningSoft: ("warning_soft.wav", None, MAX_VOLUME),
AudibleAlert.warningImmediate: ("warning_immediate.wav", None, MAX_VOLUME),
}
def check_controls_timeout_alert(sm):
controls_missing = time.monotonic() - sm.rcv_time['controlsState']
if controls_missing > CONTROLS_TIMEOUT:
if sm['controlsState'].enabled and (controls_missing - CONTROLS_TIMEOUT) < 10:
return True
return False
class Soundd:
def __init__(self):
self.load_sounds()
self.current_alert = AudibleAlert.none
self.current_volume = MIN_VOLUME
self.current_sound_frame = 0
self.controls_timeout_alert = False
self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False)
def load_sounds(self):
self.loaded_sounds: Dict[int, np.ndarray] = {}
# Load all sounds
for sound in sound_list:
filename, play_count, volume = sound_list[sound]
wavefile = wave.open(BASEDIR + "/selfdrive/assets/sounds/" + filename, 'r')
assert wavefile.getnchannels() == 1
assert wavefile.getsampwidth() == 2
assert wavefile.getframerate() == SAMPLE_RATE
length = wavefile.getnframes()
self.loaded_sounds[sound] = np.frombuffer(wavefile.readframes(length), dtype=np.int16).astype(np.float32) / (2**16/2)
def get_sound_data(self, frames): # get "frames" worth of data from the current alert sound, looping when required
ret = np.zeros(frames, dtype=np.float32)
if self.current_alert != AudibleAlert.none:
num_loops = sound_list[self.current_alert][1]
sound_data = self.loaded_sounds[self.current_alert]
written_frames = 0
current_sound_frame = self.current_sound_frame % len(sound_data)
loops = self.current_sound_frame // len(sound_data)
while written_frames < frames and (num_loops is None or loops < num_loops):
available_frames = sound_data.shape[0] - current_sound_frame
frames_to_write = min(available_frames, frames - written_frames)
ret[written_frames:written_frames+frames_to_write] = sound_data[current_sound_frame:current_sound_frame+frames_to_write]
written_frames += frames_to_write
self.current_sound_frame += frames_to_write
return ret * self.current_volume
def callback(self, data_out: np.ndarray, frames: int, time, status) -> None:
if status:
cloudlog.warning(f"soundd stream over/underflow: {status}")
data_out[:frames, 0] = self.get_sound_data(frames)
def update_alert(self, new_alert):
current_alert_played_once = self.current_alert == AudibleAlert.none or self.current_sound_frame > len(self.loaded_sounds[self.current_alert])
if self.current_alert != new_alert and (new_alert != AudibleAlert.none or current_alert_played_once):
self.current_alert = new_alert
self.current_sound_frame = 0
def get_audible_alert(self, sm):
if sm.updated['controlsState']:
new_alert = sm['controlsState'].alertSound.raw
self.update_alert(new_alert)
elif check_controls_timeout_alert(sm):
self.update_alert(AudibleAlert.warningImmediate)
self.controls_timeout_alert = True
elif self.controls_timeout_alert:
self.update_alert(AudibleAlert.none)
self.controls_timeout_alert = False
def calculate_volume(self, weighted_db):
volume = ((weighted_db - AMBIENT_DB) / DB_SCALE) * (MAX_VOLUME - MIN_VOLUME) + MIN_VOLUME
return math.pow(10, (np.clip(volume, MIN_VOLUME, MAX_VOLUME) - 1))
def soundd_thread(self):
# sounddevice must be imported after forking processes
import sounddevice as sd
if TICI:
micd.wait_for_devices(sd) # wait for alsa to be initialized on device
with sd.OutputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream:
rk = Ratekeeper(20)
sm = messaging.SubMaster(['controlsState', 'microphone'])
cloudlog.info(f"soundd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}")
while True:
sm.update(0)
if sm.updated['microphone'] and self.current_alert == AudibleAlert.none: # only update volume filter when not playing alert
self.spl_filter_weighted.update(sm["microphone"].soundPressureWeightedDb)
self.current_volume = self.calculate_volume(float(self.spl_filter_weighted.x))
self.get_audible_alert(sm)
rk.keep_time()
assert stream.active
def main():
s = Soundd()
s.soundd_thread()
if __name__ == "__main__":
main()

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

Loading…
Cancel
Save