diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 8b5b165536..c1d4eae52f 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -32,7 +32,7 @@ jobs: ${{ env.RUN }} "scons -j$(nproc)" - name: Build docs 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 if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 77fddb7099..71d95cd74c 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -268,7 +268,7 @@ jobs: ${{ env.RUN }} "scons -j$(nproc)" # PYTHONWARNINGS triggers a SyntaxError in onnxruntime - name: Run model replay with ONNX - timeout-minutes: 3 + timeout-minutes: 4 run: | ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ diff --git a/.gitmodules b/.gitmodules index 4ba149cb2d..73f832b1d6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,6 +13,9 @@ [submodule "body"] path = body url = ../../commaai/body.git +[submodule "teleoprtc_repo"] + path = teleoprtc_repo + url = ../../commaai/teleoprtc [submodule "tinygrad"] path = tinygrad_repo url = https://github.com/geohot/tinygrad.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b6a46ad507..2109c18b25 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -26,7 +26,7 @@ repos: rev: v2.2.6 hooks: - 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: # 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 @@ -39,12 +39,12 @@ repos: language: system types: [python] 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 rev: v0.1.6 hooks: - 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 hooks: - id: cppcheck diff --git a/Jenkinsfile b/Jenkinsfile index 3dd9c45f14..bd894f53e0 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -14,7 +14,8 @@ export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_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" @@ -157,7 +158,7 @@ node { // tici tests 'onroad tests': { 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"], ["check dirty", "release/check-dirty.sh"], ["onroad tests", "pytest selfdrive/test/test_onroad.py -s"], @@ -233,9 +234,8 @@ node { 'car tests': { pcStage("car tests") { 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 \ - pytest -n auto --dist=loadscope selfdrive/car/tests/test_models.py" - sh label: "test_car_interfaces.py", script: "MAX_EXAMPLES=100 pytest -n auto --dist=load selfdrive/car/tests/test_car_interfaces.py" + sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=100 INTERNAL_SEG_CNT=250 FILEREADER_CACHE=1 \ + INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt pytest test_models.py test_car_interfaces.py" } }, diff --git a/RELEASES.md b/RELEASES.md index 4c12971c54..07f70cdaff 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -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) ======================== diff --git a/cereal b/cereal index 2cb2bfb015..a3a6e4969e 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 2cb2bfb0154874775e56715ecf81f0e6b00538e9 +Subproject commit a3a6e4969e58875f7cdfc9e6cc6b1af3ee2392b5 diff --git a/common/api/__init__.py b/common/api/__init__.py index 0eb8aa7627..79875023a2 100644 --- a/common/api/__init__.py +++ b/common/api/__init__.py @@ -2,7 +2,7 @@ import jwt import os import requests 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 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(): def __init__(self, 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() def get(self, *args, **kwargs): diff --git a/common/basedir.py b/common/basedir.py index b4486f9f08..c840b86f7f 100644 --- a/common/basedir.py +++ b/common/basedir.py @@ -1,11 +1,4 @@ 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__)), "../")) - -if PC: - PERSIST = os.path.join(str(Path.home()), ".comma", "persist") -else: - PERSIST = "/persist" +BASEDIR = os.path.abspath(os.path.join(os.path.dirname(os.path.realpath(__file__)), "../")) \ No newline at end of file diff --git a/common/file_helpers.py b/common/file_helpers.py index 227d614d72..a29eafdd9f 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -4,16 +4,6 @@ import tempfile 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): try: os.remove(path) diff --git a/common/params.cc b/common/params.cc index 54168165a1..d1d3513469 100644 --- a/common/params.cc +++ b/common/params.cc @@ -114,7 +114,7 @@ std::unordered_map keys = { {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, - {"ExperimentalLongitudinalEnabled", PERSISTENT}, + {"ExperimentalLongitudinalEnabled", PERSISTENT | DEVELOPMENT_ONLY}, {"ExperimentalMode", PERSISTENT}, {"ExperimentalModeConfirmed", PERSISTENT}, {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION}, diff --git a/common/params.h b/common/params.h index fbe0bba6b0..aa586a1581 100644 --- a/common/params.h +++ b/common/params.h @@ -10,6 +10,7 @@ enum ParamKeyType { CLEAR_ON_ONROAD_TRANSITION = 0x08, CLEAR_ON_OFFROAD_TRANSITION = 0x10, DONT_LOG = 0x20, + DEVELOPMENT_ONLY = 0x40, ALL = 0xFFFFFFFF }; diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index de52c490b3..db8f496d30 100644 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -11,6 +11,7 @@ cdef extern from "common/params.h": CLEAR_ON_MANAGER_START CLEAR_ON_ONROAD_TRANSITION CLEAR_ON_OFFROAD_TRANSITION + DEVELOPMENT_ONLY ALL cdef cppclass c_Params "Params": diff --git a/common/retry.py b/common/retry.py new file mode 100644 index 0000000000..9bd4ac9522 --- /dev/null +++ b/common/retry.py @@ -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() diff --git a/system/swaglog.py b/common/swaglog.py similarity index 100% rename from system/swaglog.py rename to common/swaglog.py diff --git a/common/xattr.py b/common/xattr.py deleted file mode 100644 index 26616fd638..0000000000 --- a/common/xattr.py +++ /dev/null @@ -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})") diff --git a/conftest.py b/conftest.py index 9aaf04d798..d1787c24ef 100644 --- a/conftest.py +++ b/conftest.py @@ -45,8 +45,9 @@ def pytest_collection_modifyitems(config, items): item.add_marker(skipper) if "xdist_group_class_property" in item.keywords: - class_property = item.get_closest_marker('xdist_group_class_property').args[0] - item.add_marker(pytest.mark.xdist_group(getattr(item.cls, class_property))) + class_property_name = item.get_closest_marker('xdist_group_class_property').args[0] + class_property_value = getattr(item.cls, class_property_name) + item.add_marker(pytest.mark.xdist_group(class_property_value)) @pytest.hookimpl(trylast=True) diff --git a/docs/CARS.md b/docs/CARS.md index 044db1f0ec..647b1b15c5 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -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. -# 268 Supported Cars +# 271 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |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|| |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Ford|Explorer 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Explorer 2020-23|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2022|LARIAT Luxury|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -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)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Custin 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -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[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai I connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai H connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Cruz 2022-23[6](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai N connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe 2021-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Santa Fe Plug-in Hybrid 2022-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai L connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -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[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2023|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,12](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 5619cc8a51..6e8808ec20 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -29,8 +29,6 @@ camerad ^^^^^^^ .. autodoxygenindex:: :project: system_camerad_cameras -.. autodoxygenindex:: - :project: system_camerad_imgproc locationd ^^^^^^^^^ @@ -43,12 +41,6 @@ ui .. autodoxygenindex:: :project: selfdrive_ui -soundd -"""""" -.. autodoxygenindex:: - :project: selfdrive_ui_soundd - - replay """""" .. autodoxygenindex:: diff --git a/launch_env.sh b/launch_env.sh index d07fbe33de..dd8f431c63 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="8.2" + export AGNOS_VERSION="9.1" fi if [ -z "$PASSIVE" ]; then diff --git a/opendbc b/opendbc index 2b96bcc456..5b0c73977f 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 2b96bcc45669cdd14f9c652b07ef32d6403630f6 +Subproject commit 5b0c73977f1428700d0344d52874a90a4c5168fb diff --git a/panda b/panda index 09465a753c..a5753a2077 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 09465a753c82fbf9d467cb0fa02201769f9b33e5 +Subproject commit a5753a2077288b066e441dc2ba2f96d8062e5e49 diff --git a/poetry.lock b/poetry.lock index b05dcae11e..1461d5457c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -112,66 +112,39 @@ ifaddr = ">=0.2.0" [[package]] name = "aiortc" -version = "1.5.0" +version = "1.6.0" description = "An implementation of WebRTC and ORTC" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "aiortc-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1d3f2d6cc22fae5ea57b0371895b7830e878b9e3705fd3742b3453cdfa0fd51"}, - {file = "aiortc-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2eaf758b5e0bb16f22a9aeb8ab88eb947345f47e2e46cfca18b2815d44726c4e"}, - {file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ee76f6b30d7f39442ba7ac25d58114f077ead1460c5632d0c9e18179d01ad419"}, - {file = "aiortc-1.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a766052d93474e9bf4186465298b7c8fb9af062ef7f83ba33f191baa79dac1e"}, - {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.5.0-cp310-cp310-win32.whl", hash = "sha256:27e879b73377d4b94bd86e4c3e8cd8913905fdca1de90a9a4efb0d9d3779dbf4"}, - {file = "aiortc-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:a720d0dd53553f6dfc28a53bee2ffce4f13283b4cbbc7db548000054cc63a4f9"}, - {file = "aiortc-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a5e8cbfce84badd9a8355819343570bbec1e4eef725996cad6aebe4cc3d03ae8"}, - {file = "aiortc-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7931512dbb2ff91fb78f5512ad9ca96546452d7bb627f61bd7393bf59ee48ad3"}, - {file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6abeb857a98014fc97265891ebf4fd989987d2ee091e0844e3c8fc543b6e2f0"}, - {file = "aiortc-1.5.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dead42dc3a31570fb6f5b94f9be9c78e28b1dc045f71489858116840f299862e"}, - {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.5.0-cp311-cp311-win32.whl", hash = "sha256:cbd5d35bd34b22b8f711c708d266889c973c0dcb38da14a2a9f757266987a181"}, - {file = "aiortc-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:6749145e3d527ac98c80837d72fd832b0c403eded3546aeb7cec6f25592b4d5e"}, - {file = "aiortc-1.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:50e8e8903cf55f6f2cda9b61c115fca8e444d48f299cdd071980a3b5cec594fa"}, - {file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15218a1b81f4fa1521f3b839eefdce638b34c46306e8eaf069cee7283fe8c838"}, - {file = "aiortc-1.5.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25bca7c7bbd3619296b5737a810dd0e2fc7f6264e767fca10e65a709a443bf39"}, - {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.5.0-cp37-cp37m-win32.whl", hash = "sha256:f86b68b182537022d4ada49a7723c7a56f39372d6fbc31a29f57315d335cdc29"}, - {file = "aiortc-1.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:e4bbc2f2b97651f7aa6f5e82c69a22590901962454fc02617c4a559a1b51c21a"}, - {file = "aiortc-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7243bce7c3b95e47e56ddf961fbf6015702ddbbf3579b0bbf18c6173b6a6357a"}, - {file = "aiortc-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:883db8926deaf01fdcd32fbd74fcf055db63e968324ceff41d5a46ec86dff90c"}, - {file = "aiortc-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd663f67344e6fe240c6372f620988db5285c9b1b8336306e9fec76ffb4e5493"}, - {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"}, + {file = "aiortc-1.6.0-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:4675e04d8441797fef6c8a669b3a67d750670d1b897f08886072a084d743e07d"}, + {file = "aiortc-1.6.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:836c0686fca67f142c52e5af8043206c2bb702ad0ddcdc94ef19caf1c22f8d54"}, + {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33f846abd753935881158751994a51f14e345762130688b19c26cab42c01266f"}, + {file = "aiortc-1.6.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb4bd45397945a5323bd077d43c702a3a991d75023f23649c1d18df5d80c221d"}, + {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.6.0-cp38-abi3-win32.whl", hash = "sha256:325f847397af2892aa051dc2880a75e9bd79f535cc05ec8f4538b5ed098b3c5d"}, + {file = "aiortc-1.6.0-cp38-abi3-win_amd64.whl", hash = "sha256:98b118d53ae874126b2e9ec6bb1397ea169b85550c4bd5453e279507ff7f0cf9"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:6677018833a5d648754c99c70e6c6f6d4f3942682cda07ed5afa73422f8a009a"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:090936c719225e50cd4d66f476e6c17293a8062cf7687a1baa5080f3c90ec8b1"}, + {file = "aiortc-1.6.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e60d5bc269d4d12f1f5f47e2c17aa3799f3b5c8b73fd6d8d246ddc11bb29776"}, + {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.6.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0a41f4c0b31e45548e7c7397ef1aecc4be49ab68afd8fa134c07581fe0b3a9c2"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:64a9939016edbc8f300de6189983c983753827813ac9acd9b5be8ce61cc32684"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:813e7985665c94a0e3387b66e39dba6c751e5e588aedbca06d7e52068c6e37fb"}, + {file = "aiortc-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:672d0b4ad35c4d8f014f44a142aa55529ec82cfe2809226e1275e35a71fd4422"}, + {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.6.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:cecfa5f462e73218cadc9acd8013cb3a0d9007a4515bceba6e7755d77bb80061"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:febca6773de18a6bb9e5569ae87c8be55ed184695f1f9fc99aa4744a7b0375f8"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5ba865be9708713397ec584ed1baeb2f15d2fa9c32594ce19a41ffa6e2517cb"}, + {file = "aiortc-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9759a3c00e46ba1c3499dbf5a8513ae37ba65b940a56b0e7fa5070478e9379f"}, + {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.6.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9f20479d0dd06116ac81d332850fab874d83b561a73fd7252d218f55c6bd5b79"}, + {file = "aiortc-1.6.0.tar.gz", hash = "sha256:08cffbcde3401b33731b1d4169b9ff860b0aaaca200b62e10ce5978238671ad7"}, ] [package.dependencies] 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" cryptography = ">=2.2" google-crc32c = ">=1.1" @@ -180,7 +153,7 @@ pylibsrtp = ">=0.5.6" pyopenssl = ">=23.1.0" [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]] name = "aiosignal" diff --git a/pyproject.toml b/pyproject.toml index 1426905d06..ffbd5d43b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ testpaths = [ "system/proclogd", "system/tests", "system/ubloxd", + "system/webrtc", "tools/lib/tests", "tools/replay", "tools/cabana" @@ -43,6 +44,8 @@ exclude = [ "rednose_repo/", "tinygrad/", "tinygrad_repo/", + "teleoprtc/", + "teleoprtc_repo/", "third_party/", ] @@ -186,6 +189,8 @@ exclude = [ "opendbc", "rednose_repo", "tinygrad_repo", + "teleoprtc", + "teleoprtc_repo", "third_party", ] flake8-implicit-str-concat.allow-multiline=false diff --git a/release/build_devel.sh b/release/build_devel.sh index ca04c56f1e..8b6816e423 100755 --- a/release/build_devel.sh +++ b/release/build_devel.sh @@ -22,6 +22,7 @@ pre-commit uninstall || true echo "[-] bringing master-ci and devel in sync T=$SECONDS" cd $TARGET_DIR + git fetch --depth 1 origin master-ci git fetch --depth 1 origin devel diff --git a/release/files_common b/release/files_common index cfc3830e0d..637f8bd7d7 100644 --- a/release/files_common +++ b/release/files_common @@ -21,24 +21,8 @@ openpilot/** common/.gitignore common/__init__.py -common/conversions.py -common/gpio.py -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/*.py +common/*.pyx common/kalman/.gitignore common/kalman/* @@ -76,7 +60,6 @@ selfdrive/statsd.py system/logmessaged.py system/micd.py -system/swaglog.py system/version.py selfdrive/athena/__init__.py @@ -281,6 +264,12 @@ system/sensord/sensors/*.cc system/sensord/sensors/*.h 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/power_monitoring.py selfdrive/thermald/fan_controller.py @@ -291,7 +280,6 @@ selfdrive/test/helpers.py selfdrive/test/setup_device_ci.sh selfdrive/test/test_onroad.py selfdrive/test/test_time_to_onroad.py -selfdrive/test/pytest-tici.ini selfdrive/ui/.gitignore selfdrive/ui/SConscript @@ -300,10 +288,7 @@ selfdrive/ui/*.h selfdrive/ui/ui selfdrive/ui/text selfdrive/ui/spinner -selfdrive/ui/soundd/*.cc -selfdrive/ui/soundd/*.h -selfdrive/ui/soundd/soundd -selfdrive/ui/soundd/.gitignore +selfdrive/ui/soundd.py selfdrive/ui/translations/*.ts selfdrive/ui/translations/languages.json selfdrive/ui/update_translations.py @@ -333,12 +318,11 @@ system/camerad/main.cc system/camerad/snapshot/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc -system/camerad/cameras/sensor2_i2c.h - -system/camerad/imgproc/conv.cl -system/camerad/imgproc/pool.cl -system/camerad/imgproc/utils.cc -system/camerad/imgproc/utils.h +system/camerad/sensors/ar0231.cc +system/camerad/sensors/ar0231_registers.h +system/camerad/sensors/ox03c10.cc +system/camerad/sensors/ox03c10_registers.h +system/camerad/sensors/sensor.h selfdrive/manager/__init__.py selfdrive/manager/build.py @@ -444,6 +428,8 @@ third_party/qt5/larch64/bin/** scripts/update_now.sh scripts/stop_updater.sh +teleoprtc/** + rednose_repo/site_scons/site_tools/rednose_filter.py rednose/.gitignore rednose/** diff --git a/scripts/retry.sh b/scripts/retry.sh new file mode 100755 index 0000000000..4861748e55 --- /dev/null +++ b/scripts/retry.sh @@ -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 diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav index 9f6f672e28..b1815a9586 100644 Binary files a/selfdrive/assets/sounds/warning_immediate.wav and b/selfdrive/assets/sounds/warning_immediate.wav differ diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index eb61c6dd58..bdca04c457 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -31,21 +31,16 @@ import cereal.messaging as messaging from cereal import log from cereal.services import SERVICE_LIST from openpilot.common.api import Api -from openpilot.common.basedir import PERSIST from openpilot.common.file_helpers import CallbackReader from openpilot.common.params import Params from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import HARDWARE, PC, AGNOS from openpilot.system.loggerd.xattr_cache import getxattr, setxattr -from openpilot.selfdrive.statsd import STATS_DIR -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version from openpilot.system.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') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) LOCAL_PORT_WHITELIST = {8022} @@ -502,10 +497,10 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local @dispatcher.add_method 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 - with open(PERSIST + '/comma/id_rsa.pub') as f: + with open(Paths.persist_root() + '/comma/id_rsa.pub') as f: return f.read() @@ -641,6 +636,7 @@ def log_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(): last_scan = 0. curr_scan = time.monotonic() @@ -761,7 +757,7 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: if onroad != onroad_prev: 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_KEEPINTVL, 7 if onroad else 10) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 2 if onroad else 3) diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 877d8aca03..2a4a12e559 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -5,7 +5,7 @@ from multiprocessing import Process from openpilot.common.params import Params 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 ATHENA_MGR_PID_PARAM = "AthenadPid" diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 0ab69371c2..c9a4b949ac 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -9,10 +9,10 @@ from datetime import datetime, timedelta from openpilot.common.api import api_get from openpilot.common.params import Params from openpilot.common.spinner import Spinner -from openpilot.common.basedir import PERSIST from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert 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" @@ -32,7 +32,7 @@ def register(show_spinner=False) -> Optional[str]: dongle_id: Optional[str] = params.get("DongleId", encoding='utf8') 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(): dongle_id = UNREGISTERED_DONGLE_ID cloudlog.warning(f"missing public key: {pubkey}") @@ -42,7 +42,7 @@ def register(show_spinner=False) -> Optional[str]: spinner.update("registering device") # 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() private_key = f2.read() diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 8829ebfbf1..beffa0d232 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -46,8 +46,6 @@ class TestAthenadMethods(unittest.TestCase): else: os.unlink(p) - dispatcher["listUploadQueue"]() # ensure queue is empty at start - # *** test helpers *** @staticmethod diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/selfdrive/athena/tests/test_athenad_ping.py index 3ec7cb115c..5231b0475f 100755 --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/selfdrive/athena/tests/test_athenad_ping.py @@ -3,8 +3,8 @@ import subprocess import threading import time import unittest -from typing import Callable, cast, Optional -from unittest.mock import MagicMock +from typing import cast, Optional +from unittest import mock from openpilot.common.params import Params from openpilot.common.timeout import Timeout @@ -27,8 +27,6 @@ class TestAthenadPing(unittest.TestCase): athenad: threading.Thread exit_event: threading.Event - _create_connection: Callable - def _get_ping_time(self) -> Optional[str]: return cast(Optional[str], self.params.get("LastAthenaPingTime", encoding="utf-8")) @@ -38,38 +36,32 @@ class TestAthenadPing(unittest.TestCase): def _received_ping(self) -> bool: 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 def tearDownClass(cls) -> None: wifi_radio(True) - athenad.create_connection = cls._create_connection def setUp(self) -> None: + self.params = Params() + self.dongle_id = self.params.get("DongleId", encoding="utf-8") + wifi_radio(True) self._clear_ping_time() self.exit_event = threading.Event() self.athenad = threading.Thread(target=athenad.main, args=(self.exit_event,)) - athenad.create_connection.reset_mock() - def tearDown(self) -> None: if self.athenad.is_alive(): self.exit_event.set() 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() time.sleep(1) - athenad.create_connection.assert_called_once() - athenad.create_connection.reset_mock() + mock_create_connection.assert_called_once() + mock_create_connection.reset_mock() # check normal behaviour with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"): @@ -77,7 +69,7 @@ class TestAthenadPing(unittest.TestCase): time.sleep(0.1) print("ping received") - athenad.create_connection.assert_not_called() + mock_create_connection.assert_not_called() # websocket should attempt reconnect after short time with self.subTest("LTE: attempt reconnect"): @@ -85,7 +77,7 @@ class TestAthenadPing(unittest.TestCase): print("waiting for reconnect attempt") start_time = time.monotonic() with Timeout(reconnect_time, "no reconnect attempt"): - while not athenad.create_connection.called: + while not mock_create_connection.called: time.sleep(0.1) print(f"reconnect attempt after {time.monotonic() - start_time:.2f}s") diff --git a/selfdrive/athena/tests/test_registration.py b/selfdrive/athena/tests/test_registration.py index 195fca2df9..e7ad63a370 100755 --- a/selfdrive/athena/tests/test_registration.py +++ b/selfdrive/athena/tests/test_registration.py @@ -1,7 +1,5 @@ #!/usr/bin/env python3 import json -import os -import tempfile import unittest from Crypto.PublicKey import RSA from pathlib import Path @@ -10,6 +8,7 @@ from unittest import mock from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.selfdrive.athena.tests.helpers import MockResponse +from openpilot.system.hardware.hw import Paths class TestRegistration(unittest.TestCase): @@ -19,16 +18,11 @@ class TestRegistration(unittest.TestCase): self.params = Params() self.params.clear_all() - self.persist = tempfile.TemporaryDirectory() - os.mkdir(os.path.join(self.persist.name, "comma")) - 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() + persist_dir = Path(Paths.persist_root()) / "comma" + persist_dir.mkdir(parents=True, exist_ok=True) - def tearDown(self): - self.persist_patcher.stop() - self.persist.cleanup() + self.priv_key = persist_dir / "id_rsa" + self.pub_key = persist_dir / "id_rsa.pub" def _generate_keys(self): self.pub_key.touch() diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 14d272965d..672678778b 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -12,7 +12,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.selfdrive.boardd.set_time import set_time 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: diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 5c1234258e..fa68313e86 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -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.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.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog import cereal.messaging as messaging from openpilot.selfdrive.car import gen_empty_fingerprint diff --git a/selfdrive/car/disable_ecu.py b/selfdrive/car/disable_ecu.py index c11075342c..1f731f0344 100755 --- a/selfdrive/car/disable_ecu.py +++ b/selfdrive/car/disable_ecu.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 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_RESPONSE = b'\x50\x03' diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index cff1df79a5..13f7926def 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -8,7 +8,7 @@ from panda.python.uds import SERVICE_TYPE from openpilot.selfdrive.car import make_can_msg from openpilot.selfdrive.car.fw_query_definitions import EcuAddrBusType 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): diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 133038809f..301847b7ea 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -88,7 +88,7 @@ CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"), ], CAR.EXPLORER_MK6: [ - FordCarInfo("Ford Explorer 2020-22"), + FordCarInfo("Ford Explorer 2020-23"), FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), ], 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'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'M1PA-14C204-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.ESCAPE_MK4: { @@ -184,6 +185,7 @@ FW_VERSIONS = { 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-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): [ 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-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-BD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ 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'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'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: { diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 45c4967cb8..f50c604316 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -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.fingerprints import FW_VERSIONS 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 ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index 0a0f66eabd..7c6fa7cb4f 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -159,7 +159,10 @@ class HyundaiCarInfo(CarInfo): 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_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: [ # 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])), @@ -191,7 +194,8 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { # 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_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_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: { (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.00 99211-G8000 180903', ], (Ecu.eps, 0x7d4, None): [ 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): [ 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): [ 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): [ 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: { @@ -1912,7 +1921,8 @@ FW_VERSIONS = { ], (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.10 99210-P2000 210406', + ], }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index adbc598ea6..696935fd35 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -3,7 +3,7 @@ from collections import defaultdict from functools import partial 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 panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 882d46b7c3..915785d329 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -126,7 +126,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"), 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.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), } @@ -141,12 +141,30 @@ FW_QUERY_CONFIG = FwQueryConfig( 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], ), # 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 Request( [SUBARU_VERSION_REQUEST], [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', ], (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): [ b'\xd5"a0\x07', diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 6777aeb738..5721896787 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -227,7 +227,7 @@ class TestFwFingerprintTiming(unittest.TestCase): @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.07 + total_ref_time = 6.41 brand_ref_times = { 1: { 'body': 0.11, @@ -237,7 +237,7 @@ class TestFwFingerprintTiming(unittest.TestCase): 'hyundai': 0.72, 'mazda': 0.2, 'nissan': 0.4, - 'subaru': 0.2, + 'subaru': 0.52, 'tesla': 0.2, 'toyota': 1.6, 'volkswagen': 0.2, diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 4e5954635a..1eadf6cbc8 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -233,7 +233,7 @@ class TestCarModelBase(unittest.TestCase): error_cnt += car.RadarData.Error.canError in rr.errors self.assertEqual(error_cnt, 0) - def test_panda_safety_rx_valid(self): + def test_panda_safety_rx_checks(self): raise unittest.SkipTest if self.CP.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}") + # 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): raise unittest.SkipTest """Asserts we can tx common messages""" @@ -499,7 +504,7 @@ class TestCarModelBase(unittest.TestCase): @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): pass diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 2d2dbc3f0b..7bd83025f1 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -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 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] 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 V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] KIA EV6 2022: [3.2, 2.093457, 0.05] diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 2a02a57d86..badfd33e0b 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -21,8 +21,8 @@ MAX_USER_TORQUE = 500 # LTA limits # EPS ignores commands above this angle and causes PCS to fault -MAX_STEER_ANGLE = 94.9461 # deg -MAX_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes +MAX_LTA_ANGLE = 94.9461 # deg +MAX_LTA_DRIVER_TORQUE_ALLOWANCE = 150 # slightly above steering pressed allows some resistance when changing lanes class CarController: @@ -71,25 +71,32 @@ class CarController: apply_angle = actuators.steeringAngleDeg + CS.out.steeringAngleOffsetDeg # 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: 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 - # 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 # on consecutive messages 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: 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 - abs(CS.out.steeringTorque) < MAX_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)) + abs(CS.out.steeringTorque) < MAX_LTA_DRIVER_TORQUE_ALLOWANCE) + + # 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 *** if self.CP.enableGasInterceptor and CC.longActive: diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 9cf86d69ab..ae4cb1f2c5 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -28,12 +28,11 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE if candidate in ANGLE_CONTROL_CAR: - ret.dashcamOnly = True ret.steerControlType = SteerControlType.angle ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA # LTA control can be more delayed and winds up more often - ret.steerActuatorDelay = 0.25 + ret.steerActuatorDelay = 0.18 ret.steerLimitTimer = 0.8 else: CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py index 2a82ca5ec0..eae59a861f 100755 --- a/selfdrive/car/toyota/tests/test_toyota.py +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -17,6 +17,10 @@ class TestToyotaInterfaces(unittest.TestCase): self.assertTrue(len(ANGLE_CONTROL_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): # We make some assumptions about TSS2 platforms, # like looking up certain signals only in this DBC diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index ed0237c1be..e14e3e53a0 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -1,3 +1,8 @@ +from cereal import car + +SteerControlType = car.CarParams.SteerControlType + + def create_steer_command(packer, steer, steer_req): """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) -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.""" values = { "COUNTER": frame + 128, - "SETME_X1": 1, - "SETME_X3": 3, + "SETME_X1": 1, # suspected LTA feature availability + # 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, - "SETME_X64": setme_x64, + "TORQUE_WIND_DOWN": torque_wind_down, "ANGLE": 0, "STEER_ANGLE_CMD": steer_angle, "STEER_REQUEST": steer_req, diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 4a1011982c..10702269d6 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -27,7 +27,8 @@ class CarControllerParams: # 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 # 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_DOWN = AngleRateLimit(speed_bp=[5, 25], angle_v=[0.36, 0.26]) diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index 28edf157ae..e2709cc842 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -5,7 +5,7 @@ import cereal.messaging as messaging 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.fw_query_definitions import StdQueries -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog VIN_UNKNOWN = "0" * 17 VIN_RE = "[A-HJ-NPR-Z0-9]{17}" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 19ce407932..41866efc3d 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -13,8 +13,8 @@ import cereal.messaging as messaging from cereal.visionipc import VisionIpcClient, VisionStreamType from openpilot.common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE -from openpilot.system.swaglog import cloudlog -from openpilot.system.version import is_release_branch, get_short_branch +from openpilot.common.swaglog import cloudlog +from openpilot.system.version import get_short_branch 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.controls.lib.lateral_planner import CAMERA_OFFSET @@ -64,15 +64,13 @@ class Controls: # Setup sockets self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState', - 'carControl', 'carEvents', 'carParams']) + 'carControl', 'onroadEvents', 'carParams']) self.sensor_packets = ["accelerometer", "gyroscope"] 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.can_sock = messaging.sub_sock('can', timeout=20) self.params = Params() ignore = self.sensor_packets + ['testJoystick'] @@ -82,7 +80,7 @@ class Controls: 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', '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: # wait for one pandaState and one CAN packet @@ -90,12 +88,13 @@ class Controls: get_one_can(self.can_sock) 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) else: 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 self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") @@ -115,8 +114,8 @@ class Controls: car_recognized = self.CP.carName != 'mock' 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 - if self.read_only: + self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly + if self.CP.passive: safety_config = car.CarParams.SafetyConfig.new_message() safety_config.safetyModel = car.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] @@ -133,7 +132,7 @@ class Controls: put_nonblocking("CarParamsPersistent", cp_bytes) # cleanup old params - if not self.CP.experimentalLongitudinalAvailable or is_release_branch(): + if not self.CP.experimentalLongitudinalAvailable: self.params.remove("ExperimentalLongitudinalEnabled") if not self.CP.openpilotLongitudinalControl: self.params.remove("ExperimentalMode") @@ -179,8 +178,6 @@ class Controls: self.v_cruise_helper = VCruiseHelper(self.CP) self.recalibrating_seen = False - # TODO: no longer necessary, aside from process replay - self.sm['liveParameters'].valid = True self.can_log_mono_time = 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) else: set_offroad_alert("Offroad_NoFirmware", True) - elif self.read_only: + elif self.CP.passive: self.events.add(EventName.dashcamMode, static=True) elif self.joystick_mode: self.events.add(EventName.joystickDebug, static=True) @@ -214,7 +211,7 @@ class Controls: self.state = State.enabled def update_events(self, CS): - """Compute carEvents from carState""" + """Compute onroadEvents from carState""" self.events.clear() @@ -229,7 +226,7 @@ class Controls: return # no more events while in dashcam mode - if self.read_only: + if self.CP.passive: return # Block resume if cruise never previously enabled @@ -374,16 +371,17 @@ class Controls: else: self.logged_comm_issue = None - if not self.sm['lateralPlan'].mpcSolutionValid: - self.events.add(EventName.plannerError) - if not self.sm['liveLocationKalman'].posenetOK: - self.events.add(EventName.posenetInvalid) - if not self.sm['liveLocationKalman'].deviceStable: - self.events.add(EventName.deviceFalling) - if not self.sm['liveLocationKalman'].inputsOK: - self.events.add(EventName.locationdTemporaryError) - if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): - self.events.add(EventName.paramsdTemporaryError) + if not (self.CP.notCar and self.joystick_enabled): + if not self.sm['lateralPlan'].mpcSolutionValid: + self.events.add(EventName.plannerError) + if not self.sm['liveLocationKalman'].posenetOK: + self.events.add(EventName.posenetInvalid) + if not self.sm['liveLocationKalman'].deviceStable: + self.events.add(EventName.deviceFalling) + if not self.sm['liveLocationKalman'].inputsOK: + self.events.add(EventName.locationdTemporaryError) + if not self.sm['liveParameters'].valid and not TESTING_CLOSET and (not SIMULATION or REPLAY): + self.events.add(EventName.paramsdTemporaryError) # conservative HW alert. if the data or frequency are off, locationd will throw an error if any((self.sm.frame - self.sm.rcv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): @@ -444,7 +442,7 @@ class Controls: if VisionStreamType.VISION_STREAM_WIDE_ROAD not in available_streams: 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.initialized = True @@ -618,7 +616,7 @@ class Controls: lat_plan.curvatures, lat_plan.curvatureRates) 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']) actuators.curvature = self.desired_curvature else: @@ -637,7 +635,7 @@ class Controls: if CC.latActive: steer = clip(joystick_axes[1], -1, 1) # 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.steeringAngleDeg = CS.steeringAngleDeg @@ -749,7 +747,7 @@ class Controls: if current_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 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) @@ -825,16 +823,18 @@ class Controls: cs_send.carState.events = car_events 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): - ce_send = messaging.new_message('carEvents', len(self.events)) - ce_send.carEvents = car_events - self.pm.send('carEvents', ce_send) + ce_send = messaging.new_message('onroadEvents', len(self.events)) + ce_send.valid = True + ce_send.onroadEvents = car_events + self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() # carParams - logged every 50 seconds (> 1 per segment) if (self.sm.frame % int(50. / DT_CTRL) == 0): cp_send = messaging.new_message('carParams') + cp_send.valid = True cp_send.carParams = self.CP self.pm.send('carParams', cp_send) @@ -862,7 +862,7 @@ class Controls: self.update_events(CS) cloudlog.timestamp("Events updated") - if not self.read_only and self.initialized: + if not self.CP.passive and self.initialized: # Update control state self.state_transition(CS) self.prof.checkpoint("State transition") diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 30e1918442..723af7f806 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -17,7 +17,7 @@ class LatControl(ABC): self.steer_max = 1.0 @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 def reset(self): diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index b13d41e51c..d363295f0c 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -11,7 +11,7 @@ class LatControlAngle(LatControl): super().__init__(CP, CI) 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() if not active: diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index c41130af95..c673159ebb 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -17,7 +17,7 @@ class LatControlPID(LatControl): super().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.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 2c77630632..f46ab9eb6c 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -36,7 +36,7 @@ class LatControlTorque(LatControl): self.torque_params.latAccelOffset = latAccelOffset 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() if not active: diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index a23ba1eaf6..39cfda4e8a 100755 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -4,7 +4,7 @@ import time import numpy as np from cereal import log 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 from openpilot.selfdrive.modeld.constants import index_function from openpilot.selfdrive.car.interfaces import ACCEL_MIN diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index b1eff5302f..13127b4634 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -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 T_IDXS as T_IDXS_MPC 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 A_CRUISE_MIN = -1.2 diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 9580e604ff..5faad914cf 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -30,13 +30,11 @@ class TestLatControl(unittest.TestCase): CS.vEgo = 30 CS.steeringPressed = False - last_actuators = car.CarControl.Actuators.new_message() - params = log.LiveParametersData.new_message() llk = gen_llk() 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) diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 15dbd4c5e2..46d10ef2f5 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -4,7 +4,7 @@ import numpy as np from cereal import car from openpilot.common.params import Params 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.controls.lib.longitudinal_planner import LongitudinalPlanner from openpilot.selfdrive.controls.lib.lateral_planner import LateralPlanner diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index ec42ae66f2..f75b4b2059 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -9,7 +9,7 @@ from cereal import messaging, log, car from openpilot.common.numpy_fast import interp from openpilot.common.params import Params 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 @@ -270,6 +270,7 @@ class RadarD: # publish tracks for UI debugging (keep last) 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())): tracks_msg.liveTracks[index] = { "trackId": tid, diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index a148996d94..a2c3576b6f 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -32,8 +32,8 @@ if __name__ == "__main__": end_time = max(end_time, msg.logMonoTime) start_time = min(start_time, msg.logMonoTime) - if msg.which() == 'carEvents': - for e in msg.carEvents: + if msg.which() == 'onroadEvents': + for e in msg.onroadEvents: cnt_events[e.name] += 1 elif msg.which() == 'controlsState': if len(alerts) == 0 or alerts[-1][1] != msg.controlsState.alertType: diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 566373d7b8..961efabf8d 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -18,7 +18,7 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.params import Params, put_nonblocking from openpilot.common.realtime import set_realtime_priority 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 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) 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: self.v_ego = v_ego @@ -227,12 +227,13 @@ class Calibrator: 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() msg = messaging.new_message('liveCalibration') - liveCalibration = msg.liveCalibration + msg.valid = valid + liveCalibration = msg.liveCalibration liveCalibration.validBlocks = self.valid_blocks liveCalibration.calStatus = self.cal_status liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) @@ -250,19 +251,16 @@ class Calibrator: return msg - def send_data(self, pm: messaging.PubMaster) -> None: - pm.send('liveCalibration', self.get_msg()) + def send_data(self, pm: messaging.PubMaster, valid: bool) -> None: + 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() 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) @@ -286,11 +284,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m # 4Hz driven by cameraOdometry if sm.frame % 5 == 0: - calibrator.send_data(pm) - - -def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None) -> NoReturn: - calibrationd_thread(sm, pm) + calibrator.send_data(pm, sm.all_checks()) if __name__ == "__main__": diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index bde21f7879..d57f55d588 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -5,7 +5,7 @@ from typing import List, Optional, Tuple, Any from cereal import log from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog class NPQueue: diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index b87c83cac2..9230cb48f0 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -7,7 +7,7 @@ import numpy as np from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY 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 diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 0e779c9e3e..4a598e8d4b 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -12,7 +12,7 @@ from openpilot.common.realtime import config_realtime_process, DT_MDL from openpilot.common.numpy_fast import clip from openpilot.selfdrive.locationd.models.car_kf import CarKalman, ObservationKind, States 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 diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 707fdd743f..78de9216dc 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -60,6 +60,7 @@ class TestLocationdProc(unittest.TestCase): msg.cameraOdometry.trans = [0.0, 0.0, 0.0] msg.cameraOdometry.transStd = [0.0, 0.0, 0.0] msg.logMonoTime = t + msg.valid = True return msg def test_params_gps(self): diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index ff1fac2c22..d0a03f9b80 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -10,7 +10,7 @@ from cereal import car, log from openpilot.common.params import Params from openpilot.common.realtime import config_realtime_process, DT_MDL 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.locationd.helpers import PointBuckets, ParameterEstimator, cache_points_onexit diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 247f3c8fb8..836723e5a8 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -9,7 +9,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.spinner import Spinner from openpilot.common.text_window import TextWindow 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 MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index a739437de7..37947a9a03 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -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_config import managed_processes 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, \ get_normalized_origin, terms_version, training_version, \ 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_ONROAD_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]]] = [ ("CompletedTrainingVersion", "0"), @@ -169,7 +171,7 @@ def manager_thread() -> None: cloudlog.debug(running) # 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()] pm.send('managerState', msg) diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 82cc8d74a3..523e1fd209 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -15,7 +15,7 @@ import cereal.messaging as messaging import openpilot.selfdrive.sentry as sentry from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog WATCHDOG_FN = "/dev/shm/wd_" ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 6f974b0687..36d69b03bb 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -60,7 +60,7 @@ procs = [ PythonProcess("navmodeld", "selfdrive.modeld.navmodeld", only_onroad), 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("soundd", "selfdrive/ui/soundd", ["./soundd"], only_onroad), + PythonProcess("soundd", "selfdrive.ui.soundd", only_onroad), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"], only_onroad), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], always_run, enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd", only_onroad), @@ -84,6 +84,7 @@ procs = [ # debug procs NativeProcess("bridge", "cereal/messaging", ["./bridge"], notcar), + PythonProcess("webrtcd", "system.webrtc.webrtcd", notcar), PythonProcess("webjoystick", "tools.bodyteleop.web", notcar), ] diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 53c0af0ff3..1e25964702 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -11,7 +11,7 @@ from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster 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.realtime import set_realtime_priority 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): 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.frameId = frame_id ds.modelExecutionTime = execution_time diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 2b211b22e6..9cc238530a 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -9,7 +9,7 @@ from typing import Dict, Optional from setproctitle import setproctitle from cereal.messaging import PubMaster, SubMaster 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.realtime import DT_MDL from openpilot.common.numpy_fast import interp diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index 90b9762800..ed0b597dfe 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -10,7 +10,7 @@ from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster 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.realtime import set_realtime_priority from openpilot.selfdrive.modeld.constants import ModelConstants diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index ff2ee71e74..7455459967 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -3,7 +3,6 @@ import gc import cereal.messaging as messaging from cereal import car -from cereal import log from openpilot.common.params import Params, put_bool_nonblocking from openpilot.common.realtime import set_realtime_priority from openpilot.selfdrive.controls.lib.events import Events @@ -19,18 +18,12 @@ def dmonitoringd_thread(): 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 driver_engaged = False # 10Hz <- dmonitoringmodeld while True: sm.update() - if not sm.updated['driverStateV2']: continue @@ -48,7 +41,9 @@ def dmonitoringd_thread(): # Get data from dmonitoringmodeld events = Events() - driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) + + if sm.all_checks(): + driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) # Block engaging after max number of distrations if driver_status.terminal_alert_cnt >= driver_status.settings._MAX_TERMINAL_ALERTS or \ @@ -59,7 +54,7 @@ def dmonitoringd_thread(): driver_status.update_events(events, driver_engaged, sm['controlsState'].enabled, sm['carState'].standstill) # build driverMonitoringState packet - dat = messaging.new_message('driverMonitoringState') + dat = messaging.new_message('driverMonitoringState', valid=sm.all_checks()) dat.driverMonitoringState = { "events": events.to_msg(), "faceDetected": driver_status.face_detected, diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index da2b8c06b9..fcaf46aff4 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.navd.helpers import (Coordinate, coordinate_from_param, distance_along_geometry, maxspeed_to_ms, minimum_distance, parse_banner_instructions) -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 @@ -196,7 +196,7 @@ class RouteEngine: self.send_route() def send_instruction(self): - msg = messaging.new_message('navInstruction') + msg = messaging.new_message('navInstruction', valid=True) if self.step_idx is None: msg.valid = False @@ -302,7 +302,7 @@ class RouteEngine: for path in self.route_geometry: coords += [c.as_dict() for c in path] - msg = messaging.new_message('navRoute') + msg = messaging.new_message('navRoute', valid=True) msg.navRoute.coordinates = coords self.pm.send('navRoute', msg) diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index b8bc8eff12..f4bb1fbce8 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -6,7 +6,7 @@ from sentry_sdk.integrations.threading import ThreadingIntegration from openpilot.common.params import Params from openpilot.selfdrive.athena.registration import is_registered_device 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, \ is_comma_remote, is_dirty, is_tested_branch diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index 8acf406515..94572b82c7 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -9,11 +9,12 @@ from typing import NoReturn, Union, List, Dict from openpilot.common.params import Params 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.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.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: @@ -80,6 +81,8 @@ def main() -> NoReturn: sock = ctx.socket(zmq.PULL) sock.bind(STATS_SOCKET) + STATS_DIR = Paths.stats_root() + # initialize stats directory Path(STATS_DIR).mkdir(parents=True, exist_ok=True) diff --git a/selfdrive/test/docker_build.sh b/selfdrive/test/docker_build.sh index e0ba54f058..bd8c34c472 100755 --- a/selfdrive/test/docker_build.sh +++ b/selfdrive/test/docker_build.sh @@ -17,7 +17,7 @@ fi 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 docker push $REMOTE_TAG diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index a3b307ccba..713b7801f8 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,10 +1,8 @@ #!/usr/bin/env python3 import itertools -import os -from parameterized import parameterized_class 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.test.longitudinal_maneuvers.maneuver import Maneuver @@ -150,17 +148,6 @@ class LongitudinalControl(unittest.TestCase): e2e: 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): 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): diff --git a/selfdrive/test/process_replay/README.md b/selfdrive/test/process_replay/README.md index bec3eb9016..008a901010 100644 --- a/selfdrive/test/process_replay/README.md +++ b/selfdrive/test/process_replay/README.md @@ -31,7 +31,7 @@ optional arguments: --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) --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 --upload-only Skips testing processes and uploads logs from previous test run ``` diff --git a/selfdrive/test/process_replay/conftest.py b/selfdrive/test/process_replay/conftest.py index f3794d26ac..3f9744ed61 100644 --- a/selfdrive/test/process_replay/conftest.py +++ b/selfdrive/test/process_replay/conftest.py @@ -16,7 +16,7 @@ def pytest_addoption(parser: pytest.Parser): parser.addoption("--ignore-fields", type=str, nargs="*", default=[], help="Extra fields or msgs to ignore (e.g. carState.events)") parser.addoption("--ignore-msgs", type=str, nargs="*", default=[], - help="Msgs to ignore (e.g. carEvents)") + help="Msgs to ignore (e.g. onroadEvents)") parser.addoption("--update-refs", action="store_true", help="Updates reference logs using current commit") parser.addoption("--upload-only", action="store_true", diff --git a/selfdrive/test/process_replay/migration.py b/selfdrive/test/process_replay/migration.py index ad5192dbba..831bea7c6f 100644 --- a/selfdrive/test/process_replay/migration.py +++ b/selfdrive/test/process_replay/migration.py @@ -85,6 +85,7 @@ def migrate_peripheralState(lr): continue new_msg = messaging.new_message("peripheralState") + new_msg.valid = msg.valid new_msg.logMonoTime = msg.logMonoTime all_msg.append(new_msg.as_reader()) @@ -149,6 +150,7 @@ def migrate_carParams(lr, old_logtime=False): for msg in lr: if msg.which() == 'carParams': CP = messaging.new_message('carParams') + CP.valid = True CP.carParams = msg.carParams.as_builder() for car_fw in CP.carParams.carFw: car_fw.brand = CP.carParams.carName diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index a26e964550..c3c3670a74 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -442,8 +442,8 @@ def controlsd_config_callback(params, cfg, lr): controlsState = msg.controlsState if initialized: break - elif msg.which() == "carEvents": - initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.carEvents] + elif msg.which() == "onroadEvents": + initialized = car.CarEvent.EventName.controlsInitializing not in [e.name for e in msg.onroadEvents] assert controlsState is not None and initialized, "controlsState never initialized" params.put("ReplayControlsState", controlsState.as_builder().to_bytes()) @@ -465,8 +465,8 @@ CONFIGS = [ "modelV2", "driverCameraState", "roadCameraState", "wideRoadCameraState", "managerState", "testJoystick", "liveTorqueParameters", "accelerometer", "gyroscope" ], - subs=["controlsState", "carState", "carControl", "sendcan", "carEvents", "carParams"], - ignore=["logMonoTime", "valid", "controlsState.startMonoTime", "controlsState.cumLagMs"], + subs=["controlsState", "carState", "carControl", "sendcan", "onroadEvents", "carParams"], + ignore=["logMonoTime", "controlsState.startMonoTime", "controlsState.cumLagMs"], config_callback=controlsd_config_callback, init_callback=controlsd_fingerprint_callback, should_recv_callback=controlsd_rcv_callback, @@ -478,7 +478,7 @@ CONFIGS = [ proc_name="radard", pubs=["can", "carState", "modelV2"], subs=["radarState", "liveTracks"], - ignore=["logMonoTime", "valid", "radarState.cumLagMs"], + ignore=["logMonoTime", "radarState.cumLagMs"], init_callback=get_car_params_callback, should_recv_callback=MessageBasedRcvCallback("can"), main_pub="can", @@ -487,7 +487,7 @@ CONFIGS = [ proc_name="plannerd", pubs=["modelV2", "carControl", "carState", "controlsState", "radarState"], 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, should_recv_callback=FrequencyBasedRcvCallback("modelV2"), tolerance=NUMPY_TOLERANCE, @@ -496,14 +496,14 @@ CONFIGS = [ proc_name="calibrationd", pubs=["carState", "cameraOdometry", "carParams"], subs=["liveCalibration"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], should_recv_callback=calibration_rcv_callback, ), ProcessConfig( proc_name="dmonitoringd", pubs=["driverStateV2", "liveCalibration", "carState", "modelV2", "controlsState"], subs=["driverMonitoringState"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], should_recv_callback=FrequencyBasedRcvCallback("driverStateV2"), tolerance=NUMPY_TOLERANCE, ), @@ -514,7 +514,7 @@ CONFIGS = [ "liveCalibration", "carState", "carParams", "gpsLocation" ], subs=["liveLocationKalman"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], config_callback=locationd_config_pubsub_callback, tolerance=NUMPY_TOLERANCE, ), @@ -522,7 +522,7 @@ CONFIGS = [ proc_name="paramsd", pubs=["liveLocationKalman", "carState"], subs=["liveParameters"], - ignore=["logMonoTime", "valid"], + ignore=["logMonoTime"], init_callback=get_car_params_callback, should_recv_callback=FrequencyBasedRcvCallback("liveLocationKalman"), tolerance=NUMPY_TOLERANCE, diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 40ac91ee70..030cbc4e07 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -dbea36698ba48429b201b138846165eb4c329b92 \ No newline at end of file +921222d49db204071f0a7006fc895690e1045b5d \ No newline at end of file diff --git a/selfdrive/test/pytest-tici.ini b/selfdrive/test/pytest-tici.ini deleted file mode 100644 index a553018309..0000000000 --- a/selfdrive/test/pytest-tici.ini +++ /dev/null @@ -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 diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index c34e097f73..a180fe0de6 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -29,7 +29,6 @@ sudo abctl --set_success # patch sshd config 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 systemctl daemon-reload sudo systemctl restart ssh diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 17026dc69f..b4121529d1 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -34,8 +34,8 @@ PROCS = { "./encoderd": 17.0, "./camerad": 14.5, "./locationd": 11.0, - "./mapsd": 1.5, - "selfdrive.controls.plannerd": 16.5, + "./mapsd": (1.0, 10.0), + "selfdrive.controls.plannerd": 11.0, "./_ui": 18.0, "selfdrive.locationd.paramsd": 9.0, "./sensord": 7.0, @@ -46,7 +46,7 @@ PROCS = { "selfdrive.thermald.thermald": 3.87, "selfdrive.locationd.calibrationd": 2.0, "selfdrive.locationd.torqued": 5.0, - "./_soundd": (1.0, 65.0), + "selfdrive.ui.soundd": 5.8, "selfdrive.monitoring.dmonitoringd": 4.0, "./proclogd": 1.54, "system.logmessaged": 0.2, @@ -408,7 +408,7 @@ class TestOnroad(unittest.TestCase): def test_startup(self): startup_alert = None 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": startup_alert = msg.controlsState.alertText1 break @@ -417,8 +417,8 @@ class TestOnroad(unittest.TestCase): def test_engagable(self): no_entries = Counter() - for m in self.service_msgs['carEvents']: - for evt in m.carEvents: + for m in self.service_msgs['onroadEvents']: + for evt in m.onroadEvents: if evt.noEntry: no_entries[evt.name] += 1 diff --git a/selfdrive/test/test_time_to_onroad.py b/selfdrive/test/test_time_to_onroad.py index cc2cc6514e..aec49cb13a 100755 --- a/selfdrive/test/test_time_to_onroad.py +++ b/selfdrive/test/test_time_to_onroad.py @@ -18,7 +18,7 @@ def test_time_to_onroad(): proc = subprocess.Popen(["python", manager_path]) start_time = time.monotonic() - sm = messaging.SubMaster(['controlsState', 'deviceState', 'carEvents']) + sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents']) try: # wait for 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 for _ in range(500): sm.update(100) - assert sm['controlsState'].engageable, f"events: {sm['carEvents']}" + assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}" finally: proc.terminate() if proc.wait(60) is None: diff --git a/selfdrive/thermald/fan_controller.py b/selfdrive/thermald/fan_controller.py index 64cd4c78ee..19c3292ce2 100755 --- a/selfdrive/thermald/fan_controller.py +++ b/selfdrive/thermald/fan_controller.py @@ -3,7 +3,7 @@ from abc import ABC, abstractmethod from openpilot.common.realtime import DT_TRML 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 class BaseFanController(ABC): diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 0b3c73a1c0..0520385473 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -4,7 +4,7 @@ from typing import Optional from openpilot.common.params import Params, put_nonblocking from openpilot.system.hardware import HARDWARE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.statsd import statlog CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 3ccb9349e1..8b51aedbdb 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -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.loggerd.config import get_available_percent 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.fan_controller import TiciFanController from openpilot.system.version import terms_version, training_version @@ -81,7 +81,7 @@ def read_tz(x): 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.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] diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 3f2fb28d23..5a81e93b28 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -9,10 +9,9 @@ import time import glob from typing import NoReturn -from openpilot.common.file_helpers import mkdirs_exists_ok import openpilot.selfdrive.sentry as sentry 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 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] 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 shutil.copy(fn, os.path.join(crashlog_dir, new_fn)) diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 916f1017a3..f0db1f63a7 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -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]) 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) # spinner and text window diff --git a/selfdrive/ui/__init__.py b/selfdrive/ui/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 18a79b5983..40dda971f5 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -114,3 +114,28 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { opt.initFrom(this); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); } + +// ParamControl + +ParamControl::ParamControl(const QString ¶m, 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("

" + title_label->text() + "


" + "

" + getDescription() + "

"); + 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(); + } +} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index 811595726d..e4630c6590 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -144,24 +144,7 @@ class ParamControl : public ToggleControl { Q_OBJECT public: - ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { - key = param.toStdString(); - QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { - QString content("

" + title + "


" - "

" + getDescription() + "

"); - 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(); - } - }); - } - + ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr); void setConfirmation(bool _confirm, bool _store_confirm) { confirm = _confirm; store_confirm = _store_confirm; @@ -184,6 +167,7 @@ public: } private: + void toggleClicked(bool state); void setIcon(bool state) { if (state && !active_icon_pixmap.isNull()) { icon_label->setPixmap(active_icon_pixmap); diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py new file mode 100644 index 0000000000..9d6d24a594 --- /dev/null +++ b/selfdrive/ui/soundd.py @@ -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() \ No newline at end of file diff --git a/selfdrive/ui/soundd/.gitignore b/selfdrive/ui/soundd/.gitignore deleted file mode 100644 index c47f949d37..0000000000 --- a/selfdrive/ui/soundd/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_soundd diff --git a/selfdrive/ui/soundd/main.cc b/selfdrive/ui/soundd/main.cc deleted file mode 100644 index c6c7434ca4..0000000000 --- a/selfdrive/ui/soundd/main.cc +++ /dev/null @@ -1,18 +0,0 @@ -#include - -#include - -#include "selfdrive/ui/qt/util.h" -#include "selfdrive/ui/soundd/sound.h" - -int main(int argc, char **argv) { - qInstallMessageHandler(swagLogMessageHandler); - setpriority(PRIO_PROCESS, 0, -20); - - QApplication a(argc, argv); - std::signal(SIGINT, sigTermHandler); - std::signal(SIGTERM, sigTermHandler); - - Sound sound; - return a.exec(); -} diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc deleted file mode 100644 index a5884f113f..0000000000 --- a/selfdrive/ui/soundd/sound.cc +++ /dev/null @@ -1,67 +0,0 @@ -#include "selfdrive/ui/soundd/sound.h" - -#include - -#include -#include -#include - -#include "cereal/messaging/messaging.h" -#include "common/util.h" - -// TODO: detect when we can't play sounds -// TODO: detect when we can't display the UI - -Sound::Sound(QObject *parent) : sm({"controlsState", "microphone"}) { - qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); - - for (auto &[alert, fn, loops, volume] : sound_list) { - QSoundEffect *s = new QSoundEffect(this); - QObject::connect(s, &QSoundEffect::statusChanged, [=]() { - assert(s->status() != QSoundEffect::Error); - }); - s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); - s->setVolume(volume); - sounds[alert] = {s, loops}; - } - - QTimer *timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &Sound::update); - timer->start(1000 / UI_FREQ); -} - -void Sound::update() { - sm.update(0); - - // scale volume using ambient noise level - if (sm.updated("microphone")) { - float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 60.f, 0.f, 1.f); - volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - // set volume on changes - if (std::exchange(current_volume, std::nearbyint(volume * 10)) != current_volume) { - Hardware::set_volume(volume); - } - } - - setAlert(Alert::get(sm, 0)); -} - -void Sound::setAlert(const Alert &alert) { - if (!current_alert.equal(alert)) { - current_alert = alert; - // stop sounds - for (auto &[s, loops] : sounds) { - // Only stop repeating sounds - if (s->loopsRemaining() > 1 || s->loopsRemaining() == QSoundEffect::Infinite) { - s->stop(); - } - } - - // play sound - if (alert.sound != AudibleAlert::NONE) { - auto &[s, loops] = sounds[alert.sound]; - s->setLoopCount(loops); - s->play(); - } - } -} diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h deleted file mode 100644 index 4fcb2e1bce..0000000000 --- a/selfdrive/ui/soundd/sound.h +++ /dev/null @@ -1,41 +0,0 @@ -#pragma once - -#include - -#include -#include -#include - -#include "system/hardware/hw.h" -#include "selfdrive/ui/ui.h" - - -const float MAX_VOLUME = 1.0; - -const std::tuple sound_list[] = { - // AudibleAlert, file name, loop count - {AudibleAlert::ENGAGE, "engage.wav", 0, MAX_VOLUME}, - {AudibleAlert::DISENGAGE, "disengage.wav", 0, MAX_VOLUME}, - {AudibleAlert::REFUSE, "refuse.wav", 0, MAX_VOLUME}, - - {AudibleAlert::PROMPT, "prompt.wav", 0, MAX_VOLUME}, - {AudibleAlert::PROMPT_REPEAT, "prompt.wav", QSoundEffect::Infinite, MAX_VOLUME}, - {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite, MAX_VOLUME}, - - {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite, MAX_VOLUME}, - {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite, MAX_VOLUME}, -}; - -class Sound : public QObject { -public: - explicit Sound(QObject *parent = 0); - -protected: - void update(); - void setAlert(const Alert &alert); - - SubMaster sm; - Alert current_alert = {}; - QMap> sounds; - int current_volume = -1; -}; diff --git a/selfdrive/ui/soundd/soundd b/selfdrive/ui/soundd/soundd deleted file mode 100755 index 9b7a32fec9..0000000000 --- a/selfdrive/ui/soundd/soundd +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -cd "$(dirname "$0")" -export QT_QPA_PLATFORM="offscreen" -exec ./_soundd diff --git a/selfdrive/ui/tests/test_sound.cc b/selfdrive/ui/tests/test_sound.cc deleted file mode 100644 index d9cb5c0a7f..0000000000 --- a/selfdrive/ui/tests/test_sound.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include - -#include "catch2/catch.hpp" -#include "selfdrive/ui/soundd/sound.h" - -class TestSound : public Sound { -public: - TestSound() : Sound() { - for (auto i = sounds.constBegin(); i != sounds.constEnd(); ++i) { - sound_stats[i.key()] = {0, 0}; - QObject::connect(i.value().first, &QSoundEffect::playingChanged, [=, s = i.value().first, a = i.key()]() { - if (s->isPlaying()) { - sound_stats[a].first++; - } else { - sound_stats[a].second++; - } - }); - } - } - - QMap> sound_stats; -}; - -void controls_thread(int loop_cnt) { - PubMaster pm({"controlsState", "deviceState"}); - MessageBuilder deviceStateMsg; - auto deviceState = deviceStateMsg.initEvent().initDeviceState(); - deviceState.setStarted(true); - - const int DT_CTRL = 10; // ms - for (int i = 0; i < loop_cnt; ++i) { - for (auto &[alert, fn, loops, volume] : sound_list) { - printf("testing %s\n", qPrintable(fn)); - for (int j = 0; j < 1000 / DT_CTRL; ++j) { - MessageBuilder msg; - auto cs = msg.initEvent().initControlsState(); - cs.setAlertSound(alert); - cs.setAlertType(fn.toStdString()); - pm.send("controlsState", msg); - pm.send("deviceState", deviceStateMsg); - QThread::msleep(DT_CTRL); - } - } - } - - // send no alert sound - for (int j = 0; j < 1000 / DT_CTRL; ++j) { - MessageBuilder msg; - msg.initEvent().initControlsState(); - pm.send("controlsState", msg); - QThread::msleep(DT_CTRL); - } - - QThread::currentThread()->quit(); -} - -TEST_CASE("test soundd") { - QEventLoop loop; - TestSound test_sound; - const int test_loop_cnt = 2; - - QThread t; - QObject::connect(&t, &QThread::started, [=]() { controls_thread(test_loop_cnt); }); - QObject::connect(&t, &QThread::finished, [&]() { loop.quit(); }); - t.start(); - loop.exec(); - - for (const AudibleAlert alert : test_sound.sound_stats.keys()) { - auto [play, stop] = test_sound.sound_stats[alert]; - REQUIRE(play == test_loop_cnt); - REQUIRE(stop == test_loop_cnt); - } -} diff --git a/selfdrive/ui/tests/test_soundd.py b/selfdrive/ui/tests/test_soundd.py index 80a261e6d9..94ce26eb47 100755 --- a/selfdrive/ui/tests/test_soundd.py +++ b/selfdrive/ui/tests/test_soundd.py @@ -1,75 +1,41 @@ #!/usr/bin/env python3 -import subprocess -import time import unittest -from cereal import log, car -import cereal.messaging as messaging -from openpilot.selfdrive.test.helpers import phone_only, with_processes -# TODO: rewrite for unittest -from openpilot.common.realtime import DT_CTRL -from openpilot.system.hardware import HARDWARE +from cereal import car +from cereal import messaging +from cereal.messaging import SubMaster, PubMaster +from openpilot.selfdrive.ui.soundd import CONTROLS_TIMEOUT, check_controls_timeout_alert -AudibleAlert = car.CarControl.HUDControl.AudibleAlert +import time -SOUNDS = { - # sound: total writes - AudibleAlert.none: 0, - AudibleAlert.engage: 184, - AudibleAlert.disengage: 186, - AudibleAlert.refuse: 194, - AudibleAlert.prompt: 184, - AudibleAlert.promptRepeat: 487, - AudibleAlert.promptDistracted: 508, - AudibleAlert.warningSoft: 471, - AudibleAlert.warningImmediate: 470, -} +AudibleAlert = car.CarControl.HUDControl.AudibleAlert -def get_total_writes(): - audio_flinger = subprocess.check_output('dumpsys media.audio_flinger', shell=True, encoding='utf-8').strip() - write_lines = [l for l in audio_flinger.split('\n') if l.strip().startswith('Total writes')] - return sum(int(l.split(':')[1]) for l in write_lines) class TestSoundd(unittest.TestCase): - def test_sound_card_init(self): - assert HARDWARE.get_sound_card_online() + def test_check_controls_timeout_alert(self): + sm = SubMaster(['controlsState']) + pm = PubMaster(['controlsState']) + + for _ in range(100): + cs = messaging.new_message('controlsState') + cs.controlsState.enabled = True + + pm.send("controlsState", cs) - @phone_only - @with_processes(['soundd']) - def test_alert_sounds(self): - pm = messaging.PubMaster(['deviceState', 'controlsState']) + time.sleep(0.01) - # make sure they're all defined - alert_sounds = {v: k for k, v in car.CarControl.HUDControl.AudibleAlert.schema.enumerants.items()} - diff = set(SOUNDS.keys()).symmetric_difference(alert_sounds.keys()) - assert len(diff) == 0, f"not all sounds defined in test: {diff}" + sm.update(0) - # wait for procs to init - time.sleep(1) + self.assertFalse(check_controls_timeout_alert(sm)) - for sound, expected_writes in SOUNDS.items(): - print(f"testing {alert_sounds[sound]}") - start_writes = get_total_writes() + for _ in range(CONTROLS_TIMEOUT * 110): + sm.update(0) + time.sleep(0.01) - for i in range(int(10 / DT_CTRL)): - msg = messaging.new_message('deviceState') - msg.deviceState.started = True - pm.send('deviceState', msg) + self.assertTrue(check_controls_timeout_alert(sm)) - msg = messaging.new_message('controlsState') - if i < int(6 / DT_CTRL): - msg.controlsState.alertSound = sound - msg.controlsState.alertType = str(sound) - msg.controlsState.alertText1 = "Testing Sounds" - msg.controlsState.alertText2 = f"playing {alert_sounds[sound]}" - msg.controlsState.alertSize = log.ControlsState.AlertSize.mid - pm.send('controlsState', msg) - time.sleep(DT_CTRL) + # TODO: add test with micd for checking that soundd actually outputs sounds - tolerance = expected_writes / 8 - actual_writes = get_total_writes() - start_writes - print(f" expected {expected_writes} writes, got {actual_writes}") - assert abs(expected_writes - actual_writes) <= tolerance, f"{alert_sounds[sound]}: expected {expected_writes} writes, got {actual_writes}" if __name__ == "__main__": unittest.main() diff --git a/selfdrive/updated.py b/selfdrive/updated.py index fabe28920f..db6a06dc9e 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -18,7 +18,7 @@ from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params from openpilot.common.time import system_time_valid from openpilot.system.hardware import AGNOS, HARDWARE -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.system.version import is_tested_branch diff --git a/system/camerad/SConscript b/system/camerad/SConscript index ffd7278bbb..a0056c1f9f 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -2,13 +2,9 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] -camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) -env.Program('camerad', [ - 'main.cc', - camera_obj, - ], LIBS=libs) +camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc', + 'sensors/ar0231.cc', 'sensors/ox03c10.cc']) +env.Program('camerad', ['main.cc', camera_obj], LIBS=libs) if GetOption("extras") and arch == "x86_64": - env.Program('test/ae_gray_test', - ['test/ae_gray_test.cc', camera_obj], - LIBS=libs) + env.Program('test/ae_gray_test', ['test/ae_gray_test.cc', camera_obj], LIBS=libs) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index d2c7a35f6e..e36766ca2d 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -11,7 +11,6 @@ #include "third_party/libyuv/include/libyuv.h" #include -#include "system/camerad/imgproc/utils.h" #include "common/clutil.h" #include "common/swaglog.h" #include "common/util.h" @@ -29,7 +28,7 @@ class Debayer { public: Debayer(cl_device_id device_id, cl_context context, const CameraBuf *b, const CameraState *s, int buf_width, int uv_offset) { char args[4096]; - const CameraInfo *ci = &s->ci; + const SensorInfo *ci = s->ci.get(); snprintf(args, sizeof(args), "-cl-fast-relaxed-math -cl-denorms-are-zero " "-DFRAME_WIDTH=%d -DFRAME_HEIGHT=%d -DFRAME_STRIDE=%d -DFRAME_OFFSET=%d " @@ -37,7 +36,7 @@ public: "-DIS_OX=%d -DCAM_NUM=%d%s", ci->frame_width, ci->frame_height, ci->frame_stride, ci->frame_offset, b->rgb_width, b->rgb_height, b->rgb_stride, buf_width, uv_offset, - s->camera_id==CAMERA_ID_OX03C10 ? 1 : 0, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); + ci->image_sensor == cereal::FrameData::ImageSensor::OX03C10, s->camera_num, s->camera_num==1 ? " -DVIGNETTING" : ""); const char *cl_file = "cameras/real_debayer.cl"; cl_program prg_debayer = cl_program_from_file(context, device_id, cl_file, args); krnl_ = CL_CHECK_ERR(clCreateKernel(prg_debayer, "debayer10", &err)); @@ -67,7 +66,7 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, this->yuv_type = init_yuv_type; frame_buf_count = frame_cnt; - const CameraInfo *ci = &s->ci; + const SensorInfo *ci = s->ci.get(); // RAW frame const int frame_size = (ci->frame_height + ci->extra_height) * ci->frame_stride; camera_bufs = std::make_unique(frame_buf_count); @@ -93,12 +92,8 @@ void CameraBuf::init(cl_device_id device_id, cl_context context, CameraState *s, debayer = new Debayer(device_id, context, this, s, nv12_width, nv12_uv_offset); -#ifdef __APPLE__ - q = CL_CHECK_ERR(clCreateCommandQueue(context, device_id, 0, &err)); -#else const cl_queue_properties props[] = {0}; //CL_QUEUE_PRIORITY_KHR, CL_QUEUE_PRIORITY_HIGH_KHR, 0}; q = CL_CHECK_ERR(clCreateCommandQueueWithProperties(context, device_id, props, &err)); -#endif } CameraBuf::~CameraBuf() { @@ -157,14 +152,9 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; - const float perc = util::map_val(ev, c->min_ev, c->max_ev, 0.0f, 100.0f); + const float perc = util::map_val(ev, c->ci->min_ev, c->ci->max_ev, 0.0f, 100.0f); framed.setExposureValPercent(perc); - - if (c->camera_id == CAMERA_ID_AR0231) { - framed.setSensor(cereal::FrameData::ImageSensor::AR0231); - } else if (c->camera_id == CAMERA_ID_OX03C10) { - framed.setSensor(cereal::FrameData::ImageSensor::OX03C10); - } + framed.setSensor(c->ci->image_sensor); } kj::Array get_raw_frame_image(const CameraBuf *b) { @@ -281,7 +271,6 @@ float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip } } - // Find mean lumimance value unsigned int lum_cur = 0; for (lum_med = 255; lum_med >= 0; lum_med--) { diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 0f69aa3774..e93d357b8d 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -14,18 +14,6 @@ #include "common/swaglog.h" #include "system/hardware/hw.h" -#define CAMERA_ID_IMX298 0 -#define CAMERA_ID_IMX179 1 -#define CAMERA_ID_S5K3P8SP 2 -#define CAMERA_ID_OV8865 3 -#define CAMERA_ID_IMX298_FLIPPED 4 -#define CAMERA_ID_OV10640 5 -#define CAMERA_ID_LGC920 6 -#define CAMERA_ID_LGC615 7 -#define CAMERA_ID_AR0231 8 -#define CAMERA_ID_OX03C10 9 -#define CAMERA_ID_MAX 10 - const int YUV_BUFFER_COUNT = 20; enum CameraType { @@ -42,20 +30,11 @@ const bool env_debug_frames = getenv("DEBUG_FRAMES") != NULL; const bool env_log_raw_frames = getenv("LOG_RAW_FRAMES") != NULL; const bool env_ctrl_exp_from_params = getenv("CTRL_EXP_FROM_PARAMS") != NULL; -typedef struct CameraInfo { - uint32_t frame_width, frame_height; - uint32_t frame_stride; - uint32_t frame_offset = 0; - uint32_t extra_height = 0; - int registers_offset = -1; - int stats_offset = -1; -} CameraInfo; - typedef struct FrameMetadata { uint32_t frame_id; // Timestamps - uint64_t timestamp_sof; // only set on tici + uint64_t timestamp_sof; uint64_t timestamp_eof; // Exposure diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 3a9ecb467b..194f11fe6d 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -19,105 +19,17 @@ #include "media/cam_defs.h" #include "media/cam_isp.h" #include "media/cam_isp_ife.h" -#include "media/cam_sensor.h" #include "media/cam_sensor_cmn_header.h" #include "media/cam_sync.h" #include "common/swaglog.h" -#include "system/camerad/cameras/sensor2_i2c.h" + +const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py // For debugging: // echo "4294967295" > /sys/module/cam_debug_util/parameters/debug_mdl extern ExitHandler do_exit; -const size_t FRAME_WIDTH = 1928; -const size_t FRAME_HEIGHT = 1208; -const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) - -const size_t AR0231_REGISTERS_HEIGHT = 2; -// TODO: this extra height is universal and doesn't apply per camera -const size_t AR0231_STATS_HEIGHT = 2+8; - -const int MIPI_SETTLE_CNT = 33; // Calculated by camera_freqs.py - -CameraInfo cameras_supported[CAMERA_ID_MAX] = { - [CAMERA_ID_AR0231] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, - .extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT, - - .registers_offset = 0, - .frame_offset = AR0231_REGISTERS_HEIGHT, - .stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT, - }, - [CAMERA_ID_OX03C10] = { - .frame_width = FRAME_WIDTH, - .frame_height = FRAME_HEIGHT, - .frame_stride = FRAME_STRIDE, // (0xa80*12//8) - .extra_height = 16, // top 2 + bot 14 - .frame_offset = 2, - }, -}; - -const float DC_GAIN_AR0231 = 2.5; -const float DC_GAIN_OX03C10 = 7.32; - -const float DC_GAIN_ON_GREY_AR0231 = 0.2; -const float DC_GAIN_OFF_GREY_AR0231 = 0.3; -const float DC_GAIN_ON_GREY_OX03C10 = 0.9; -const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; - -const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; -const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; -const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine -const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; - -const float TARGET_GREY_FACTOR_AR0231 = 1.0; -const float TARGET_GREY_FACTOR_OX03C10 = 0.01; - -const float sensor_analog_gains_AR0231[] = { - 1.0/8.0, 2.0/8.0, 2.0/7.0, 3.0/7.0, // 0, 1, 2, 3 - 3.0/6.0, 4.0/6.0, 4.0/5.0, 5.0/5.0, // 4, 5, 6, 7 - 5.0/4.0, 6.0/4.0, 6.0/3.0, 7.0/3.0, // 8, 9, 10, 11 - 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass - -const float sensor_analog_gains_OX03C10[] = { - 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, - 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, - 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, - 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, - 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; - -const uint32_t ox03c10_analog_gains_reg[] = { - 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, - 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, - 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, - 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, - 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; - -const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x -const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x -const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x -const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; -const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; -const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; - -const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; -const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x0; // 1x -const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; -const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; -const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; -const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; - -const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss -const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms - -const int EXPOSURE_TIME_MIN_OX03C10 = 2; // 1x -const int EXPOSURE_TIME_MAX_OX03C10 = 2016; -const uint32_t VS_TIME_MIN_OX03C10 = 1; -const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 - int CameraState::clear_req_queue() { struct cam_req_mgr_flush_info req_mgr_flush_request = {0}; req_mgr_flush_request.session_hdl = session_handle; @@ -134,13 +46,7 @@ int CameraState::clear_req_queue() { void CameraState::sensors_start() { if (!enabled) return; LOGD("starting sensor %d", camera_num); - if (camera_id == CAMERA_ID_AR0231) { - sensors_i2c(start_reg_array_ar0231, std::size(start_reg_array_ar0231), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - } else if (camera_id == CAMERA_ID_OX03C10) { - sensors_i2c(start_reg_array_ox03c10, std::size(start_reg_array_ox03c10), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - } else { - assert(false); - } + sensors_i2c(ci->start_reg_array.data(), ci->start_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); } void CameraState::sensors_poke(int request_id) { @@ -161,7 +67,7 @@ void CameraState::sensors_poke(int request_id) { } } -void CameraState::sensors_i2c(struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word) { +void CameraState::sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word) { // LOGD("sensors_i2c: %d", len); uint32_t cam_packet_handle = 0; int size = sizeof(struct cam_packet)+sizeof(struct cam_cmd_buf_desc)*1; @@ -215,21 +121,7 @@ int CameraState::sensors_init() { auto probe = (struct cam_cmd_probe *)(i2c_info.get() + 1); probe->camera_id = camera_num; - switch (camera_num) { - case 0: - // port 0 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x20 : 0x6C; // 6C = 0x36*2 - break; - case 1: - // port 1 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x30 : 0x20; - break; - case 2: - // port 2 - i2c_info->slave_addr = (camera_id == CAMERA_ID_AR0231) ? 0x20 : 0x6C; - break; - } - + i2c_info->slave_addr = ci->getSlaveAddress(camera_num); // 0(I2C_STANDARD_MODE) = 100khz, 1(I2C_FAST_MODE) = 400khz //i2c_info->i2c_freq_mode = I2C_STANDARD_MODE; i2c_info->i2c_freq_mode = I2C_FAST_MODE; @@ -239,15 +131,8 @@ int CameraState::sensors_init() { probe->addr_type = CAMERA_SENSOR_I2C_TYPE_WORD; probe->op_code = 3; // don't care? probe->cmd_type = CAMERA_SENSOR_CMD_TYPE_PROBE; - if (camera_id == CAMERA_ID_AR0231) { - probe->reg_addr = 0x3000; - probe->expected_data = 0x354; - } else if (camera_id == CAMERA_ID_OX03C10) { - probe->reg_addr = 0x300a; - probe->expected_data = 0x5803; - } else { - assert(false); - } + probe->reg_addr = ci->probe_reg_addr; + probe->expected_data = ci->probe_expected_data; probe->data_mask = 0; //buf_desc[1].size = buf_desc[1].length = 148; @@ -270,7 +155,7 @@ int CameraState::sensors_init() { power->count = 1; power->cmd_type = CAMERA_SENSOR_CMD_TYPE_PWR_UP; power->power_settings[0].power_seq_type = 0; - power->power_settings[0].config_val_low = (camera_id == CAMERA_ID_AR0231) ? 19200000 : 24000000; //Hz + power->power_settings[0].config_val_low = ci->power_config_val_low; power = power_set_wait(power, 1); // reset high @@ -423,10 +308,10 @@ void CameraState::config_isp(int io_mem_handle, int fence, int request_id, int b if (io_mem_handle != 0) { io_cfg[0].mem_handle[0] = io_mem_handle; io_cfg[0].planes[0] = (struct cam_plane_cfg){ - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .plane_stride = ci.frame_stride, - .slice_height = ci.frame_height + ci.extra_height, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, + .plane_stride = ci->frame_stride, + .slice_height = ci->frame_height + ci->extra_height, .meta_stride = 0x0, // YUV has meta(stride=0x400, size=0x5000) .meta_size = 0x0, .meta_offset = 0x0, @@ -516,57 +401,14 @@ void CameraState::enqueue_req_multi(int start, int n, bool dp) { // ******************* camera ******************* -void CameraState::camera_set_parameters() { - if (camera_id == CAMERA_ID_AR0231) { - dc_gain_factor = DC_GAIN_AR0231; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_AR0231; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_AR0231; - dc_gain_on_grey = DC_GAIN_ON_GREY_AR0231; - dc_gain_off_grey = DC_GAIN_OFF_GREY_AR0231; - exposure_time_min = EXPOSURE_TIME_MIN_AR0231; - exposure_time_max = EXPOSURE_TIME_MAX_AR0231; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; - for (int i=0; i<=analog_gain_max_idx; i++) { - sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; - } - min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; - target_grey_factor = TARGET_GREY_FACTOR_AR0231; - } else if (camera_id == CAMERA_ID_OX03C10) { - dc_gain_factor = DC_GAIN_OX03C10; - dc_gain_min_weight = DC_GAIN_MIN_WEIGHT_OX03C10; - dc_gain_max_weight = DC_GAIN_MAX_WEIGHT_OX03C10; - dc_gain_on_grey = DC_GAIN_ON_GREY_OX03C10; - dc_gain_off_grey = DC_GAIN_OFF_GREY_OX03C10; - exposure_time_min = EXPOSURE_TIME_MIN_OX03C10; - exposure_time_max = EXPOSURE_TIME_MAX_OX03C10; - analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; - analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; - analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; - analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; - analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; - analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; - for (int i=0; i<=analog_gain_max_idx; i++) { - sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; - } - min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; - target_grey_factor = TARGET_GREY_FACTOR_OX03C10; - } else { - assert(false); - } - - max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; +void CameraState::sensor_set_parameters() { target_grey_fraction = 0.3; dc_gain_enabled = false; - dc_gain_weight = dc_gain_min_weight; - gain_idx = analog_gain_rec_idx; + dc_gain_weight = ci->dc_gain_min_weight; + gain_idx = ci->analog_gain_rec_idx; exposure_time = 5; - cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight) * sensor_analog_gains[gain_idx] * exposure_time; + cur_ev[0] = cur_ev[1] = cur_ev[2] = (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight) * ci->sensor_analog_gains[gain_idx] * exposure_time; } void CameraState::camera_map_bufs(MultiCameraState *s) { @@ -584,20 +426,13 @@ void CameraState::camera_map_bufs(MultiCameraState *s) { enqueue_req_multi(1, FRAME_BUF_COUNT, 0); } -void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, int camera_id_, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { +void CameraState::camera_init(MultiCameraState *s, VisionIpcServer * v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type) { if (!enabled) return; - camera_id = camera_id_; LOGD("camera init %d", camera_num); - assert(camera_id < std::size(cameras_supported)); - ci = cameras_supported[camera_id]; - assert(ci.frame_width != 0); - request_id_last = 0; skipped = true; - camera_set_parameters(); - buf.init(device_id, ctx, this, v, FRAME_BUF_COUNT, yuv_type); camera_map_bufs(s); } @@ -616,22 +451,25 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num // init memorymanager for this camera mm.init(multi_cam_state->video0_fd); - // probe the sensor LOGD("-- Probing sensor %d", camera_num); - camera_id = CAMERA_ID_AR0231; - ret = sensors_init(); - if (ret != 0) { - // TODO: use build flag instead? - LOGD("AR0231 init failed, trying OX03C10"); - camera_id = CAMERA_ID_OX03C10; - ret = sensors_init(); - } - LOGD("-- Probing sensor %d done with %d", camera_num, ret); - if (ret != 0) { + + auto init_sensor_lambda = [this](SensorInfo *sensor) { + ci.reset(sensor); + int ret = sensors_init(); + if (ret == 0) { + sensor_set_parameters(); + } + return ret == 0; + }; + + // Try different sensors one by one until it success. + if (!init_sensor_lambda(new AR0231) && + !init_sensor_lambda(new OX03C10)) { LOGE("** sensor %d FAILED bringup, disabling", camera_num); enabled = false; return; } + LOGD("-- Probing sensor %d success", camera_num); // create session struct cam_req_mgr_session_info session_info = {}; @@ -647,69 +485,59 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num LOGD("acquire sensor dev"); LOG("-- Configuring sensor"); - uint32_t dt; - if (camera_id == CAMERA_ID_AR0231) { - sensors_i2c(init_array_ar0231, std::size(init_array_ar0231), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - dt = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead - } else if (camera_id == CAMERA_ID_OX03C10) { - sensors_i2c(init_array_ox03c10, std::size(init_array_ox03c10), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - // one is 0x2a, two are 0x2b - dt = 0x2c; - } else { - assert(false); - } - printf("dt is %x\n", dt); + sensors_i2c(ci->init_reg_array.data(), ci->init_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); + printf("dt is %x\n", ci->in_port_info_dt); // NOTE: to be able to disable road and wide road, we still have to configure the sensor over i2c // If you don't do this, the strobe GPIO is an output (even in reset it seems!) if (!enabled) return; struct cam_isp_in_port_info in_port_info = { - .res_type = (uint32_t[]){CAM_ISP_IFE_IN_RES_PHY_0, CAM_ISP_IFE_IN_RES_PHY_1, CAM_ISP_IFE_IN_RES_PHY_2}[camera_num], + .res_type = (uint32_t[]){CAM_ISP_IFE_IN_RES_PHY_0, CAM_ISP_IFE_IN_RES_PHY_1, CAM_ISP_IFE_IN_RES_PHY_2}[camera_num], - .lane_type = CAM_ISP_LANE_TYPE_DPHY, - .lane_num = 4, - .lane_cfg = 0x3210, + .lane_type = CAM_ISP_LANE_TYPE_DPHY, + .lane_num = 4, + .lane_cfg = 0x3210, - .vc = 0x0, - .dt = dt, - .format = CAM_FORMAT_MIPI_RAW_12, + .vc = 0x0, + .dt = ci->in_port_info_dt, + .format = CAM_FORMAT_MIPI_RAW_12, + + .test_pattern = 0x2, // 0x3? + .usage_type = 0x0, - .test_pattern = 0x2, // 0x3? - .usage_type = 0x0, - - .left_start = 0, - .left_stop = ci.frame_width - 1, - .left_width = ci.frame_width, - - .right_start = 0, - .right_stop = ci.frame_width - 1, - .right_width = ci.frame_width, - - .line_start = 0, - .line_stop = ci.frame_height + ci.extra_height - 1, - .height = ci.frame_height + ci.extra_height, - - .pixel_clk = 0x0, - .batch_size = 0x0, - .dsp_mode = CAM_ISP_DSP_MODE_NONE, - .hbi_cnt = 0x0, - .custom_csid = 0x0, - - .num_out_res = 0x1, - .data[0] = (struct cam_isp_out_port_info){ - .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, - .format = CAM_FORMAT_MIPI_RAW_12, - .width = ci.frame_width, - .height = ci.frame_height + ci.extra_height, - .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, - }, + .left_start = 0, + .left_stop = ci->frame_width - 1, + .left_width = ci->frame_width, + + .right_start = 0, + .right_stop = ci->frame_width - 1, + .right_width = ci->frame_width, + + .line_start = 0, + .line_stop = ci->frame_height + ci->extra_height - 1, + .height = ci->frame_height + ci->extra_height, + + .pixel_clk = 0x0, + .batch_size = 0x0, + .dsp_mode = CAM_ISP_DSP_MODE_NONE, + .hbi_cnt = 0x0, + .custom_csid = 0x0, + + .num_out_res = 0x1, + .data[0] = (struct cam_isp_out_port_info){ + .res_type = CAM_ISP_IFE_OUT_RES_RDI_0, + .format = CAM_FORMAT_MIPI_RAW_12, + .width = ci->frame_width, + .height = ci->frame_height + ci->extra_height, + .comp_grp_id = 0x0, .split_point = 0x0, .secure_mode = 0x0, + }, }; struct cam_isp_resource isp_resource = { - .resource_id = CAM_ISP_RES_ID_PORT, - .handle_type = CAM_HANDLE_USER_POINTER, - .res_hdl = (uint64_t)&in_port_info, - .length = sizeof(in_port_info), + .resource_id = CAM_ISP_RES_ID_PORT, + .handle_type = CAM_HANDLE_USER_POINTER, + .res_hdl = (uint64_t)&in_port_info, + .length = sizeof(in_port_info), }; auto isp_dev_handle_ = device_acquire(multi_cam_state->isp_fd, session_handle, &isp_resource); @@ -790,9 +618,9 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num } void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx) { - s->driver_cam.camera_init(s, v, s->driver_cam.camera_id, 20, device_id, ctx, VISION_STREAM_DRIVER); - s->road_cam.camera_init(s, v, s->road_cam.camera_id, 20, device_id, ctx, VISION_STREAM_ROAD); - s->wide_road_cam.camera_init(s, v, s->wide_road_cam.camera_id, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD); + s->driver_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_DRIVER); + s->road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_ROAD); + s->wide_road_cam.camera_init(s, v, 20, device_id, ctx, VISION_STREAM_WIDE_ROAD); s->pm = new PubMaster({"roadCameraState", "driverCameraState", "wideRoadCameraState", "thumbnail"}); } @@ -907,70 +735,6 @@ void cameras_close(MultiCameraState *s) { delete s->pm; } -std::map> CameraState::ar0231_build_register_lut(uint8_t *data) { - // This function builds a lookup table from register address, to a pair of indices in the - // buffer where to read this address. The buffer contains padding bytes, - // as well as markers to indicate the type of the next byte. - // - // 0xAA is used to indicate the MSB of the address, 0xA5 for the LSB of the address. - // Every byte of data (MSB and LSB) is preceded by 0x5A. Specifying an address is optional - // for contiguous ranges. See page 27-29 of the AR0231 Developer guide for more information. - - int max_i[] = {1828 / 2 * 3, 1500 / 2 * 3}; - auto get_next_idx = [](int cur_idx) { - return (cur_idx % 3 == 1) ? cur_idx + 2 : cur_idx + 1; // Every third byte is padding - }; - - std::map> registers; - for (int register_row = 0; register_row < 2; register_row++) { - uint8_t *registers_raw = data + ci.frame_stride * register_row; - assert(registers_raw[0] == 0x0a); // Start of line - - int value_tag_count = 0; - int first_val_idx = 0; - uint16_t cur_addr = 0; - - for (int i = 1; i <= max_i[register_row]; i = get_next_idx(get_next_idx(i))) { - int val_idx = get_next_idx(i); - - uint8_t tag = registers_raw[i]; - uint16_t val = registers_raw[val_idx]; - - if (tag == 0xAA) { // Register MSB tag - cur_addr = val << 8; - } else if (tag == 0xA5) { // Register LSB tag - cur_addr |= val; - cur_addr -= 2; // Next value tag will increment address again - } else if (tag == 0x5A) { // Value tag - - // First tag - if (value_tag_count % 2 == 0) { - cur_addr += 2; - first_val_idx = val_idx; - } else { - registers[cur_addr] = std::make_pair(first_val_idx + ci.frame_stride * register_row, val_idx + ci.frame_stride * register_row); - } - - value_tag_count++; - } - } - } - return registers; -} - -std::map CameraState::ar0231_parse_registers(uint8_t *data, std::initializer_list addrs) { - if (ar0231_register_lut.empty()) { - ar0231_register_lut = ar0231_build_register_lut(data); - } - - std::map registers; - for (uint16_t addr : addrs) { - auto offset = ar0231_register_lut[addr]; - registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; - } - return registers; -} - void CameraState::handle_camera_event(void *evdat) { if (!enabled) return; struct cam_req_mgr_message *event_data = (struct cam_req_mgr_message *)evdat; @@ -1009,7 +773,7 @@ void CameraState::handle_camera_event(void *evdat) { meta_data.frame_id = main_id - idx_offset; meta_data.timestamp_sof = timestamp; exp_lock.lock(); - meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + meta_data.gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); meta_data.high_conversion_gain = dc_gain_enabled; meta_data.integ_lines = exposure_time; meta_data.measured_grey_fraction = measured_grey_fraction; @@ -1030,22 +794,7 @@ void CameraState::handle_camera_event(void *evdat) { } void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain) { - float score = 1e6; - if (camera_id == CAMERA_ID_AR0231) { - // Cost of ev diff - score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; - // Cost of absolute gain - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; - // Cost of changing gain - score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; - } else if (camera_id == CAMERA_ID_OX03C10) { - score = std::abs(desired_ev - (exp_t * exp_gain)); - float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; - score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; - score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; - } - + float score = ci->getExposureScore(desired_ev, exp_t, exp_g_idx, exp_gain, gain_idx); if (score < best_ev_score) { new_exp_t = exp_t; new_exp_g = exp_g_idx; @@ -1071,10 +820,10 @@ void CameraState::set_camera_exposure(float grey_frac) { const float cur_ev_ = cur_ev[buf.cur_frame_data.frame_id % 3]; // Scale target grey between 0.1 and 0.4 depending on lighting conditions - float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); + float new_target_grey = std::clamp(0.4 - 0.3 * log2(1.0 + ci->target_grey_factor*cur_ev_) / log2(6000.0), 0.1, 0.4); float target_grey = (1.0 - k_grey) * target_grey_fraction + k_grey * new_target_grey; - float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, min_ev, max_ev); + float desired_ev = std::clamp(cur_ev_ * target_grey / grey_frac, ci->min_ev, ci->max_ev); float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); @@ -1085,21 +834,21 @@ void CameraState::set_camera_exposure(float grey_frac) { // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes bool enable_dc_gain = dc_gain_enabled; - if (!enable_dc_gain && target_grey < dc_gain_on_grey) { + if (!enable_dc_gain && target_grey < ci->dc_gain_on_grey) { enable_dc_gain = true; - dc_gain_weight = dc_gain_min_weight; - } else if (enable_dc_gain && target_grey > dc_gain_off_grey) { + dc_gain_weight = ci->dc_gain_min_weight; + } else if (enable_dc_gain && target_grey > ci->dc_gain_off_grey) { enable_dc_gain = false; - dc_gain_weight = dc_gain_max_weight; + dc_gain_weight = ci->dc_gain_max_weight; } - if (enable_dc_gain && dc_gain_weight < dc_gain_max_weight) {dc_gain_weight += 1;} - if (!enable_dc_gain && dc_gain_weight > dc_gain_min_weight) {dc_gain_weight -= 1;} + if (enable_dc_gain && dc_gain_weight < ci->dc_gain_max_weight) {dc_gain_weight += 1;} + if (!enable_dc_gain && dc_gain_weight > ci->dc_gain_min_weight) {dc_gain_weight -= 1;} std::string gain_bytes, time_bytes; if (env_ctrl_exp_from_params) { - gain_bytes = Params().get("CameraDebugExpGain"); - time_bytes = Params().get("CameraDebugExpTime"); + gain_bytes = params.get("CameraDebugExpGain"); + time_bytes = params.get("CameraDebugExpTime"); } if (gain_bytes.size() > 0 && time_bytes.size() > 0) { @@ -1113,14 +862,14 @@ void CameraState::set_camera_exposure(float grey_frac) { } else { // Simple brute force optimizer to choose sensor parameters // to reach desired EV - for (int g = std::max((int)analog_gain_min_idx, gain_idx - 1); g <= std::min((int)analog_gain_max_idx, gain_idx + 1); g++) { - float gain = sensor_analog_gains[g] * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + for (int g = std::max((int)ci->analog_gain_min_idx, gain_idx - 1); g <= std::min((int)ci->analog_gain_max_idx, gain_idx + 1); g++) { + float gain = ci->sensor_analog_gains[g] * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); // Compute optimal time for given gain - int t = std::clamp(int(std::round(desired_ev / gain)), exposure_time_min, exposure_time_max); + int t = std::clamp(int(std::round(desired_ev / gain)), ci->exposure_time_min, ci->exposure_time_max); // Only go below recommended gain when absolutely necessary to not overexpose - if (g < analog_gain_rec_idx && t > 20 && g < gain_idx) { + if (g < ci->analog_gain_rec_idx && t > 20 && g < gain_idx) { continue; } @@ -1133,12 +882,12 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = sensor_analog_gains[new_exp_g]; + analog_gain_frac = ci->sensor_analog_gains[new_exp_g]; gain_idx = new_exp_g; exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; - float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); + float gain = analog_gain_frac * (1 + dc_gain_weight * (ci->dc_gain_factor-1) / ci->dc_gain_max_weight); cur_ev[buf.cur_frame_data.frame_id % 3] = exposure_time * gain; exp_lock.unlock(); @@ -1151,59 +900,8 @@ void CameraState::set_camera_exposure(float grey_frac) { } // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", camera_num, 1e-9 * nanos_since_boot(), 1e-9 * buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - buf.cur_frame_data.timestamp_sof)); - if (camera_id == CAMERA_ID_AR0231) { - uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; - struct i2c_random_wr_payload exp_reg_array[] = { - {0x3366, analog_gain_reg}, - {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, - {0x3012, (uint16_t)exposure_time}, - }; - sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); - } else if (camera_id == CAMERA_ID_OX03C10) { - // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD - uint32_t hcg_time = exposure_time; - uint32_t lcg_time = hcg_time; - uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); - uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - - uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; - - struct i2c_random_wr_payload exp_reg_array[] = { - {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, - {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, - {0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, - {0x35c2, vs_time&0xFF}, - - {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, - }; - sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); - } -} - -static float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_reg) { - // See AR0231 Developer Guide - page 36 - float slope = (125.0 - 55.0) / ((float)calib1 - (float)calib2); - float t0 = 55.0 - slope * (float)calib2; - return t0 + slope * (float)data_reg; -} - -static void ar0231_process_registers(MultiCameraState *s, CameraState *c, cereal::FrameData::Builder &framed){ - const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; - uint8_t *data = (uint8_t*)c->buf.cur_camera_buf->addr + c->ci.registers_offset; - - if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0){ - LOGE("unexpected register data found"); - return; - } - - auto registers = c->ar0231_parse_registers(data, {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}); - - uint32_t frame_id = ((uint32_t)registers[0x2000] << 16) | registers[0x2002]; - framed.setFrameIdSensor(frame_id); - - float temp_0 = ar0231_parse_temp_sensor(registers[0x30c6], registers[0x30c8], registers[0x20b0]); - float temp_1 = ar0231_parse_temp_sensor(registers[0x30ca], registers[0x30cc], registers[0x20b2]); - framed.setTemperaturesC({temp_0, temp_1}); + auto exp_reg_array = ci->getExposureRegisters(exposure_time, new_exp_g, dc_gain_enabled); + sensors_i2c(exp_reg_array.data(), exp_reg_array.size(), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, ci->data_word); } static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { @@ -1214,9 +912,7 @@ static void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) framed.setFrameType(cereal::FrameData::FrameType::FRONT); fill_frame_data(framed, c->buf.cur_frame_data, c); - if (c->camera_id == CAMERA_ID_AR0231) { - ar0231_process_registers(s, c, framed); - } + c->ci->processRegisters(c, framed); s->pm->send("driverCameraState", msg); } @@ -1231,10 +927,7 @@ void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { } LOGT(c->buf.cur_frame_data.frame_id, "%s: Image set", c == &s->road_cam ? "RoadCamera" : "WideRoadCamera"); - if (c->camera_id == CAMERA_ID_AR0231) { - ar0231_process_registers(s, c, framed); - } - + c->ci->processRegisters(c, framed); s->pm->send(c == &s->road_cam ? "roadCameraState" : "wideRoadCameraState", msg); const auto [x, y, w, h] = (c == &s->wide_road_cam) ? std::tuple(96, 250, 1734, 524) : std::tuple(96, 160, 1734, 986); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 9e0109ab20..3da5ec207d 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -1,23 +1,23 @@ #pragma once #include -#include +#include #include #include #include "system/camerad/cameras/camera_common.h" #include "system/camerad/cameras/camera_util.h" +#include "system/camerad/sensors/sensor.h" #include "common/params.h" #include "common/util.h" #define FRAME_BUF_COUNT 4 -#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: MultiCameraState *multi_cam_state; - CameraInfo ci; + std::unique_ptr ci; bool enabled; std::mutex exp_lock; @@ -28,32 +28,13 @@ public: int gain_idx; float analog_gain_frac; - int exposure_time_min; - int exposure_time_max; - - float dc_gain_factor; - int dc_gain_min_weight; - int dc_gain_max_weight; - float dc_gain_on_grey; - float dc_gain_off_grey; - - float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; - int analog_gain_min_idx; - int analog_gain_max_idx; - int analog_gain_rec_idx; - int analog_gain_cost_delta; - float analog_gain_cost_low; - float analog_gain_cost_high; - float cur_ev[3]; - float min_ev, max_ev; float best_ev_score; int new_exp_g; int new_exp_t; float measured_grey_fraction; float target_grey_fraction; - float target_grey_factor; unique_fd sensor_fd; unique_fd csiphy_fd; @@ -67,13 +48,11 @@ public: void sensors_start(); void camera_open(MultiCameraState *multi_cam_state, int camera_num, bool enabled); - void camera_set_parameters(); + void sensor_set_parameters(); void camera_map_bufs(MultiCameraState *s); - void camera_init(MultiCameraState *s, VisionIpcServer *v, int camera_id, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); + void camera_init(MultiCameraState *s, VisionIpcServer *v, unsigned int fps, cl_device_id device_id, cl_context ctx, VisionStreamType yuv_type); void camera_close(); - std::map ar0231_parse_registers(uint8_t *data, std::initializer_list addrs); - int32_t session_handle; int32_t sensor_dev_handle; int32_t isp_dev_handle; @@ -89,12 +68,10 @@ public: int frame_id_last; int idx_offset; bool skipped; - int camera_id; CameraBuf buf; MemoryManager mm; -private: void config_isp(int io_mem_handle, int fence, int request_id, int buf0_mem_handle, int buf0_offset); void enqueue_req_multi(int start, int n, bool dp); void enqueue_buffer(int i, bool dp); @@ -102,11 +79,11 @@ private: int sensors_init(); void sensors_poke(int request_id); - void sensors_i2c(struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word); + void sensors_i2c(const struct i2c_random_wr_payload* dat, int len, int op_code, bool data_word); - // Register parsing - std::map> ar0231_register_lut; - std::map> ar0231_build_register_lut(uint8_t *data); +private: + // for debugging + Params params; }; typedef struct MultiCameraState { diff --git a/system/camerad/cameras/camera_util.cc b/system/camerad/cameras/camera_util.cc index 74c81a878a..b2cf4115dc 100644 --- a/system/camerad/cameras/camera_util.cc +++ b/system/camerad/cameras/camera_util.cc @@ -30,10 +30,10 @@ int do_cam_control(int fd, int op_code, void *handle, int size) { std::optional device_acquire(int fd, int32_t session_handle, void *data, uint32_t num_resources) { struct cam_acquire_dev_cmd cmd = { - .session_handle = session_handle, - .handle_type = CAM_HANDLE_USER_POINTER, - .num_resources = (uint32_t)(data ? num_resources : 0), - .resource_hdl = (uint64_t)data, + .session_handle = session_handle, + .handle_type = CAM_HANDLE_USER_POINTER, + .num_resources = (uint32_t)(data ? num_resources : 0), + .resource_hdl = (uint64_t)data, }; int err = do_cam_control(fd, CAM_ACQUIRE_DEV, &cmd, sizeof(cmd)); return err == 0 ? std::make_optional(cmd.dev_handle) : std::nullopt; @@ -41,9 +41,9 @@ std::optional device_acquire(int fd, int32_t session_handle, void *data int device_config(int fd, int32_t session_handle, int32_t dev_handle, uint64_t packet_handle) { struct cam_config_dev_cmd cmd = { - .session_handle = session_handle, - .dev_handle = dev_handle, - .packet_handle = packet_handle, + .session_handle = session_handle, + .dev_handle = dev_handle, + .packet_handle = packet_handle, }; return do_cam_control(fd, CAM_CONFIG_DEV, &cmd, sizeof(cmd)); } diff --git a/system/camerad/cameras/camera_util.h b/system/camerad/cameras/camera_util.h index b36f404c0f..891ce3e793 100644 --- a/system/camerad/cameras/camera_util.h +++ b/system/camerad/cameras/camera_util.h @@ -1,6 +1,6 @@ #pragma once - #include +#include #include #include #include diff --git a/system/camerad/imgproc/conv.cl b/system/camerad/imgproc/conv.cl deleted file mode 100644 index a7115ae76c..0000000000 --- a/system/camerad/imgproc/conv.cl +++ /dev/null @@ -1,110 +0,0 @@ -// const __constant float3 rgb_weights = (0.299, 0.587, 0.114); // opencv rgb2gray weights -// const __constant float3 bgr_weights = (0.114, 0.587, 0.299); // bgr2gray weights - -// convert input rgb image to single channel then conv -__kernel void rgb2gray_conv2d( - const __global uchar * input, - __global short * output, - __constant short * filter, - __local uchar3 * cached -) -{ - const int rowOffset = get_global_id(1) * IMAGE_W; - const int my = get_global_id(0) + rowOffset; - - const int localRowLen = TWICE_HALF_FILTER_SIZE + get_local_size(0); - const int localRowOffset = ( get_local_id(1) + HALF_FILTER_SIZE ) * localRowLen; - const int myLocal = localRowOffset + get_local_id(0) + HALF_FILTER_SIZE; - - // cache local pixels - cached[ myLocal ].x = input[ my * 3 ]; // r - cached[ myLocal ].y = input[ my * 3 + 1]; // g - cached[ myLocal ].z = input[ my * 3 + 2]; // b - - // pad - if ( - get_global_id(0) < HALF_FILTER_SIZE || - get_global_id(0) > IMAGE_W - HALF_FILTER_SIZE - 1 || - get_global_id(1) < HALF_FILTER_SIZE || - get_global_id(1) > IMAGE_H - HALF_FILTER_SIZE - 1 - ) - { - barrier(CLK_LOCAL_MEM_FENCE); - return; - } - else - { - int localColOffset = -1; - int globalColOffset = -1; - - // cache extra - if ( get_local_id(0) < HALF_FILTER_SIZE ) - { - localColOffset = get_local_id(0); - globalColOffset = -HALF_FILTER_SIZE; - - cached[ localRowOffset + get_local_id(0) ].x = input[ my * 3 - HALF_FILTER_SIZE * 3 ]; - cached[ localRowOffset + get_local_id(0) ].y = input[ my * 3 - HALF_FILTER_SIZE * 3 + 1]; - cached[ localRowOffset + get_local_id(0) ].z = input[ my * 3 - HALF_FILTER_SIZE * 3 + 2]; - } - else if ( get_local_id(0) >= get_local_size(0) - HALF_FILTER_SIZE ) - { - localColOffset = get_local_id(0) + TWICE_HALF_FILTER_SIZE; - globalColOffset = HALF_FILTER_SIZE; - - cached[ myLocal + HALF_FILTER_SIZE ].x = input[ my * 3 + HALF_FILTER_SIZE * 3 ]; - cached[ myLocal + HALF_FILTER_SIZE ].y = input[ my * 3 + HALF_FILTER_SIZE * 3 + 1]; - cached[ myLocal + HALF_FILTER_SIZE ].z = input[ my * 3 + HALF_FILTER_SIZE * 3 + 2]; - } - - - if ( get_local_id(1) < HALF_FILTER_SIZE ) - { - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].x = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 ]; - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].y = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + 1]; - cached[ get_local_id(1) * localRowLen + get_local_id(0) + HALF_FILTER_SIZE ].z = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + 2]; - if (localColOffset > 0) - { - cached[ get_local_id(1) * localRowLen + localColOffset ].x = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3]; - cached[ get_local_id(1) * localRowLen + localColOffset ].y = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 1]; - cached[ get_local_id(1) * localRowLen + localColOffset ].z = input[ my * 3 - HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 2]; - } - } - else if ( get_local_id(1) >= get_local_size(1) -HALF_FILTER_SIZE ) - { - int offset = ( get_local_id(1) + TWICE_HALF_FILTER_SIZE ) * localRowLen; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].x = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 ]; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].y = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + 1]; - cached[ offset + get_local_id(0) + HALF_FILTER_SIZE ].z = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + 2]; - if (localColOffset > 0) - { - cached[ offset + localColOffset ].x = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3]; - cached[ offset + localColOffset ].y = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 1]; - cached[ offset + localColOffset ].z = input[ my * 3 + HALF_FILTER_SIZE_IMAGE_W * 3 + globalColOffset * 3 + 2]; - } - } - - // sync - barrier(CLK_LOCAL_MEM_FENCE); - - // perform convolution - int fIndex = 0; - short sum = 0; - - for (int r = -HALF_FILTER_SIZE; r <= HALF_FILTER_SIZE; r++) - { - int curRow = r * localRowLen; - for (int c = -HALF_FILTER_SIZE; c <= HALF_FILTER_SIZE; c++, fIndex++) - { - if (!FLIP_RB){ - // sum += dot(rgb_weights, cached[ myLocal + curRow + c ]) * filter[ fIndex ]; - sum += (cached[ myLocal + curRow + c ].x / 3 + cached[ myLocal + curRow + c ].y / 2 + cached[ myLocal + curRow + c ].z / 9) * filter[ fIndex ]; - } else { - // sum += dot(bgr_weights, cached[ myLocal + curRow + c ]) * filter[ fIndex ]; - sum += (cached[ myLocal + curRow + c ].x / 9 + cached[ myLocal + curRow + c ].y / 2 + cached[ myLocal + curRow + c ].z / 3) * filter[ fIndex ]; - } - } - } - output[my] = sum; - } -} \ No newline at end of file diff --git a/system/camerad/imgproc/pool.cl b/system/camerad/imgproc/pool.cl deleted file mode 100644 index d674b5f363..0000000000 --- a/system/camerad/imgproc/pool.cl +++ /dev/null @@ -1,34 +0,0 @@ -// calculate variance in each subregion -__kernel void var_pool( - const __global char * input, - __global ushort * output // should not be larger than 128*128 so uint16 -) -{ - const int xidx = get_global_id(0) + ROI_X_MIN; - const int yidx = get_global_id(1) + ROI_Y_MIN; - - const int size = X_PITCH * Y_PITCH; - - float fsum = 0; - char mean, max; - - for (int i = 0; i < size; i++) { - int x_offset = i % X_PITCH; - int y_offset = i / X_PITCH; - fsum += input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]; - max = input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]>max ? input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X]:max; - } - - mean = convert_char_rte(fsum / size); - - float fvar = 0; - for (int i = 0; i < size; i++) { - int x_offset = i % X_PITCH; - int y_offset = i / X_PITCH; - fvar += (input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X] - mean) * (input[xidx*X_PITCH + yidx*Y_PITCH*FULL_STRIDE_X + x_offset + y_offset*FULL_STRIDE_X] - mean); - } - - fvar = fvar / size; - - output[(xidx-ROI_X_MIN)+(yidx-ROI_Y_MIN)*(ROI_X_MAX-ROI_X_MIN+1)] = convert_ushort_rte(5 * fvar + convert_float_rte(max)); -} \ No newline at end of file diff --git a/system/camerad/imgproc/utils.cc b/system/camerad/imgproc/utils.cc deleted file mode 100644 index a7bbeb9e86..0000000000 --- a/system/camerad/imgproc/utils.cc +++ /dev/null @@ -1,106 +0,0 @@ -#include "system/camerad/imgproc/utils.h" - -#include -#include -#include -#include -#include - -const int16_t lapl_conv_krnl[9] = {0, 1, 0, - 1, -4, 1, - 0, 1, 0}; - -// calculate score based on laplacians in one area -uint16_t get_lapmap_one(const int16_t *lap, int x_pitch, int y_pitch) { - const int size = x_pitch * y_pitch; - // avg and max of roi - int16_t max = 0; - int sum = 0; - for (int i = 0; i < size; ++i) { - const int16_t v = lap[i]; - sum += v; - if (v > max) max = v; - } - - const int16_t mean = sum / size; - - // var of roi - int var = 0; - for (int i = 0; i < size; ++i) { - var += std::pow(lap[i] - mean, 2); - } - - const float fvar = (float)var / size; - return std::min(5 * fvar + max, (float)65535); -} - -bool is_blur(const uint16_t *lapmap, const size_t size) { - float bad_sum = 0; - for (int i = 0; i < size; i++) { - if (lapmap[i] < LM_THRESH) { - bad_sum += 1 / (float)size; - } - } - return (bad_sum > LM_PREC_THRESH); -} - -static cl_program build_conv_program(cl_device_id device_id, cl_context context, int image_w, int image_h, int filter_size) { - char args[4096]; - snprintf(args, sizeof(args), - "-cl-fast-relaxed-math -cl-denorms-are-zero " - "-DIMAGE_W=%d -DIMAGE_H=%d -DFLIP_RB=%d " - "-DFILTER_SIZE=%d -DHALF_FILTER_SIZE=%d -DTWICE_HALF_FILTER_SIZE=%d -DHALF_FILTER_SIZE_IMAGE_W=%d", - image_w, image_h, 1, - filter_size, filter_size/2, (filter_size/2)*2, (filter_size/2)*image_w); - return cl_program_from_file(context, device_id, "imgproc/conv.cl", args); -} - -LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size) - : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), rgb_stride(rgb_stride), - roi_buf(width * height * 3), result_buf(width * height) { - - prg = build_conv_program(device_id, ctx, width, height, filter_size); - krnl = CL_CHECK_ERR(clCreateKernel(prg, "rgb2gray_conv2d", &err)); - // TODO: Removed CL_MEM_SVM_FINE_GRAIN_BUFFER, confirm it doesn't matter - roi_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, roi_buf.size() * sizeof(roi_buf[0]), NULL, &err)); - result_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_WRITE, result_buf.size() * sizeof(result_buf[0]), NULL, &err)); - filter_cl = CL_CHECK_ERR(clCreateBuffer(ctx, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, - 9 * sizeof(int16_t), (void *)&lapl_conv_krnl, &err)); -} - -LapConv::~LapConv() { - CL_CHECK(clReleaseMemObject(roi_cl)); - CL_CHECK(clReleaseMemObject(result_cl)); - CL_CHECK(clReleaseMemObject(filter_cl)); - CL_CHECK(clReleaseKernel(krnl)); - CL_CHECK(clReleaseProgram(prg)); -} - -uint16_t LapConv::Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id) { - // sharpness scores - const int x_offset = ROI_X_MIN + roi_id % (ROI_X_MAX - ROI_X_MIN + 1); - const int y_offset = ROI_Y_MIN + roi_id / (ROI_X_MAX - ROI_X_MIN + 1); - - const uint8_t *rgb_offset = rgb_buf + y_offset * height * rgb_stride + x_offset * width * 3; - for (int i = 0; i < height; ++i) { - memcpy(&roi_buf[i * width * 3], &rgb_offset[i * rgb_stride], width * 3); - } - - constexpr int local_mem_size = (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (3 * sizeof(uint8_t)); - const size_t global_work_size[] = {(size_t)width, (size_t)height}; - const size_t local_work_size[] = {CONV_LOCAL_WORKSIZE, CONV_LOCAL_WORKSIZE}; - - CL_CHECK(clEnqueueWriteBuffer(q, roi_cl, CL_TRUE, 0, roi_buf.size() * sizeof(roi_buf[0]), roi_buf.data(), 0, 0, 0)); - CL_CHECK(clSetKernelArg(krnl, 0, sizeof(cl_mem), (void *)&roi_cl)); - CL_CHECK(clSetKernelArg(krnl, 1, sizeof(cl_mem), (void *)&result_cl)); - CL_CHECK(clSetKernelArg(krnl, 2, sizeof(cl_mem), (void *)&filter_cl)); - CL_CHECK(clSetKernelArg(krnl, 3, local_mem_size, 0)); - cl_event conv_event; - CL_CHECK(clEnqueueNDRangeKernel(q, krnl, 2, NULL, global_work_size, local_work_size, 0, 0, &conv_event)); - CL_CHECK(clWaitForEvents(1, &conv_event)); - CL_CHECK(clReleaseEvent(conv_event)); - CL_CHECK(clEnqueueReadBuffer(q, result_cl, CL_TRUE, 0, - result_buf.size() * sizeof(result_buf[0]), result_buf.data(), 0, 0, 0)); - - return get_lapmap_one(result_buf.data(), width, height); -} diff --git a/system/camerad/imgproc/utils.h b/system/camerad/imgproc/utils.h deleted file mode 100644 index 94323b15c5..0000000000 --- a/system/camerad/imgproc/utils.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "common/clutil.h" - -#define NUM_SEGMENTS_X 8 -#define NUM_SEGMENTS_Y 6 - -#define ROI_X_MIN 1 -#define ROI_X_MAX 6 -#define ROI_Y_MIN 2 -#define ROI_Y_MAX 3 - -#define LM_THRESH 120 -#define LM_PREC_THRESH 0.9 // 90 perc is blur -#define CONV_LOCAL_WORKSIZE 16 - -class LapConv { -public: - LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size); - ~LapConv(); - uint16_t Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id); - -private: - cl_mem roi_cl, result_cl, filter_cl; - cl_program prg; - cl_kernel krnl; - const int width, height; - const int rgb_stride; - std::vector roi_buf; - std::vector result_buf; -}; - -bool is_blur(const uint16_t *lapmap, const size_t size); diff --git a/system/camerad/main.cc b/system/camerad/main.cc index 35a3329f30..19de21c9bb 100644 --- a/system/camerad/main.cc +++ b/system/camerad/main.cc @@ -8,7 +8,7 @@ int main(int argc, char *argv[]) { if (Hardware::PC()) { - printf("camerad is not meant to run on PC\n"); + printf("exiting, camerad is not meant to run on PC\n"); return 0; } diff --git a/system/camerad/sensors/ar0231.cc b/system/camerad/sensors/ar0231.cc new file mode 100644 index 0000000000..9a657982a8 --- /dev/null +++ b/system/camerad/sensors/ar0231.cc @@ -0,0 +1,169 @@ +#include + +#include "system/camerad/cameras/camera_common.h" +#include "system/camerad/cameras/camera_qcom2.h" +#include "system/camerad/sensors/sensor.h" + +namespace { + +const size_t AR0231_REGISTERS_HEIGHT = 2; +// TODO: this extra height is universal and doesn't apply per camera +const size_t AR0231_STATS_HEIGHT = 2 + 8; + +const float sensor_analog_gains_AR0231[] = { + 1.0 / 8.0, 2.0 / 8.0, 2.0 / 7.0, 3.0 / 7.0, // 0, 1, 2, 3 + 3.0 / 6.0, 4.0 / 6.0, 4.0 / 5.0, 5.0 / 5.0, // 4, 5, 6, 7 + 5.0 / 4.0, 6.0 / 4.0, 6.0 / 3.0, 7.0 / 3.0, // 8, 9, 10, 11 + 7.0 / 2.0, 8.0 / 2.0, 8.0 / 1.0}; // 12, 13, 14, 15 = bypass + +std::map> ar0231_build_register_lut(CameraState *c, uint8_t *data) { + // This function builds a lookup table from register address, to a pair of indices in the + // buffer where to read this address. The buffer contains padding bytes, + // as well as markers to indicate the type of the next byte. + // + // 0xAA is used to indicate the MSB of the address, 0xA5 for the LSB of the address. + // Every byte of data (MSB and LSB) is preceded by 0x5A. Specifying an address is optional + // for contiguous ranges. See page 27-29 of the AR0231 Developer guide for more information. + + int max_i[] = {1828 / 2 * 3, 1500 / 2 * 3}; + auto get_next_idx = [](int cur_idx) { + return (cur_idx % 3 == 1) ? cur_idx + 2 : cur_idx + 1; // Every third byte is padding + }; + + std::map> registers; + for (int register_row = 0; register_row < 2; register_row++) { + uint8_t *registers_raw = data + c->ci->frame_stride * register_row; + assert(registers_raw[0] == 0x0a); // Start of line + + int value_tag_count = 0; + int first_val_idx = 0; + uint16_t cur_addr = 0; + + for (int i = 1; i <= max_i[register_row]; i = get_next_idx(get_next_idx(i))) { + int val_idx = get_next_idx(i); + + uint8_t tag = registers_raw[i]; + uint16_t val = registers_raw[val_idx]; + + if (tag == 0xAA) { // Register MSB tag + cur_addr = val << 8; + } else if (tag == 0xA5) { // Register LSB tag + cur_addr |= val; + cur_addr -= 2; // Next value tag will increment address again + } else if (tag == 0x5A) { // Value tag + + // First tag + if (value_tag_count % 2 == 0) { + cur_addr += 2; + first_val_idx = val_idx; + } else { + registers[cur_addr] = std::make_pair(first_val_idx + c->ci->frame_stride * register_row, val_idx + c->ci->frame_stride * register_row); + } + + value_tag_count++; + } + } + } + return registers; +} + +float ar0231_parse_temp_sensor(uint16_t calib1, uint16_t calib2, uint16_t data_reg) { + // See AR0231 Developer Guide - page 36 + float slope = (125.0 - 55.0) / ((float)calib1 - (float)calib2); + float t0 = 55.0 - slope * (float)calib2; + return t0 + slope * (float)data_reg; +} + +} // namespace + +AR0231::AR0231() { + image_sensor = cereal::FrameData::ImageSensor::AR0231; + data_word = true; + frame_width = FRAME_WIDTH; + frame_height = FRAME_HEIGHT; + frame_stride = FRAME_STRIDE; + extra_height = AR0231_REGISTERS_HEIGHT + AR0231_STATS_HEIGHT; + + registers_offset = 0; + frame_offset = AR0231_REGISTERS_HEIGHT; + stats_offset = AR0231_REGISTERS_HEIGHT + FRAME_HEIGHT; + + start_reg_array.assign(std::begin(start_reg_array_ar0231), std::end(start_reg_array_ar0231)); + init_reg_array.assign(std::begin(init_array_ar0231), std::end(init_array_ar0231)); + probe_reg_addr = 0x3000; + probe_expected_data = 0x354; + in_port_info_dt = 0x12; // Changing stats to 0x2C doesn't work, so change pixels to 0x12 instead + power_config_val_low = 19200000; //Hz + + dc_gain_factor = 2.5; + dc_gain_min_weight = 0; + dc_gain_max_weight = 1; + dc_gain_on_grey = 0.2; + dc_gain_off_grey = 0.3; + exposure_time_min = 2; // with HDR, fastest ss + exposure_time_max = 0x0855; // with HDR, slowest ss, 40ms + analog_gain_min_idx = 0x1; // 0.25x + analog_gain_rec_idx = 0x6; // 0.8x + analog_gain_max_idx = 0xD; // 4.0x + analog_gain_cost_delta = 0; + analog_gain_cost_low = 0.1; + analog_gain_cost_high = 5.0; + for (int i = 0; i <= analog_gain_max_idx; i++) { + sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; + } + min_ev = exposure_time_min * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; + target_grey_factor = 1.0; +} + +void AR0231::processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const { + const uint8_t expected_preamble[] = {0x0a, 0xaa, 0x55, 0x20, 0xa5, 0x55}; + uint8_t *data = (uint8_t *)c->buf.cur_camera_buf->addr + c->ci->registers_offset; + + if (memcmp(data, expected_preamble, std::size(expected_preamble)) != 0) { + LOGE("unexpected register data found"); + return; + } + + if (ar0231_register_lut.empty()) { + ar0231_register_lut = ar0231_build_register_lut(c, data); + } + std::map registers; + for (uint16_t addr : {0x2000, 0x2002, 0x20b0, 0x20b2, 0x30c6, 0x30c8, 0x30ca, 0x30cc}) { + auto offset = ar0231_register_lut[addr]; + registers[addr] = ((uint16_t)data[offset.first] << 8) | data[offset.second]; + } + + uint32_t frame_id = ((uint32_t)registers[0x2000] << 16) | registers[0x2002]; + framed.setFrameIdSensor(frame_id); + + float temp_0 = ar0231_parse_temp_sensor(registers[0x30c6], registers[0x30c8], registers[0x20b0]); + float temp_1 = ar0231_parse_temp_sensor(registers[0x30ca], registers[0x30cc], registers[0x20b2]); + framed.setTemperaturesC({temp_0, temp_1}); +} + + +std::vector AR0231::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { + uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; + return { + {0x3366, analog_gain_reg}, + {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, + {0x3012, (uint16_t)exposure_time}, + }; +} + +int AR0231::getSlaveAddress(int port) const { + assert(port >= 0 && port <= 2); + return (int[]){0x20, 0x30, 0x20}[port]; +} + +float AR0231::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const { + // Cost of ev diff + float score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; + // Cost of absolute gain + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + // Cost of changing gain + score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; + return score; +} diff --git a/system/camerad/sensors/ar0231_registers.h b/system/camerad/sensors/ar0231_registers.h new file mode 100644 index 0000000000..6c4c251e8e --- /dev/null +++ b/system/camerad/sensors/ar0231_registers.h @@ -0,0 +1,119 @@ +#pragma once + +const struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; +const struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; + +const struct i2c_random_wr_payload init_array_ar0231[] = { + {0x301A, 0x0018}, // RESET_REGISTER + + // CLOCK Settings + // input clock is 19.2 / 2 * 0x37 = 528 MHz + // pixclk is 528 / 6 = 88 MHz + // full roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*FRAME_LENGTH_LINES)) = 39.99 ms + // img roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*Y_OUTPUT_CONTROL)) = 22.85 ms + {0x302A, 0x0006}, // VT_PIX_CLK_DIV + {0x302C, 0x0001}, // VT_SYS_CLK_DIV + {0x302E, 0x0002}, // PRE_PLL_CLK_DIV + {0x3030, 0x0037}, // PLL_MULTIPLIER + {0x3036, 0x000C}, // OP_PIX_CLK_DIV + {0x3038, 0x0001}, // OP_SYS_CLK_DIV + + // FORMAT + {0x3040, 0xC000}, // READ_MODE + {0x3004, 0x0000}, // X_ADDR_START_ + {0x3008, 0x0787}, // X_ADDR_END_ + {0x3002, 0x0000}, // Y_ADDR_START_ + {0x3006, 0x04B7}, // Y_ADDR_END_ + {0x3032, 0x0000}, // SCALING_MODE + {0x30A2, 0x0001}, // X_ODD_INC_ + {0x30A6, 0x0001}, // Y_ODD_INC_ + {0x3402, 0x0788}, // X_OUTPUT_CONTROL + {0x3404, 0x04B8}, // Y_OUTPUT_CONTROL + {0x3064, 0x1982}, // SMIA_TEST + {0x30BA, 0x11F2}, // DIGITAL_CTRL + + // Enable external trigger and disable GPIO outputs + {0x30CE, 0x0120}, // SLAVE_SH_SYNC_MODE | FRAME_START_MODE + {0x340A, 0xE0}, // GPIO3_INPUT_DISABLE | GPIO2_INPUT_DISABLE | GPIO1_INPUT_DISABLE + {0x340C, 0x802}, // GPIO_HIDRV_EN | GPIO0_ISEL=2 + + // Readout timing + {0x300C, 0x0672}, // LINE_LENGTH_PCK (valid for 3-exposure HDR) + {0x300A, 0x0855}, // FRAME_LENGTH_LINES + {0x3042, 0x0000}, // EXTRA_DELAY + + // Readout Settings + {0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI + {0x31AC, 0x0C0C}, // DATA_FORMAT_BITS, 12 -> 12 + {0x3342, 0x1212}, // MIPI_F1_PDT_EDT + {0x3346, 0x1212}, // MIPI_F2_PDT_EDT + {0x334A, 0x1212}, // MIPI_F3_PDT_EDT + {0x334E, 0x1212}, // MIPI_F4_PDT_EDT + {0x3344, 0x0011}, // MIPI_F1_VDT_VC + {0x3348, 0x0111}, // MIPI_F2_VDT_VC + {0x334C, 0x0211}, // MIPI_F3_VDT_VC + {0x3350, 0x0311}, // MIPI_F4_VDT_VC + {0x31B0, 0x0053}, // FRAME_PREAMBLE + {0x31B2, 0x003B}, // LINE_PREAMBLE + {0x301A, 0x001C}, // RESET_REGISTER + + // Noise Corrections + {0x3092, 0x0C24}, // ROW_NOISE_CONTROL + {0x337A, 0x0C80}, // DBLC_SCALE0 + {0x3370, 0x03B1}, // DBLC + {0x3044, 0x0400}, // DARK_CONTROL + + // Enable temperature sensor + {0x30B4, 0x0007}, // TEMPSENS0_CTRL_REG + {0x30B8, 0x0007}, // TEMPSENS1_CTRL_REG + + // Enable dead pixel correction using + // the 1D line correction scheme + {0x31E0, 0x0003}, + + // HDR Settings + {0x3082, 0x0004}, // OPERATION_MODE_CTRL + {0x3238, 0x0444}, // EXPOSURE_RATIO + + {0x1008, 0x0361}, // FINE_INTEGRATION_TIME_MIN + {0x100C, 0x0589}, // FINE_INTEGRATION_TIME2_MIN + {0x100E, 0x07B1}, // FINE_INTEGRATION_TIME3_MIN + {0x1010, 0x0139}, // FINE_INTEGRATION_TIME4_MIN + + // TODO: do these have to be lower than LINE_LENGTH_PCK? + {0x3014, 0x08CB}, // FINE_INTEGRATION_TIME_ + {0x321E, 0x0894}, // FINE_INTEGRATION_TIME2 + + {0x31D0, 0x0000}, // COMPANDING, no good in 10 bit? + {0x33DA, 0x0000}, // COMPANDING + {0x318E, 0x0200}, // PRE_HDR_GAIN_EN + + // DLO Settings + {0x3100, 0x4000}, // DLO_CONTROL0 + {0x3280, 0x0CCC}, // T1 G1 + {0x3282, 0x0CCC}, // T1 R + {0x3284, 0x0CCC}, // T1 B + {0x3286, 0x0CCC}, // T1 G2 + {0x3288, 0x0FA0}, // T2 G1 + {0x328A, 0x0FA0}, // T2 R + {0x328C, 0x0FA0}, // T2 B + {0x328E, 0x0FA0}, // T2 G2 + + // Initial Gains + {0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_ + {0x3366, 0xFF77}, // ANALOG_GAIN (1x) + + {0x3060, 0x3333}, // ANALOG_COLOR_GAIN + + {0x3362, 0x0000}, // DC GAIN + + {0x305A, 0x00F8}, // red gain + {0x3058, 0x0122}, // blue gain + {0x3056, 0x009A}, // g1 gain + {0x305C, 0x009A}, // g2 gain + + {0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_ + + // Initial Integration Time + {0x3012, 0x0005}, +}; diff --git a/system/camerad/sensors/ox03c10.cc b/system/camerad/sensors/ox03c10.cc new file mode 100644 index 0000000000..2bad50cff9 --- /dev/null +++ b/system/camerad/sensors/ox03c10.cc @@ -0,0 +1,93 @@ +#include "system/camerad/sensors/sensor.h" + +namespace { + +const float sensor_analog_gains_OX03C10[] = { + 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, + 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, + 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, + 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; + +const uint32_t ox03c10_analog_gains_reg[] = { + 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, + 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, + 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, + 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, + 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; + +const uint32_t VS_TIME_MIN_OX03C10 = 1; +const uint32_t VS_TIME_MAX_OX03C10 = 34; // vs < 35 + +} // namespace + +OX03C10::OX03C10() { + image_sensor = cereal::FrameData::ImageSensor::OX03C10; + data_word = false; + frame_width = FRAME_WIDTH; + frame_height = FRAME_HEIGHT; + frame_stride = FRAME_STRIDE; // (0xa80*12//8) + extra_height = 16; // top 2 + bot 14 + frame_offset = 2; + + start_reg_array.assign(std::begin(start_reg_array_ox03c10), std::end(start_reg_array_ox03c10)); + init_reg_array.assign(std::begin(init_array_ox03c10), std::end(init_array_ox03c10)); + probe_reg_addr = 0x300a; + probe_expected_data = 0x5803; + in_port_info_dt = 0x2c; // one is 0x2a, two are 0x2b + power_config_val_low = 24000000; //Hz + + dc_gain_factor = 7.32; + dc_gain_min_weight = 1; // always on is fine + dc_gain_max_weight = 1; + dc_gain_on_grey = 0.9; + dc_gain_off_grey = 1.0; + exposure_time_min = 2; // 1x + exposure_time_max = 2016; + analog_gain_min_idx = 0x0; + analog_gain_rec_idx = 0x0; // 1x + analog_gain_max_idx = 0x36; + analog_gain_cost_delta = -1; + analog_gain_cost_low = 0.4; + analog_gain_cost_high = 6.4; + for (int i = 0; i <= analog_gain_max_idx; i++) { + sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; + } + min_ev = (exposure_time_min + VS_TIME_MIN_OX03C10) * sensor_analog_gains[analog_gain_min_idx]; + max_ev = exposure_time_max * dc_gain_factor * sensor_analog_gains[analog_gain_max_idx]; + target_grey_factor = 0.01; +} + +std::vector OX03C10::getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { + // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD + uint32_t hcg_time = exposure_time; + uint32_t lcg_time = hcg_time; + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); + + uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; + + return { + {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, + {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, + {0x3541, spd_time>>8}, {0x3542, spd_time&0xFF}, + {0x35c2, vs_time&0xFF}, + + {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, + }; +} + +int OX03C10::getSlaveAddress(int port) const { + assert(port >= 0 && port <= 2); + return (int[]){0x6C, 0x20, 0x6C}[port]; +} + +float OX03C10::getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const { + float score = std::abs(desired_ev - (exp_t * exp_gain)); + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * + std::abs(exp_g_idx - gain_idx) * 5.0; + return score; +} diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/sensors/ox03c10_registers.h similarity index 82% rename from system/camerad/cameras/sensor2_i2c.h rename to system/camerad/sensors/ox03c10_registers.h index 9170c5183a..575a2cb934 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/sensors/ox03c10_registers.h @@ -1,9 +1,9 @@ -struct i2c_random_wr_payload start_reg_array_ar0231[] = {{0x301A, 0x91C}}; -struct i2c_random_wr_payload stop_reg_array_ar0231[] = {{0x301A, 0x918}}; -struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; -struct i2c_random_wr_payload stop_reg_array_ox03c10[] = {{0x100, 0}}; +#pragma once -struct i2c_random_wr_payload init_array_ox03c10[] = { +const struct i2c_random_wr_payload start_reg_array_ox03c10[] = {{0x100, 1}}; +const struct i2c_random_wr_payload stop_reg_array_ox03c10[] = {{0x100, 0}}; + +const struct i2c_random_wr_payload init_array_ox03c10[] = { {0x103, 1}, {0x107, 1}, @@ -759,118 +759,3 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3548, 0x0F}, {0x3549, 0x00}, {0x35c1, 0x00}, }; - -struct i2c_random_wr_payload init_array_ar0231[] = { - {0x301A, 0x0018}, // RESET_REGISTER - - // CLOCK Settings - // input clock is 19.2 / 2 * 0x37 = 528 MHz - // pixclk is 528 / 6 = 88 MHz - // full roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*FRAME_LENGTH_LINES)) = 39.99 ms - // img roll time is 1000/(PIXCLK/(LINE_LENGTH_PCK*Y_OUTPUT_CONTROL)) = 22.85 ms - {0x302A, 0x0006}, // VT_PIX_CLK_DIV - {0x302C, 0x0001}, // VT_SYS_CLK_DIV - {0x302E, 0x0002}, // PRE_PLL_CLK_DIV - {0x3030, 0x0037}, // PLL_MULTIPLIER - {0x3036, 0x000C}, // OP_PIX_CLK_DIV - {0x3038, 0x0001}, // OP_SYS_CLK_DIV - - // FORMAT - {0x3040, 0xC000}, // READ_MODE - {0x3004, 0x0000}, // X_ADDR_START_ - {0x3008, 0x0787}, // X_ADDR_END_ - {0x3002, 0x0000}, // Y_ADDR_START_ - {0x3006, 0x04B7}, // Y_ADDR_END_ - {0x3032, 0x0000}, // SCALING_MODE - {0x30A2, 0x0001}, // X_ODD_INC_ - {0x30A6, 0x0001}, // Y_ODD_INC_ - {0x3402, 0x0788}, // X_OUTPUT_CONTROL - {0x3404, 0x04B8}, // Y_OUTPUT_CONTROL - {0x3064, 0x1982}, // SMIA_TEST - {0x30BA, 0x11F2}, // DIGITAL_CTRL - - // Enable external trigger and disable GPIO outputs - {0x30CE, 0x0120}, // SLAVE_SH_SYNC_MODE | FRAME_START_MODE - {0x340A, 0xE0}, // GPIO3_INPUT_DISABLE | GPIO2_INPUT_DISABLE | GPIO1_INPUT_DISABLE - {0x340C, 0x802}, // GPIO_HIDRV_EN | GPIO0_ISEL=2 - - // Readout timing - {0x300C, 0x0672}, // LINE_LENGTH_PCK (valid for 3-exposure HDR) - {0x300A, 0x0855}, // FRAME_LENGTH_LINES - {0x3042, 0x0000}, // EXTRA_DELAY - - // Readout Settings - {0x31AE, 0x0204}, // SERIAL_FORMAT, 4-lane MIPI - {0x31AC, 0x0C0C}, // DATA_FORMAT_BITS, 12 -> 12 - {0x3342, 0x1212}, // MIPI_F1_PDT_EDT - {0x3346, 0x1212}, // MIPI_F2_PDT_EDT - {0x334A, 0x1212}, // MIPI_F3_PDT_EDT - {0x334E, 0x1212}, // MIPI_F4_PDT_EDT - {0x3344, 0x0011}, // MIPI_F1_VDT_VC - {0x3348, 0x0111}, // MIPI_F2_VDT_VC - {0x334C, 0x0211}, // MIPI_F3_VDT_VC - {0x3350, 0x0311}, // MIPI_F4_VDT_VC - {0x31B0, 0x0053}, // FRAME_PREAMBLE - {0x31B2, 0x003B}, // LINE_PREAMBLE - {0x301A, 0x001C}, // RESET_REGISTER - - // Noise Corrections - {0x3092, 0x0C24}, // ROW_NOISE_CONTROL - {0x337A, 0x0C80}, // DBLC_SCALE0 - {0x3370, 0x03B1}, // DBLC - {0x3044, 0x0400}, // DARK_CONTROL - - // Enable temperature sensor - {0x30B4, 0x0007}, // TEMPSENS0_CTRL_REG - {0x30B8, 0x0007}, // TEMPSENS1_CTRL_REG - - // Enable dead pixel correction using - // the 1D line correction scheme - {0x31E0, 0x0003}, - - // HDR Settings - {0x3082, 0x0004}, // OPERATION_MODE_CTRL - {0x3238, 0x0444}, // EXPOSURE_RATIO - - {0x1008, 0x0361}, // FINE_INTEGRATION_TIME_MIN - {0x100C, 0x0589}, // FINE_INTEGRATION_TIME2_MIN - {0x100E, 0x07B1}, // FINE_INTEGRATION_TIME3_MIN - {0x1010, 0x0139}, // FINE_INTEGRATION_TIME4_MIN - - // TODO: do these have to be lower than LINE_LENGTH_PCK? - {0x3014, 0x08CB}, // FINE_INTEGRATION_TIME_ - {0x321E, 0x0894}, // FINE_INTEGRATION_TIME2 - - {0x31D0, 0x0000}, // COMPANDING, no good in 10 bit? - {0x33DA, 0x0000}, // COMPANDING - {0x318E, 0x0200}, // PRE_HDR_GAIN_EN - - // DLO Settings - {0x3100, 0x4000}, // DLO_CONTROL0 - {0x3280, 0x0CCC}, // T1 G1 - {0x3282, 0x0CCC}, // T1 R - {0x3284, 0x0CCC}, // T1 B - {0x3286, 0x0CCC}, // T1 G2 - {0x3288, 0x0FA0}, // T2 G1 - {0x328A, 0x0FA0}, // T2 R - {0x328C, 0x0FA0}, // T2 B - {0x328E, 0x0FA0}, // T2 G2 - - // Initial Gains - {0x3022, 0x0001}, // GROUPED_PARAMETER_HOLD_ - {0x3366, 0xFF77}, // ANALOG_GAIN (1x) - - {0x3060, 0x3333}, // ANALOG_COLOR_GAIN - - {0x3362, 0x0000}, // DC GAIN - - {0x305A, 0x00F8}, // red gain - {0x3058, 0x0122}, // blue gain - {0x3056, 0x009A}, // g1 gain - {0x305C, 0x009A}, // g2 gain - - {0x3022, 0x0000}, // GROUPED_PARAMETER_HOLD_ - - // Initial Integration Time - {0x3012, 0x0005}, -}; diff --git a/system/camerad/sensors/sensor.h b/system/camerad/sensors/sensor.h new file mode 100644 index 0000000000..06d3a43a21 --- /dev/null +++ b/system/camerad/sensors/sensor.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "media/cam_sensor.h" +#include "system/camerad/cameras/camera_common.h" +#include "system/camerad/sensors/ar0231_registers.h" +#include "system/camerad/sensors/ox03c10_registers.h" + +#define ANALOG_GAIN_MAX_CNT 55 +const size_t FRAME_WIDTH = 1928; +const size_t FRAME_HEIGHT = 1208; +const size_t FRAME_STRIDE = 2896; // for 12 bit output. 1928 * 12 / 8 + 4 (alignment) + +class SensorInfo { +public: + SensorInfo() = default; + virtual std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const { return {}; } + virtual float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const {return 0; } + virtual int getSlaveAddress(int port) const { assert(0); } + virtual void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const {} + + cereal::FrameData::ImageSensor image_sensor = cereal::FrameData::ImageSensor::UNKNOWN; + uint32_t frame_width, frame_height; + uint32_t frame_stride; + uint32_t frame_offset = 0; + uint32_t extra_height = 0; + int registers_offset = -1; + int stats_offset = -1; + + int exposure_time_min; + int exposure_time_max; + + float dc_gain_factor; + int dc_gain_min_weight; + int dc_gain_max_weight; + float dc_gain_on_grey; + float dc_gain_off_grey; + + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; + int analog_gain_min_idx; + int analog_gain_max_idx; + int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; + float target_grey_factor; + float min_ev; + float max_ev; + + bool data_word; + uint32_t probe_reg_addr; + uint32_t probe_expected_data; + std::vector start_reg_array; + std::vector init_reg_array; + uint32_t in_port_info_dt; + uint32_t power_config_val_low; +}; + +class AR0231 : public SensorInfo { +public: + AR0231(); + std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; + float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; + int getSlaveAddress(int port) const override; + void processRegisters(CameraState *c, cereal::FrameData::Builder &framed) const override; + +private: + mutable std::map> ar0231_register_lut; +}; + +class OX03C10 : public SensorInfo { +public: + OX03C10(); + std::vector getExposureRegisters(int exposure_time, int new_exp_g, bool dc_gain_enabled) const override; + float getExposureScore(float desired_ev, int exp_t, int exp_g_idx, float exp_gain, int gain_idx) const override; + int getSlaveAddress(int port) const override; +}; diff --git a/system/camerad/snapshot/snapshot.py b/system/camerad/snapshot/snapshot.py index ede97d1a79..8c1b6084c7 100755 --- a/system/camerad/snapshot/snapshot.py +++ b/system/camerad/snapshot/snapshot.py @@ -13,7 +13,6 @@ from openpilot.system.hardware import PC from openpilot.selfdrive.controls.lib.alertmanager import set_offroad_alert from openpilot.selfdrive.manager.process_config import managed_processes -LM_THRESH = 120 # defined in system/camerad/imgproc/utils.h VISION_STREAMS = { "roadCameraState": VisionStreamType.VISION_STREAM_ROAD, diff --git a/system/camerad/test/get_thumbnails_for_segment.py b/system/camerad/test/get_thumbnails_for_segment.py index 97667ede86..43f543f1bd 100755 --- a/system/camerad/test/get_thumbnails_for_segment.py +++ b/system/camerad/test/get_thumbnails_for_segment.py @@ -4,7 +4,6 @@ import os from tqdm import tqdm -from openpilot.common.file_helpers import mkdirs_exists_ok from openpilot.tools.lib.logreader import LogReader from openpilot.tools.lib.route import Route @@ -17,7 +16,7 @@ if __name__ == "__main__": args = parser.parse_args() out_path = os.path.join("jpegs", f"{args.route.replace('|', '_')}_{args.segment}") - mkdirs_exists_ok(out_path) + os.makedirs(out_path, exist_ok=True) r = Route(args.route) path = r.log_paths()[args.segment] or r.qlog_paths()[args.segment] diff --git a/system/hardware/base.h b/system/hardware/base.h index 890a743ea0..dc2282a93a 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -29,7 +29,6 @@ public: static void poweroff() {} static void set_brightness(int percent) {} static void set_display_power(bool on) {} - static void set_volume(float volume) {} static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} diff --git a/system/hardware/hw.py b/system/hardware/hw.py index 119ca48c86..6c3d90345c 100644 --- a/system/hardware/hw.py +++ b/system/hardware/hw.py @@ -33,3 +33,24 @@ class Paths: if os.environ.get('COMMA_CACHE', False): return os.environ['COMMA_CACHE'] return "/tmp/comma_download_cache" + os.environ.get("OPENPILOT_PREFIX", "") + "/" + + @staticmethod + def persist_root() -> str: + if PC: + return os.path.join(Paths.comma_home(), "persist") + else: + return "/persist/" + + @staticmethod + def stats_root() -> str: + if PC: + return str(Path(Paths.comma_home()) / "stats") + else: + return "/data/stats/" + + @staticmethod + def config_root() -> str: + if PC: + return Paths.comma_home() + else: + return "/tmp/.comma" \ No newline at end of file diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h index 189adbbbee..5dea184ca6 100644 --- a/system/hardware/pc/hardware.h +++ b/system/hardware/pc/hardware.h @@ -13,14 +13,6 @@ public: static bool TICI() { return util::getenv("TICI", 0) == 1; } static bool AGNOS() { return util::getenv("TICI", 0) == 1; } - static void set_volume(float volume) { - volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); - - char volume_str[6]; - snprintf(volume_str, sizeof(volume_str), "%.3f", volume); - std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); - } - static void config_cpu_rendering(bool offscreen) { if (offscreen) { setenv("QT_QPA_PLATFORM", "offscreen", 1); diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 7e8cd480be..080fa2cf8e 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,9 +1,9 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279.img.xz", - "hash": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", - "hash_raw": "8d8d8620de8b2687f3a8fffdb81b2abd1fe2ead5bc831361a1a212e5589ac279", + "url": "https://commadist.azureedge.net/agnosupdate/boot-fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275.img.xz", + "hash": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", + "hash_raw": "fd30f580375279ff4605034ec13711890a2b227205571a087cdc5226a2710275", "size": 15636480, "sparse": false, "full_check": true, @@ -11,9 +11,9 @@ }, { "name": "abl", - "url": "https://commadist.azureedge.net/agnosupdate/abl-0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d.img.xz", - "hash": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", - "hash_raw": "0084fcf79fea067632a1c2d9519b6445ad484aa8b09f49f22e6b45b4dccacd2d", + "url": "https://commadist.azureedge.net/agnosupdate/abl-bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a.img.xz", + "hash": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", + "hash_raw": "bb234733816781b3d09266f91f741436e9bf17e1a7caf468cf7d09ee788cef4a", "size": 274432, "sparse": false, "full_check": true, @@ -21,9 +21,9 @@ }, { "name": "xbl", - "url": "https://commadist.azureedge.net/agnosupdate/xbl-942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa.img.xz", - "hash": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", - "hash_raw": "942b9b2914d89c2a70fdf27380b59e04b549ac2fd53ecb29d6549d1a9c8daeaa", + "url": "https://commadist.azureedge.net/agnosupdate/xbl-bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5.img.xz", + "hash": "bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5", + "hash_raw": "bcef195b00a1ab685da601f4072722569773ab161e91c8753ad99ca4217a28f5", "size": 3282672, "sparse": false, "full_check": true, @@ -31,9 +31,9 @@ }, { "name": "xbl_config", - "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf.img.xz", - "hash": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", - "hash_raw": "6881d94599f65d94c13bcc0bd860184dfba2dfe96ec776d08fb35ac5b5f85bbf", + "url": "https://commadist.azureedge.net/agnosupdate/xbl_config-19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac.img.xz", + "hash": "19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac", + "hash_raw": "19791056558c16f8dae787531b5e30b3b3db2ded9d666688df45ce1b91a72bac", "size": 98124, "sparse": false, "full_check": true, @@ -41,9 +41,9 @@ }, { "name": "devcfg", - "url": "https://commadist.azureedge.net/agnosupdate/devcfg-9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262.img.xz", - "hash": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", - "hash_raw": "9bbf168baff6101f4890c5c95c118e30813c2610cfb35b8e19e363f04a32a262", + "url": "https://commadist.azureedge.net/agnosupdate/devcfg-be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27.img.xz", + "hash": "be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27", + "hash_raw": "be44b73dda5be840b09d5347d536459e31098da3fea97596956c0bdad19bdf27", "size": 40336, "sparse": false, "full_check": true, @@ -51,9 +51,9 @@ }, { "name": "aop", - "url": "https://commadist.azureedge.net/agnosupdate/aop-c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222.img.xz", - "hash": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", - "hash_raw": "c1d9d712980f6b2a4b12196597f4d1bf3fe4fec6c59edf29ae63ef21f11b8222", + "url": "https://commadist.azureedge.net/agnosupdate/aop-5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f.img.xz", + "hash": "5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f", + "hash_raw": "5d764611a683d6a738cf06a1dcf8a926d0f47b5117ad40d3054167de6dd8bd0f", "size": 184364, "sparse": false, "full_check": true, @@ -61,16 +61,16 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz", - "hash": "611011f3e3f147bc24f371105a9dd3760ec11ba424c56d4a442a66b098c784c0", - "hash_raw": "e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432", + "url": "https://commadist.azureedge.net/agnosupdate/system-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz", + "hash": "3b6cdf9bd881a5e90b21dd02c6faa923b415e32ecae9bfdc96753d4208fb82fe", + "hash_raw": "e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98", "size": 10737418240, "sparse": true, "full_check": false, "has_ab": true, "alt": { - "hash": "256442a55fcb9e8f72969f003a4db91598dee1136f8dda85b553a557d36b93d8", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1fa3018bce9bad01c6967e5e21f1141cf5c8f02d2edfaed51c738f74a32a432.img.xz" + "hash": "2fb81e58f4bc6c4e5e71c8e7ac7553f85082c430627d7a5cc54a6bbc82862500", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-e1952bb363688c0f5c0646e39bcdfb45be25b5e2baed37d1ba7801aa1a3a9c98.img.xz" } } -] +] \ No newline at end of file diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index 0a00aca5be..f6ea86b002 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -67,14 +67,6 @@ public: bl_power_control.close(); } } - static void set_volume(float volume) { - volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); - - char volume_str[6]; - snprintf(volume_str, sizeof(volume_str), "%.3f", volume); - std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); - } - static std::map get_init_logs() { std::map ret = { diff --git a/system/loggerd/config.py b/system/loggerd/config.py index 664e78b378..5c2c89b73f 100644 --- a/system/loggerd/config.py +++ b/system/loggerd/config.py @@ -1,6 +1,4 @@ import os -from pathlib import Path -from openpilot.system.hardware import PC from openpilot.system.hardware.hw import Paths @@ -9,10 +7,6 @@ SEGMENT_LENGTH = 60 STATS_DIR_FILE_LIMIT = 10000 STATS_SOCKET = "ipc:///tmp/stats" -if PC: - STATS_DIR = str(Path.home() / ".comma" / "stats") -else: - STATS_DIR = "/data/stats/" STATS_FLUSH_TIME_S = 60 def get_available_percent(default=None): diff --git a/system/loggerd/deleter.py b/system/loggerd/deleter.py index b060232b6e..868340150a 100755 --- a/system/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -4,7 +4,7 @@ import shutil import threading from typing import List from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.loggerd.config import get_available_bytes, get_available_percent from openpilot.system.loggerd.uploader import listdir_by_creation from openpilot.system.loggerd.xattr_cache import getxattr diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index a7349fc5e4..538d99f66f 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -9,7 +9,7 @@ from pathlib import Path from typing import List, Optional from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.loggerd.uploader import uploader_fn, UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE from openpilot.system.loggerd.tests.loggerd_tests_common import UploaderTestCase diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 12c6ecdca9..4bc031a632 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -19,7 +19,7 @@ from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import TICI from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import getxattr, setxattr -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType UPLOAD_ATTR_NAME = 'user.upload' @@ -228,7 +228,7 @@ class Uploader: return success def get_msg(self): - msg = messaging.new_message("uploaderState") + msg = messaging.new_message("uploaderState", valid=True) us = msg.uploaderState us.immediateQueueSize = int(self.immediate_size / 1e6) us.immediateQueueCount = self.immediate_count diff --git a/system/logmessaged.py b/system/logmessaged.py index c53e20e483..46bf79b0b2 100755 --- a/system/logmessaged.py +++ b/system/logmessaged.py @@ -5,7 +5,7 @@ from typing import NoReturn import cereal.messaging as messaging from openpilot.common.logging_extra import SwagLogFileFormatter from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import get_file_handler +from openpilot.common.swaglog import get_file_handler def main() -> NoReturn: @@ -35,13 +35,11 @@ def main() -> NoReturn: continue # then we publish them - msg = messaging.new_message() - msg.logMessage = record + msg = messaging.new_message(None, valid=True, logMessage=record) log_message_sock.send(msg.to_bytes()) if level >= 40: # logging.ERROR - msg = messaging.new_message() - msg.errorLogMessage = record + msg = messaging.new_message(None, valid=True, errorLogMessage=record) error_log_message_sock.send(msg.to_bytes()) finally: sock.close() diff --git a/system/micd.py b/system/micd.py index 72f3b8b490..f4085becd4 100755 --- a/system/micd.py +++ b/system/micd.py @@ -2,15 +2,27 @@ import numpy as np from cereal import messaging -from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import Ratekeeper -from openpilot.system.swaglog import cloudlog +from openpilot.common.retry import retry +from openpilot.common.swaglog import cloudlog +from openpilot.system.hardware import TICI RATE = 10 FFT_SAMPLES = 4096 REFERENCE_SPL = 2e-5 # newtons/m^2 SAMPLE_RATE = 44100 -FILTER_DT = 1. / (SAMPLE_RATE / FFT_SAMPLES) + + +@retry(attempts=7, delay=3) +def wait_for_devices(sd): + # reload sounddevice to reinitialize portaudio + sd._terminate() + sd._initialize() + + devices = sd.query_devices() + cloudlog.info(f"sounddevice available devices: {list(devices)}") + + assert len(devices) > 0 def calculate_spl(measurements): @@ -50,15 +62,12 @@ class Mic: self.sound_pressure_weighted = 0 self.sound_pressure_level_weighted = 0 - self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) - def update(self): - msg = messaging.new_message('microphone') + msg = messaging.new_message('microphone', valid=True) msg.microphone.soundPressure = float(self.sound_pressure) msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted) - msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) self.pm.send('microphone', msg) self.rk.keep_time() @@ -79,7 +88,6 @@ class Mic: self.sound_pressure, _ = calculate_spl(measurements) measurements_weighted = apply_a_weighting(measurements) self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted) - self.spl_filter_weighted.update(self.sound_pressure_level_weighted) self.measurements = self.measurements[FFT_SAMPLES:] @@ -87,7 +95,10 @@ class Mic: # sounddevice must be imported after forking processes import sounddevice as sd - with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + if TICI: + wait_for_devices(sd) # wait for alsa to be initialized on device + + with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") while True: self.update() diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index ac862052d3..0105d4074f 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -16,8 +16,9 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging from openpilot.common.gpio import gpio_init, gpio_set +from openpilot.common.retry import retry from openpilot.system.hardware.tici.pins import GPIO -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.qcomgpsd.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv from openpilot.system.qcomgpsd.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, @@ -84,27 +85,13 @@ measurementStatusGlonassFields = { "glonassTimeMarkValid": 17 } +@retry(attempts=10, delay=1.0) +def try_setup_logs(diag, logs): + return setup_logs(diag, logs) -def try_setup_logs(diag, log_types): - for _ in range(10): - try: - setup_logs(diag, log_types) - break - except Exception: - cloudlog.exception("setup logs failed, trying again") - time.sleep(1.0) - else: - raise Exception(f"setup logs failed, {log_types=}") - +@retry(attempts=3, delay=1.0) def at_cmd(cmd: str) -> Optional[str]: - for _ in range(3): - try: - return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') - except subprocess.CalledProcessError: - cloudlog.exception("qcomgps.mmcli_command_failed") - time.sleep(1.0) - raise Exception(f"failed to execute mmcli command {cmd=}") - + return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') def gps_enabled() -> bool: try: @@ -154,24 +141,13 @@ def downloader_loop(event): except KeyboardInterrupt: pass +@retry(attempts=5, delay=0.2, ignore_failure=True) def inject_assistance(): - for _ in range(5): - try: - cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" - subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) - cloudlog.info("successfully loaded assistance data") - return - except subprocess.CalledProcessError as e: - cloudlog.event( - "qcomgps.assistance_loading_failed", - error=True, - cmd=e.cmd, - output=e.output, - returncode=e.returncode - ) - time.sleep(0.2) - cloudlog.error("failed to load assistance after retry") + cmd = f"mmcli -m any --timeout 30 --location-inject-assistance-data={ASSIST_DATA_FILE}" + subprocess.check_output(cmd, stderr=subprocess.PIPE, shell=True) + cloudlog.info("successfully loaded assistance data") +@retry(attempts=5, delay=1.0) def setup_quectel(diag: ModemDiag) -> bool: ret = False @@ -321,7 +297,7 @@ def main() -> NoReturn: print("%.4f: got log: %x len %d" % (time.time(), log_type, len(log_payload))) if log_type == LOG_GNSS_OEMDRE_MEASUREMENT_REPORT: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) gnss = msg.qcomGnss gnss.logTs = log_time @@ -370,7 +346,7 @@ def main() -> NoReturn: vNED = [report["q_FltVelEnuMps[1]"], report["q_FltVelEnuMps[0]"], -report["q_FltVelEnuMps[2]"]] vNEDsigma = [report["q_FltVelSigmaMps[1]"], report["q_FltVelSigmaMps[0]"], -report["q_FltVelSigmaMps[2]"]] - msg = messaging.new_message('gpsLocation') + msg = messaging.new_message('gpsLocation', valid=True) gps = msg.gpsLocation gps.latitude = report["t_DblFinalPosLatLon[0]"] * 180/math.pi gps.longitude = report["t_DblFinalPosLatLon[1]"] * 180/math.pi @@ -396,7 +372,7 @@ def main() -> NoReturn: pm.send('gpsLocation', msg) elif log_type == LOG_GNSS_OEMDRE_SVPOLY_REPORT: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) dat = unpack_svpoly(log_payload) dat = relist(dat) gnss = msg.qcomGnss @@ -433,7 +409,7 @@ def main() -> NoReturn: pm.send('qcomGnss', msg) elif log_type in [LOG_GNSS_GPS_MEASUREMENT_REPORT, LOG_GNSS_GLONASS_MEASUREMENT_REPORT]: - msg = messaging.new_message('qcomGnss') + msg = messaging.new_message('qcomGnss', valid=True) gnss = msg.qcomGnss gnss.logTs = log_time diff --git a/system/sensord/pigeond.py b/system/sensord/pigeond.py index 2e8f151d17..78b3b07498 100755 --- a/system/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -11,7 +11,7 @@ from typing import List, Optional, Tuple from cereal import messaging from openpilot.common.params import Params -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.hardware import TICI from openpilot.common.gpio import gpio_init, gpio_set from openpilot.system.hardware.tici.pins import GPIO @@ -291,7 +291,7 @@ def run_receiving(pigeon: TTYPigeon, pm: messaging.PubMaster, duration: int = 0) continue # send out to socket - msg = messaging.new_message('ubloxRaw', len(dat)) + msg = messaging.new_message('ubloxRaw', len(dat), valid=True) msg.ubloxRaw = dat[:] pm.send('ubloxRaw', msg) else: diff --git a/system/tests/test_logmessaged.py b/system/tests/test_logmessaged.py index 5d0e1bfc82..d27dae46ad 100755 --- a/system/tests/test_logmessaged.py +++ b/system/tests/test_logmessaged.py @@ -7,7 +7,7 @@ import unittest import cereal.messaging as messaging from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.system.hardware.hw import Paths -from openpilot.system.swaglog import cloudlog, ipchandler +from openpilot.common.swaglog import cloudlog, ipchandler class TestLogmessaged(unittest.TestCase): diff --git a/system/timezoned.py b/system/timezoned.py index 91424d33b5..546405c9a0 100755 --- a/system/timezoned.py +++ b/system/timezoned.py @@ -10,7 +10,7 @@ from timezonefinder import TimezoneFinder from openpilot.common.params import Params from openpilot.system.hardware import AGNOS -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog from openpilot.system.version import get_version REQUEST_HEADERS = {'User-Agent': "openpilot-" + get_version()} diff --git a/system/version.py b/system/version.py index 82e99fd416..f9fd2bc847 100755 --- a/system/version.py +++ b/system/version.py @@ -5,7 +5,7 @@ from typing import List, Optional from functools import lru_cache from openpilot.common.basedir import BASEDIR -from openpilot.system.swaglog import cloudlog +from openpilot.common.swaglog import cloudlog RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] diff --git a/system/webrtc/__init__.py b/system/webrtc/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py new file mode 100644 index 0000000000..3c78be6752 --- /dev/null +++ b/system/webrtc/device/audio.py @@ -0,0 +1,110 @@ +import asyncio +import io +from typing import Optional, List, Tuple + +import aiortc +import av +import numpy as np +import pyaudio + + +class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): + PYAUDIO_TO_AV_FORMAT_MAP = { + pyaudio.paUInt8: 'u8', + pyaudio.paInt16: 's16', + pyaudio.paInt24: 's24', + pyaudio.paInt32: 's32', + pyaudio.paFloat32: 'flt', + } + + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: Optional[int] = None): + super().__init__() + + self.p = pyaudio.PyAudio() + chunk_size = int(packet_time * rate) + self.stream = self.p.open(format=audio_format, + channels=channels, + rate=rate, + frames_per_buffer=chunk_size, + input=True, + input_device_index=device_index) + self.format = audio_format + self.rate = rate + self.channels = channels + self.packet_time = packet_time + self.chunk_size = chunk_size + self.pts = 0 + + async def recv(self): + mic_data = self.stream.read(self.chunk_size) + mic_array = np.frombuffer(mic_data, dtype=np.int16) + mic_array = np.expand_dims(mic_array, axis=0) + layout = 'stereo' if self.channels > 1 else 'mono' + frame = av.AudioFrame.from_ndarray(mic_array, format=self.PYAUDIO_TO_AV_FORMAT_MAP[self.format], layout=layout) + frame.rate = self.rate + frame.pts = self.pts + self.pts += frame.samples + + return frame + + +class AudioOutputSpeaker: + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: Optional[int] = None): + + chunk_size = int(packet_time * rate) + self.p = pyaudio.PyAudio() + self.buffer = io.BytesIO() + self.channels = channels + self.stream = self.p.open(format=audio_format, + channels=channels, + rate=rate, + frames_per_buffer=chunk_size, + output=True, + output_device_index=device_index, + stream_callback=self.__pyaudio_callback) + self.tracks_and_tasks: List[Tuple[aiortc.MediaStreamTrack, Optional[asyncio.Task]]] = [] + + def __pyaudio_callback(self, in_data, frame_count, time_info, status): + if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: + buff = b'\x00\x00' * frame_count * self.channels + elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size + self.buffer.seek(0) + buff = self.buffer.read(frame_count * self.channels * 4) + buff = buff[:frame_count * self.channels * 2] + self.buffer.seek(2) + else: + self.buffer.seek(0) + buff = self.buffer.read(frame_count * self.channels * 2) + self.buffer.seek(2) + return (buff, pyaudio.paContinue) + + async def __consume(self, track): + while True: + try: + frame = await track.recv() + except aiortc.MediaStreamError: + return + + self.buffer.write(bytes(frame.planes[0])) + + def hasTrack(self, track: aiortc.MediaStreamTrack) -> bool: + return any(t == track for t, _ in self.tracks_and_tasks) + + def addTrack(self, track: aiortc.MediaStreamTrack): + if not self.hasTrack(track): + self.tracks_and_tasks.append((track, None)) + + def start(self): + for index, (track, task) in enumerate(self.tracks_and_tasks): + if task is None: + self.tracks_and_tasks[index] = (track, asyncio.create_task(self.__consume(track))) + + def stop(self): + for _, task in self.tracks_and_tasks: + if task is not None: + task.cancel() + + self.tracks_and_tasks = [] + self.stream.stop_stream() + self.stream.close() + self.p.terminate() diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py new file mode 100644 index 0000000000..1ecb6dbd74 --- /dev/null +++ b/system/webrtc/device/video.py @@ -0,0 +1,69 @@ +import asyncio +from typing import Optional + +import av +from teleoprtc.tracks import TiciVideoStreamTrack + +from cereal import messaging +from openpilot.tools.lib.framereader import FrameReader +from openpilot.common.realtime import DT_MDL, DT_DMON + + +class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): + camera_to_sock_mapping = { + "driver": "livestreamDriverEncodeData", + "wideRoad": "livestreamWideRoadEncodeData", + "road": "livestreamRoadEncodeData", + } + + def __init__(self, camera_type: str): + dt = DT_DMON if camera_type == "driver" else DT_MDL + super().__init__(camera_type, dt) + + self._sock = messaging.sub_sock(self.camera_to_sock_mapping[camera_type], conflate=True) + self._pts = 0 + + async def recv(self): + while True: + msg = messaging.recv_one_or_none(self._sock) + if msg is not None: + break + await asyncio.sleep(0.005) + + evta = getattr(msg, msg.which()) + + packet = av.Packet(evta.header + evta.data) + packet.time_base = self._time_base + packet.pts = self._pts + + self.log_debug("track sending frame %s", self._pts) + self._pts += self._dt * self._clock_rate + + return packet + + def codec_preference(self) -> Optional[str]: + return "H264" + + +class FrameReaderVideoStreamTrack(TiciVideoStreamTrack): + def __init__(self, input_file: str, dt: float = DT_MDL, camera_type: str = "driver"): + super().__init__(camera_type, dt) + + frame_reader = FrameReader(input_file) + self._frames = [frame_reader.get(i, pix_fmt="rgb24") for i in range(frame_reader.frame_count)] + self._frame_count = len(self.frames) + self._frame_index = 0 + self._pts = 0 + + async def recv(self): + self.log_debug("track sending frame %s", self._pts) + img = self._frames[self._frame_index] + + new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") + new_frame.pts = self._pts + new_frame.time_base = self._time_base + + self._frame_index = (self._frame_index + 1) % self._frame_count + self._pts = await self.next_pts(self._pts) + + return new_frame diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py new file mode 100644 index 0000000000..f659b34293 --- /dev/null +++ b/system/webrtc/schema.py @@ -0,0 +1,43 @@ +import capnp +from typing import Union, List, Dict, Any + + +def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, Any]]: + data_type = next(type_walker) + if data_type.which() == 'struct': + return generate_struct(next(schema_walker)) + elif data_type.which() == 'list': + _ = next(schema_walker) + return [generate_type(type_walker, schema_walker)] + elif data_type.which() == 'enum': + return "text" + else: + return str(data_type.which()) + + +def generate_struct(schema: capnp.lib.capnp._StructSchema) -> Dict[str, Any]: + return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} + + +def generate_field(field: capnp.lib.capnp._StructSchemaField) -> Union[str, List[Any], Dict[str, Any]]: + def schema_walker(field): + yield field.schema + + s = field.schema + while hasattr(s, 'elementType'): + s = s.elementType + yield s + + def type_walker(field): + yield field.proto.slot.type + + t = field.proto.slot.type + while hasattr(getattr(t, t.which()), 'elementType'): + t = getattr(t, t.which()).elementType + yield t + + if field.proto.which() == "slot": + schema_gen, type_gen = schema_walker(field), type_walker(field) + return generate_type(type_gen, schema_gen) + else: + return generate_struct(field.schema) diff --git a/system/webrtc/tests/test_stream_session.py b/system/webrtc/tests/test_stream_session.py new file mode 100755 index 0000000000..2173c3806b --- /dev/null +++ b/system/webrtc/tests/test_stream_session.py @@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +import asyncio +import unittest +from unittest.mock import Mock, MagicMock, patch +import json +# for aiortc and its dependencies +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from aiortc import RTCDataChannel +from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE +import capnp +import pyaudio + +from cereal import messaging, log + +from openpilot.system.webrtc.webrtcd import CerealOutgoingMessageProxy, CerealIncomingMessageProxy +from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack +from openpilot.system.webrtc.device.audio import AudioInputStreamTrack +from openpilot.common.realtime import DT_DMON + + +class TestStreamSession(unittest.TestCase): + def setUp(self): + self.loop = asyncio.new_event_loop() + + def tearDown(self): + self.loop.stop() + self.loop.close() + + def test_outgoing_proxy(self): + test_msg = log.Event.new_message() + test_msg.logMonoTime = 123 + test_msg.valid = True + test_msg.customReservedRawData0 = b"test" + expected_dict = {"type": "customReservedRawData0", "logMonoTime": 123, "valid": True, "data": "test"} + expected_json = json.dumps(expected_dict).encode() + + channel = Mock(spec=RTCDataChannel) + mocked_submaster = messaging.SubMaster(["customReservedRawData0"]) + def mocked_update(t): + mocked_submaster.update_msgs(0, [test_msg]) + + with patch.object(messaging.SubMaster, "update", side_effect=mocked_update): + proxy = CerealOutgoingMessageProxy(mocked_submaster) + proxy.add_channel(channel) + + proxy.update() + + channel.send.assert_called_once_with(expected_json) + + def test_incoming_proxy(self): + tested_msgs = [ + {"type": "customReservedRawData0", "data": "test"}, # primitive + {"type": "can", "data": [{"address": 0, "busTime": 0, "dat": "", "src": 0}]}, # list + {"type": "testJoystick", "data": {"axes": [0, 0], "buttons": [False]}}, # dict + ] + + mocked_pubmaster = MagicMock(spec=messaging.PubMaster) + + proxy = CerealIncomingMessageProxy(mocked_pubmaster) + + for msg in tested_msgs: + proxy.send(json.dumps(msg).encode()) + + mocked_pubmaster.send.assert_called_once() + mt, md = mocked_pubmaster.send.call_args.args + self.assertEqual(mt, msg["type"]) + self.assertIsInstance(md, capnp._DynamicStructBuilder) + self.assertTrue(hasattr(md, msg["type"])) + + mocked_pubmaster.reset_mock() + + def test_livestream_track(self): + fake_msg = messaging.new_message("livestreamDriverEncodeData") + + config = {"receive.return_value": fake_msg.to_bytes()} + with patch("cereal.messaging.SubSocket", spec=True, **config): + track = LiveStreamVideoStreamTrack("driver") + + self.assertTrue(track.id.startswith("driver")) + self.assertEqual(track.codec_preference(), "H264") + + for i in range(5): + packet = self.loop.run_until_complete(track.recv()) + self.assertEqual(packet.time_base, VIDEO_TIME_BASE) + self.assertEqual(packet.pts, int(i * DT_DMON * VIDEO_CLOCK_RATE)) + self.assertEqual(packet.size, 0) + + def test_input_audio_track(self): + packet_time, rate = 0.02, 16000 + sample_count = int(packet_time * rate) + mocked_stream = MagicMock(spec=pyaudio.Stream) + mocked_stream.read.return_value = b"\x00" * 2 * sample_count + + config = {"open.side_effect": lambda *args, **kwargs: mocked_stream} + with patch("pyaudio.PyAudio", spec=True, **config): + track = AudioInputStreamTrack(audio_format=pyaudio.paInt16, packet_time=packet_time, rate=rate) + + for i in range(5): + frame = self.loop.run_until_complete(track.recv()) + self.assertEqual(frame.rate, rate) + self.assertEqual(frame.samples, sample_count) + self.assertEqual(frame.pts, i * sample_count) + + +if __name__ == "__main__": + unittest.main() diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py new file mode 100755 index 0000000000..b48bf6bc19 --- /dev/null +++ b/system/webrtc/tests/test_webrtcd.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python +import asyncio +import json +import unittest +from unittest.mock import MagicMock, AsyncMock +# for aiortc and its dependencies +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +from openpilot.system.webrtc.webrtcd import get_stream + +import aiortc +from teleoprtc import WebRTCOfferBuilder + + +class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): + async def assertCompletesWithTimeout(self, awaitable, timeout=1): + try: + async with asyncio.timeout(timeout): + await awaitable + except asyncio.TimeoutError: + self.fail("Timeout while waiting for awaitable to complete") + + async def test_webrtcd(self): + mock_request = MagicMock() + async def connect(offer): + body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': [], 'bridge_services_out': []} + mock_request.json.side_effect = AsyncMock(return_value=body) + response = await get_stream(mock_request) + response_json = json.loads(response.text) + return aiortc.RTCSessionDescription(**response_json) + + builder = WebRTCOfferBuilder(connect) + builder.offer_to_receive_video_stream("road") + builder.offer_to_receive_audio_stream() + builder.add_messaging() + + stream = builder.stream() + + await self.assertCompletesWithTimeout(stream.start()) + await self.assertCompletesWithTimeout(stream.wait_for_connection()) + + self.assertTrue(stream.has_incoming_video_track("road")) + self.assertTrue(stream.has_incoming_audio_track()) + self.assertTrue(stream.has_messaging_channel()) + + video_track, audio_track = stream.get_incoming_video_track("road"), stream.get_incoming_audio_track() + await self.assertCompletesWithTimeout(video_track.recv()) + await self.assertCompletesWithTimeout(audio_track.recv()) + + await self.assertCompletesWithTimeout(stream.stop()) + + # cleanup, very implementation specific, test may break if it changes + self.assertTrue(mock_request.app["streams"].__setitem__.called, "Implementation changed, please update this test") + _, session = mock_request.app["streams"].__setitem__.call_args.args + await self.assertCompletesWithTimeout(session.post_run_cleanup()) + + +if __name__ == "__main__": + unittest.main() diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py new file mode 100755 index 0000000000..12f9328532 --- /dev/null +++ b/system/webrtc/webrtcd.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python3 + +import argparse +import asyncio +import json +import uuid +import logging +from dataclasses import dataclass, field +from typing import Any, List, Optional, Union + +# aiortc and its dependencies have lots of internal warnings :( +import warnings +warnings.filterwarnings("ignore", category=DeprecationWarning) + +import aiortc +from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack +from aiortc.contrib.media import MediaBlackhole +from aiortc.exceptions import InvalidStateError +from aiohttp import web +import capnp +from teleoprtc import WebRTCAnswerBuilder +from teleoprtc.info import parse_info_from_offer + +from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack +from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker +from openpilot.system.webrtc.schema import generate_field + +from cereal import messaging, log + + +class CerealOutgoingMessageProxy: + def __init__(self, sm: messaging.SubMaster): + self.sm = sm + self.channels: List[aiortc.RTCDataChannel] = [] + + def add_channel(self, channel: aiortc.RTCDataChannel): + self.channels.append(channel) + + def to_json(self, msg_content: Any): + if isinstance(msg_content, capnp._DynamicStructReader): + msg_dict = msg_content.to_dict() + elif isinstance(msg_content, capnp._DynamicListReader): + msg_dict = [self.to_json(msg) for msg in msg_content] + elif isinstance(msg_content, bytes): + msg_dict = msg_content.decode() + else: + msg_dict = msg_content + + return msg_dict + + def update(self): + # this is blocking in async context... + self.sm.update(0) + for service, updated in self.sm.updated.items(): + if not updated: + continue + msg_dict = self.to_json(self.sm[service]) + mono_time, valid = self.sm.logMonoTime[service], self.sm.valid[service] + outgoing_msg = {"type": service, "logMonoTime": mono_time, "valid": valid, "data": msg_dict} + encoded_msg = json.dumps(outgoing_msg).encode() + for channel in self.channels: + channel.send(encoded_msg) + + +class CerealIncomingMessageProxy: + def __init__(self, pm: messaging.PubMaster): + self.pm = pm + + def send(self, message: bytes): + msg_json = json.loads(message) + msg_type, msg_data = msg_json["type"], msg_json["data"] + size = None + if not isinstance(msg_data, dict): + size = len(msg_data) + + msg = messaging.new_message(msg_type, size=size) + setattr(msg, msg_type, msg_data) + self.pm.send(msg_type, msg) + + +class CerealProxyRunner: + def __init__(self, proxy: CerealOutgoingMessageProxy): + self.proxy = proxy + self.is_running = False + self.task = None + self.logger = logging.getLogger("webrtcd") + + def start(self): + assert self.task is None + self.task = asyncio.create_task(self.run()) + + def stop(self): + if self.task is None or self.task.done(): + return + self.task.cancel() + self.task = None + + async def run(self): + while True: + try: + self.proxy.update() + except InvalidStateError: + self.logger.warning("Cereal outgoing proxy invalid state (connection closed)") + break + except Exception as ex: + self.logger.error("Cereal outgoing proxy failure: %s", ex) + await asyncio.sleep(0.01) + + +class StreamSession: + def __init__(self, sdp: str, cameras: List[str], incoming_services: List[str], outgoing_services: List[str], debug_mode: bool = False): + config = parse_info_from_offer(sdp) + builder = WebRTCAnswerBuilder(sdp) + + assert len(cameras) == config.n_expected_camera_tracks, "Incoming stream has misconfigured number of video tracks" + for cam in cameras: + track = LiveStreamVideoStreamTrack(cam) if not debug_mode else VideoStreamTrack() + builder.add_video_stream(cam, track) + if config.expected_audio_track: + track = AudioInputStreamTrack() if not debug_mode else AudioStreamTrack() + builder.add_audio_stream(track) + if config.incoming_audio_track: + self.audio_output_cls = AudioOutputSpeaker if not debug_mode else MediaBlackhole + builder.offer_to_receive_audio_stream() + + self.stream = builder.stream() + self.identifier = str(uuid.uuid4()) + + self.outgoing_bridge = CerealOutgoingMessageProxy(messaging.SubMaster(outgoing_services)) + self.incoming_bridge = CerealIncomingMessageProxy(messaging.PubMaster(incoming_services)) + self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) + + self.audio_output: Optional[Union[AudioOutputSpeaker, MediaBlackhole]] = None + self.run_task: Optional[asyncio.Task] = None + self.logger = logging.getLogger("webrtcd") + self.logger.info("New stream session (%s), cameras %s, audio in %s out %s, incoming services %s, outgoing services %s", + self.identifier, cameras, config.incoming_audio_track, config.expected_audio_track, incoming_services, outgoing_services) + + def start(self): + self.run_task = asyncio.create_task(self.run()) + + def stop(self): + if self.run_task.done(): + return + self.run_task.cancel() + self.run_task = None + asyncio.run(self.post_run_cleanup()) + + async def get_answer(self): + return await self.stream.start() + + async def message_handler(self, message: bytes): + try: + self.incoming_bridge.send(message) + except Exception as ex: + self.logger.error("Cereal incoming proxy failure: %s", ex) + + async def run(self): + try: + await self.stream.wait_for_connection() + if self.stream.has_messaging_channel(): + self.stream.set_message_handler(self.message_handler) + channel = self.stream.get_messaging_channel() + self.outgoing_bridge_runner.proxy.add_channel(channel) + self.outgoing_bridge_runner.start() + if self.stream.has_incoming_audio_track(): + track = self.stream.get_incoming_audio_track(buffered=False) + self.audio_output = self.audio_output_cls() + self.audio_output.addTrack(track) + self.audio_output.start() + self.logger.info("Stream session (%s) connected", self.identifier) + + await self.stream.wait_for_disconnection() + await self.post_run_cleanup() + + self.logger.info("Stream session (%s) ended", self.identifier) + except Exception as ex: + self.logger.error("Stream session failure: %s", ex) + + async def post_run_cleanup(self): + await self.stream.stop() + self.outgoing_bridge_runner.stop() + if self.audio_output: + self.audio_output.stop() + + +@dataclass +class StreamRequestBody: + sdp: str + cameras: List[str] + bridge_services_in: List[str] = field(default_factory=list) + bridge_services_out: List[str] = field(default_factory=list) + + +async def get_stream(request: web.Request): + stream_dict, debug_mode = request.app['streams'], request.app['debug'] + raw_body = await request.json() + body = StreamRequestBody(**raw_body) + + session = StreamSession(body.sdp, body.cameras, body.bridge_services_in, body.bridge_services_out, debug_mode) + answer = await session.get_answer() + session.start() + + stream_dict[session.identifier] = session + + return web.json_response({"sdp": answer.sdp, "type": answer.type}) + + +async def get_schema(request: web.Request): + services = request.query["services"].split(",") + services = [s for s in services if s] + assert all(s in log.Event.schema.fields and not s.endswith("DEPRECATED") for s in services), "Invalid service name" + schema_dict = {s: generate_field(log.Event.schema.fields[s]) for s in services} + return web.json_response(schema_dict) + + +async def on_shutdown(app: web.Application): + for session in app['streams'].values(): + session.stop() + del app['streams'] + + +def webrtcd_thread(host: str, port: int, debug: bool): + logging.basicConfig(level=logging.CRITICAL, handlers=[logging.StreamHandler()]) + logging_level = logging.DEBUG if debug else logging.INFO + logging.getLogger("WebRTCStream").setLevel(logging_level) + logging.getLogger("webrtcd").setLevel(logging_level) + + app = web.Application() + + app['streams'] = dict() + app['debug'] = debug + app.on_shutdown.append(on_shutdown) + app.router.add_post("/stream", get_stream) + app.router.add_get("/schema", get_schema) + + web.run_app(app, host=host, port=port) + + +def main(): + parser = argparse.ArgumentParser(description="WebRTC daemon") + parser.add_argument("--host", type=str, default="0.0.0.0", help="Host to listen on") + parser.add_argument("--port", type=int, default=5001, help="Port to listen on") + parser.add_argument("--debug", action="store_true", help="Enable debug mode") + args = parser.parse_args() + + webrtcd_thread(args.host, args.port, args.debug) + + +if __name__=="__main__": + main() diff --git a/teleoprtc b/teleoprtc new file mode 120000 index 0000000000..3d3dbc8dea --- /dev/null +++ b/teleoprtc @@ -0,0 +1 @@ +teleoprtc_repo/teleoprtc \ No newline at end of file diff --git a/teleoprtc_repo b/teleoprtc_repo new file mode 160000 index 0000000000..4df237c512 --- /dev/null +++ b/teleoprtc_repo @@ -0,0 +1 @@ +Subproject commit 4df237c512622d3914a73f4fc15a3fc89b7f5a97 diff --git a/tools/bodyteleop/bodyav.py b/tools/bodyteleop/bodyav.py deleted file mode 100644 index 3f11f8d4f2..0000000000 --- a/tools/bodyteleop/bodyav.py +++ /dev/null @@ -1,159 +0,0 @@ -import asyncio -import io -import numpy as np -import pyaudio -import wave - -from aiortc.contrib.media import MediaBlackhole -from aiortc.mediastreams import AudioStreamTrack, MediaStreamError, MediaStreamTrack -from aiortc.mediastreams import VIDEO_CLOCK_RATE, VIDEO_TIME_BASE -from aiortc.rtcrtpsender import RTCRtpSender -from av import CodecContext, Packet -from pydub import AudioSegment -import cereal.messaging as messaging - -AUDIO_RATE = 16000 -SOUNDS = { - 'engage': '../../selfdrive/assets/sounds/engage.wav', - 'disengage': '../../selfdrive/assets/sounds/disengage.wav', - 'error': '../../selfdrive/assets/sounds/warning_immediate.wav', -} - - -def force_codec(pc, sender, forced_codec='video/VP9', stream_type="video"): - codecs = RTCRtpSender.getCapabilities(stream_type).codecs - codec = [codec for codec in codecs if codec.mimeType == forced_codec] - transceiver = next(t for t in pc.getTransceivers() if t.sender == sender) - transceiver.setCodecPreferences(codec) - - -class EncodedBodyVideo(MediaStreamTrack): - kind = "video" - - _start: float - _timestamp: int - - def __init__(self): - super().__init__() - sock_name = 'livestreamDriverEncodeData' - messaging.context = messaging.Context() - self.sock = messaging.sub_sock(sock_name, None, conflate=True) - self.pts = 0 - - async def recv(self) -> Packet: - while True: - msg = messaging.recv_one_or_none(self.sock) - if msg is not None: - break - await asyncio.sleep(0.005) - - evta = getattr(msg, msg.which()) - self.last_idx = evta.idx.encodeId - - packet = Packet(evta.header + evta.data) - packet.time_base = VIDEO_TIME_BASE - packet.pts = self.pts - self.pts += 0.05 * VIDEO_CLOCK_RATE - return packet - - -class WebClientSpeaker(MediaBlackhole): - def __init__(self): - super().__init__() - self.p = pyaudio.PyAudio() - self.buffer = io.BytesIO() - self.channels = 2 - self.stream = self.p.open(format=pyaudio.paInt16, channels=self.channels, rate=48000, frames_per_buffer=9600, - output=True, stream_callback=self.pyaudio_callback) - - def pyaudio_callback(self, in_data, frame_count, time_info, status): - if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: - buff = np.zeros((frame_count, 2), dtype=np.int16).tobytes() - elif self.buffer.getbuffer().nbytes > 115200: # 3x the usual read size - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 4) - buff = buff[:frame_count * self.channels * 2] - self.buffer.seek(2) - else: - self.buffer.seek(0) - buff = self.buffer.read(frame_count * self.channels * 2) - self.buffer.seek(2) - return (buff, pyaudio.paContinue) - - async def consume(self, track): - while True: - try: - frame = await track.recv() - except MediaStreamError: - return - bio = bytes(frame.planes[0]) - self.buffer.write(bio) - - async def start(self): - for track, task in self._MediaBlackhole__tracks.items(): - if task is None: - self._MediaBlackhole__tracks[track] = asyncio.ensure_future(self.consume(track)) - - async def stop(self): - for task in self._MediaBlackhole__tracks.values(): - if task is not None: - task.cancel() - self._MediaBlackhole__tracks = {} - self.stream.stop_stream() - self.stream.close() - self.p.terminate() - - -class BodyMic(AudioStreamTrack): - def __init__(self): - super().__init__() - - self.sample_rate = AUDIO_RATE - self.AUDIO_PTIME = 0.020 # 20ms audio packetization - self.samples = int(self.AUDIO_PTIME * self.sample_rate) - self.FORMAT = pyaudio.paInt16 - self.CHANNELS = 2 - self.RATE = self.sample_rate - self.CHUNK = int(AUDIO_RATE * 0.020) - self.p = pyaudio.PyAudio() - self.mic_stream = self.p.open(format=self.FORMAT, channels=1, rate=self.RATE, input=True, frames_per_buffer=self.CHUNK) - - self.codec = CodecContext.create('pcm_s16le', 'r') - self.codec.sample_rate = self.RATE - self.codec.channels = 2 - self.audio_samples = 0 - self.chunk_number = 0 - - async def recv(self): - mic_data = self.mic_stream.read(self.CHUNK) - mic_sound = AudioSegment(mic_data, sample_width=2, channels=1, frame_rate=self.RATE) - mic_sound = AudioSegment.from_mono_audiosegments(mic_sound, mic_sound) - mic_sound += 3 # increase volume by 3db - packet = Packet(mic_sound.raw_data) - frame = self.codec.decode(packet)[0] - frame.pts = self.audio_samples - self.audio_samples += frame.samples - self.chunk_number = self.chunk_number + 1 - return frame - - -async def play_sound(sound): - chunk = 5120 - with wave.open(SOUNDS[sound], 'rb') as wf: - def callback(in_data, frame_count, time_info, status): - data = wf.readframes(frame_count) - return data, pyaudio.paContinue - - p = pyaudio.PyAudio() - stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), - channels=wf.getnchannels(), - rate=wf.getframerate(), - output=True, - frames_per_buffer=chunk, - stream_callback=callback) - stream.start_stream() - while stream.is_active(): - await asyncio.sleep(0) - stream.stop_stream() - stream.close() - p.terminate() diff --git a/tools/bodyteleop/static/js/jsmain.js b/tools/bodyteleop/static/js/jsmain.js index f521905724..83205a876b 100644 --- a/tools/bodyteleop/static/js/jsmain.js +++ b/tools/bodyteleop/static/js/jsmain.js @@ -1,5 +1,5 @@ import { handleKeyX, executePlan } from "./controls.js"; -import { start, stop, last_ping } from "./webrtc.js"; +import { start, stop, lastChannelMessageTime, playSoundRequest } from "./webrtc.js"; export var pc = null; export var dc = null; @@ -9,10 +9,14 @@ document.addEventListener('keyup', (e)=>(handleKeyX(e.key.toLowerCase(), 0))); $(".keys").bind("mousedown touchstart", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 1)); $(".keys").bind("mouseup touchend", (e)=>handleKeyX($(e.target).attr('id').replace('key-', ''), 0)); $("#plan-button").click(executePlan); +$(".sound").click((e)=>{ + const sound = $(e.target).attr('id').replace('sound-', '') + return playSoundRequest(sound); +}); setInterval( () => { const dt = new Date().getTime(); - if ((dt - last_ping) > 1000) { + if ((dt - lastChannelMessageTime) > 1000) { $(".pre-blob").removeClass('blob'); $("#battery").text("-"); $("#ping-time").text('-'); @@ -20,4 +24,4 @@ setInterval( () => { } }, 5000); -start(pc, dc); \ No newline at end of file +start(pc, dc); diff --git a/tools/bodyteleop/static/js/webrtc.js b/tools/bodyteleop/static/js/webrtc.js index e2f6583c17..165a2ce6c4 100644 --- a/tools/bodyteleop/static/js/webrtc.js +++ b/tools/bodyteleop/static/js/webrtc.js @@ -1,9 +1,34 @@ import { getXY } from "./controls.js"; import { pingPoints, batteryPoints, chartPing, chartBattery } from "./plots.js"; -export let dcInterval = null; -export let batteryInterval = null; -export let last_ping = null; +export let controlCommandInterval = null; +export let latencyInterval = null; +export let lastChannelMessageTime = null; + + +export function offerRtcRequest(sdp, type) { + return fetch('/offer', { + body: JSON.stringify({sdp: sdp, type: type}), + headers: {'Content-Type': 'application/json'}, + method: 'POST' + }); +} + + +export function playSoundRequest(sound) { + return fetch('/sound', { + body: JSON.stringify({sound}), + headers: {'Content-Type': 'application/json'}, + method: 'POST' + }); +} + + +export function pingHeadRequest() { + return fetch('/', { + method: 'HEAD' + }); +} export function createPeerConnection(pc) { @@ -45,16 +70,7 @@ export function negotiate(pc) { }); }).then(function() { var offer = pc.localDescription; - return fetch('/offer', { - body: JSON.stringify({ - sdp: offer.sdp, - type: offer.type, - }), - headers: { - 'Content-Type': 'application/json' - }, - method: 'POST' - }); + return offerRtcRequest(offer.sdp, offer.type); }).then(function(response) { console.log(response); return response.json(); @@ -86,25 +102,6 @@ export const constraints = { }; -export function createDummyVideoTrack() { - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - - const frameWidth = 5; // Set the width of the frame - const frameHeight = 5; // Set the height of the frame - canvas.width = frameWidth; - canvas.height = frameHeight; - - context.fillStyle = 'black'; - context.fillRect(0, 0, frameWidth, frameHeight); - - const stream = canvas.captureStream(); - const videoTrack = stream.getVideoTracks()[0]; - - return videoTrack; -} - - export function start(pc, dc) { pc = createPeerConnection(pc); @@ -138,71 +135,56 @@ export function start(pc, dc) { alert('Could not acquire media: ' + err); }); - // add a fake video? - // const dummyVideoTrack = createDummyVideoTrack(); - // const dummyMediaStream = new MediaStream(); - // dummyMediaStream.addTrack(dummyVideoTrack); - // pc.addTrack(dummyVideoTrack, dummyMediaStream); - - // setInterval(() => {pc.getStats(null).then((stats) => {stats.forEach((report) => console.log(report))})}, 10000) - // var video = document.querySelector('video'); - // var print = function (e, f){console.log(e, f); video.requestVideoFrameCallback(print);}; - // video.requestVideoFrameCallback(print); - var parameters = {"ordered": true}; dc = pc.createDataChannel('data', parameters); dc.onclose = function() { - console.log("data channel closed"); - clearInterval(dcInterval); - clearInterval(batteryInterval); + clearInterval(controlCommandInterval); + clearInterval(latencyInterval); }; - function controlCommand() { + + function sendJoystickOverDataChannel() { const {x, y} = getXY(); - const dt = new Date().getTime(); - var message = JSON.stringify({type: 'control_command', x, y, dt}); + var message = JSON.stringify({type: "testJoystick", data: {axes: [x, y], buttons: [false]}}) dc.send(message); } - - function batteryLevel() { - var message = JSON.stringify({type: 'battery_level'}); - dc.send(message); + function checkLatency() { + const initialTime = new Date().getTime(); + pingHeadRequest().then(function() { + const currentTime = new Date().getTime(); + if (Math.abs(currentTime - lastChannelMessageTime) < 1000) { + const pingtime = currentTime - initialTime; + pingPoints.push({'x': currentTime, 'y': pingtime}); + if (pingPoints.length > 1000) { + pingPoints.shift(); + } + chartPing.update(); + $("#ping-time").text((pingtime) + "ms"); + } + }) } - dc.onopen = function() { - dcInterval = setInterval(controlCommand, 50); - batteryInterval = setInterval(batteryLevel, 10000); - controlCommand(); - batteryLevel(); - $(".sound").click((e)=>{ - const sound = $(e.target).attr('id').replace('sound-', '') - dc.send(JSON.stringify({type: 'play_sound', sound})); - }); + controlCommandInterval = setInterval(sendJoystickOverDataChannel, 50); + latencyInterval = setInterval(checkLatency, 1000); + sendJoystickOverDataChannel(); }; - let val_print_idx = 0; + const textDecoder = new TextDecoder(); + var carStaterIndex = 0; dc.onmessage = function(evt) { - const data = JSON.parse(evt.data); - if(val_print_idx == 0 && data.type === 'ping_time') { - const dt = new Date().getTime(); - const pingtime = dt - data.incoming_time; - pingPoints.push({'x': dt, 'y': pingtime}); - if (pingPoints.length > 1000) { - pingPoints.shift(); - } - chartPing.update(); - $("#ping-time").text((pingtime) + "ms"); - last_ping = dt; - $(".pre-blob").addClass('blob'); - } - val_print_idx = (val_print_idx + 1 ) % 20; - if(data.type === 'battery_level') { - $("#battery").text(data.value + "%"); - batteryPoints.push({'x': new Date().getTime(), 'y': data.value}); - if (batteryPoints.length > 1000) { + const text = textDecoder.decode(evt.data); + const msg = JSON.parse(text); + if (carStaterIndex % 100 == 0 && msg.type === 'carState') { + const batteryLevel = Math.round(msg.data.fuelGauge * 100); + $("#battery").text(batteryLevel + "%"); + batteryPoints.push({'x': new Date().getTime(), 'y': batteryLevel}); + if (batteryPoints.length > 1000) { batteryPoints.shift(); } chartBattery.update(); } + carStaterIndex += 1; + lastChannelMessageTime = new Date().getTime(); + $(".pre-blob").addClass('blob'); }; } diff --git a/tools/bodyteleop/web.py b/tools/bodyteleop/web.py index 929cfa26fe..53077af67e 100644 --- a/tools/bodyteleop/web.py +++ b/tools/bodyteleop/web.py @@ -1,208 +1,124 @@ import asyncio +import dataclasses import json import logging import os import ssl -import uuid -import time +import subprocess -# aiortc and its dependencies have lots of internal warnings :( -import warnings -warnings.resetwarnings() -warnings.simplefilter("always") +from aiohttp import web, ClientSession +import pyaudio +import wave -from aiohttp import web -from aiortc import RTCPeerConnection, RTCSessionDescription - -import cereal.messaging as messaging from openpilot.common.basedir import BASEDIR -from openpilot.tools.bodyteleop.bodyav import BodyMic, WebClientSpeaker, force_codec, play_sound, MediaBlackhole, EncodedBodyVideo - -from typing import Optional +from openpilot.system.webrtc.webrtcd import StreamRequestBody +from openpilot.common.params import Params -logger = logging.getLogger("pc") +logger = logging.getLogger("bodyteleop") logging.basicConfig(level=logging.INFO) -pcs: set[RTCPeerConnection] = set() -pm: Optional[messaging.PubMaster] = None -sm: Optional[messaging.SubMaster] = None TELEOPDIR = f"{BASEDIR}/tools/bodyteleop" +WEBRTCD_HOST, WEBRTCD_PORT = "localhost", 5001 + + +## UTILS +async def play_sound(sound): + SOUNDS = { + "engage": "selfdrive/assets/sounds/engage.wav", + "disengage": "selfdrive/assets/sounds/disengage.wav", + "error": "selfdrive/assets/sounds/warning_immediate.wav", + } + assert sound in SOUNDS + + chunk = 5120 + with wave.open(os.path.join(BASEDIR, SOUNDS[sound]), "rb") as wf: + def callback(in_data, frame_count, time_info, status): + data = wf.readframes(frame_count) + return data, pyaudio.paContinue + + p = pyaudio.PyAudio() + stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), + channels=wf.getnchannels(), + rate=wf.getframerate(), + output=True, + frames_per_buffer=chunk, + stream_callback=callback) + stream.start_stream() + while stream.is_active(): + await asyncio.sleep(0) + stream.stop_stream() + stream.close() + p.terminate() + +## SSL +def create_ssl_cert(cert_path, key_path): + try: + proc = subprocess.run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \ + -days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"', + stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) + proc.check_returncode() + except subprocess.CalledProcessError as ex: + raise ValueError(f"Error creating SSL certificate:\n[stdout]\n{proc.stdout.decode()}\n[stderr]\n{proc.stderr.decode()}") from ex + + +def create_ssl_context(): + cert_path = os.path.join(TELEOPDIR, "cert.pem") + key_path = os.path.join(TELEOPDIR, "key.pem") + if not os.path.exists(cert_path) or not os.path.exists(key_path): + logger.info("Creating certificate...") + create_ssl_cert(cert_path, key_path) + else: + logger.info("Certificate exists!") + ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS_SERVER) + ssl_context.load_cert_chain(cert_path, key_path) + return ssl_context +## ENDPOINTS async def index(request): - content = open(TELEOPDIR + "/static/index.html", "r").read() - now = time.monotonic() - request.app['mutable_vals']['last_send_time'] = now - request.app['mutable_vals']['last_override_time'] = now - request.app['mutable_vals']['prev_command'] = [] - request.app['mutable_vals']['find_person'] = False - - return web.Response(content_type="text/html", text=content) - - -async def control_body(data, app): - now = time.monotonic() - if (data['type'] == 'dummy_controls') and (now < (app['mutable_vals']['last_send_time'] + 0.2)): - return - if (data['type'] == 'control_command') and (app['mutable_vals']['prev_command'] == [data['x'], data['y']] and data['x'] == 0 and data['y'] == 0): - return - - logger.info(str(data)) - x = max(-1.0, min(1.0, data['x'])) - y = max(-1.0, min(1.0, data['y'])) - dat = messaging.new_message('testJoystick') - dat.testJoystick.axes = [x, y] - dat.testJoystick.buttons = [False] - pm.send('testJoystick', dat) - app['mutable_vals']['last_send_time'] = now - if (data['type'] == 'control_command'): - app['mutable_vals']['last_override_time'] = now - app['mutable_vals']['prev_command'] = [data['x'], data['y']] - + with open(os.path.join(TELEOPDIR, "static", "index.html"), "r") as f: + content = f.read() + return web.Response(content_type="text/html", text=content) -async def dummy_controls_msg(app): - while True: - if 'last_send_time' in app['mutable_vals']: - this_time = time.monotonic() - if (app['mutable_vals']['last_send_time'] + 0.2) < this_time: - await control_body({'type': 'dummy_controls', 'x': 0, 'y': 0}, app) - await asyncio.sleep(0.2) +async def ping(request): + return web.Response(text="pong") -async def start_background_tasks(app): - app['bgtask_dummy_controls_msg'] = asyncio.create_task(dummy_controls_msg(app)) +async def sound(request): + params = await request.json() + sound_to_play = params["sound"] -async def stop_background_tasks(app): - app['bgtask_dummy_controls_msg'].cancel() - await app['bgtask_dummy_controls_msg'] + await play_sound(sound_to_play) + return web.json_response({"status": "ok"}) async def offer(request): - logger.info("\n\n\nnewoffer!\n\n") - params = await request.json() - offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) - speaker = WebClientSpeaker() - blackhole = MediaBlackhole() - - pc = RTCPeerConnection() - pc_id = "PeerConnection(%s)" % uuid.uuid4() - pcs.add(pc) - - def log_info(msg, *args): - logger.info(pc_id + " " + msg, *args) - - log_info("Created for %s", request.remote) - - @pc.on("datachannel") - def on_datachannel(channel): - request.app['mutable_vals']['remote_channel'] = channel - - @channel.on("message") - async def on_message(message): - data = json.loads(message) - if data['type'] == 'control_command': - await control_body(data, request.app) - times = { - 'type': 'ping_time', - 'incoming_time': data['dt'], - 'outgoing_time': int(time.time() * 1000), - } - channel.send(json.dumps(times)) - if data['type'] == 'battery_level': - sm.update(timeout=0) - if sm.updated['carState']: - channel.send(json.dumps({'type': 'battery_level', 'value': int(sm['carState'].fuelGauge * 100)})) - if data['type'] == 'play_sound': - logger.info(f"Playing sound: {data['sound']}") - await play_sound(data['sound']) - if data['type'] == 'find_person': - request.app['mutable_vals']['find_person'] = data['value'] - - @pc.on("connectionstatechange") - async def on_connectionstatechange(): - log_info("Connection state is %s", pc.connectionState) - if pc.connectionState == "failed": - await pc.close() - pcs.discard(pc) - - @pc.on('track') - def on_track(track): - logger.info(f"Track received: {track.kind}") - if track.kind == "audio": - speaker.addTrack(track) - elif track.kind == "video": - blackhole.addTrack(track) - - @track.on("ended") - async def on_ended(): - log_info("Remote %s track ended", track.kind) - if track.kind == "audio": - await speaker.stop() - elif track.kind == "video": - await blackhole.stop() - - video_sender = pc.addTrack(EncodedBodyVideo()) - force_codec(pc, video_sender, forced_codec='video/H264') - _ = pc.addTrack(BodyMic()) - - await pc.setRemoteDescription(offer) - await speaker.start() - await blackhole.start() - answer = await pc.createAnswer() - await pc.setLocalDescription(answer) - - return web.Response( - content_type="application/json", - text=json.dumps( - {"sdp": pc.localDescription.sdp, "type": pc.localDescription.type} - ), - ) - - -async def on_shutdown(app): - coros = [pc.close() for pc in pcs] - await asyncio.gather(*coros) - pcs.clear() - - -async def run(cmd): - proc = await asyncio.create_subprocess_shell( - cmd, - stdout=asyncio.subprocess.PIPE, - stderr=asyncio.subprocess.PIPE - ) - stdout, stderr = await proc.communicate() - logger.info("Created key and cert!") - if stdout: - logger.info(f'[stdout]\n{stdout.decode()}') - if stderr: - logger.info(f'[stderr]\n{stderr.decode()}') + body = StreamRequestBody(params["sdp"], ["driver"], ["testJoystick"], ["carState"]) + body_json = json.dumps(dataclasses.asdict(body)) + + logger.info("Sending offer to webrtcd...") + webrtcd_url = f"http://{WEBRTCD_HOST}:{WEBRTCD_PORT}/stream" + async with ClientSession() as session, session.post(webrtcd_url, data=body_json) as resp: + assert resp.status == 200 + answer = await resp.json() + return web.json_response(answer) def main(): - global pm, sm - pm = messaging.PubMaster(['testJoystick']) - sm = messaging.SubMaster(['carState', 'logMessage']) + # Enable joystick debug mode + Params().put_bool("JoystickDebugMode", True) + # App needs to be HTTPS for microphone and audio autoplay to work on the browser - cert_path = TELEOPDIR + '/cert.pem' - key_path = TELEOPDIR + '/key.pem' - if (not os.path.exists(cert_path)) or (not os.path.exists(key_path)): - asyncio.run(run(f'openssl req -x509 -newkey rsa:4096 -nodes -out {cert_path} -keyout {key_path} \ - -days 365 -subj "/C=US/ST=California/O=commaai/OU=comma body"')) - else: - logger.info("Certificate exists!") - ssl_context = ssl.SSLContext() - ssl_context.load_cert_chain(cert_path, key_path) + ssl_context = create_ssl_context() + app = web.Application() - app['mutable_vals'] = {} - app.on_shutdown.append(on_shutdown) - app.router.add_post("/offer", offer) app.router.add_get("/", index) - app.router.add_static('/static', TELEOPDIR + '/static') - app.on_startup.append(start_background_tasks) - app.on_cleanup.append(stop_background_tasks) + app.router.add_get("/ping", ping, allow_head=True) + app.router.add_post("/offer", offer) + app.router.add_post("/sound", sound) + app.router.add_static('/static', os.path.join(TELEOPDIR, 'static')) web.run_app(app, access_log=None, host="0.0.0.0", port=5000, ssl_context=ssl_context) diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 71e5ade9bb..f7f2fba45f 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -77,9 +77,10 @@ void MainWindow::loadFingerprints() { QFile json_file(QApplication::applicationDirPath() + "/dbc/car_fingerprint_to_dbc.json"); if (json_file.open(QIODevice::ReadOnly)) { fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); - auto dbc_names = fingerprint_to_dbc.object().toVariantMap().values(); - std::transform(dbc_names.begin(), dbc_names.end(), std::inserter(opendbc_names, opendbc_names.begin()), - [](const auto &name) { return name.toString(); }); + } + // get opendbc names + for (auto fn : QDir(OPENDBC_FILE_PATH).entryList({"*.dbc"}, QDir::Files, QDir::Name)) { + opendbc_names << QFileInfo(fn).baseName(); } } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 2b1c991271..1a889b88c2 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -86,7 +86,7 @@ protected: QProgressBar *progress_bar; QLabel *status_label; QJsonDocument fingerprint_to_dbc; - std::set opendbc_names; + QStringList opendbc_names; QSplitter *video_splitter = nullptr; enum { MAX_RECENT_FILES = 15 }; QAction *recent_files_acts[MAX_RECENT_FILES] = {}; diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc index f6884a2dc9..c7dc91e7aa 100644 --- a/tools/cabana/tests/test_cabana.cc +++ b/tools/cabana/tests/test_cabana.cc @@ -1,5 +1,7 @@ #undef INFO +#include + #include "catch2/catch.hpp" #include "tools/replay/logreader.h" #include "tools/cabana/dbc/dbcmanager.h" @@ -85,3 +87,17 @@ CM_ SG_ 160 signal_2 "multiple line comment REQUIRE(msg->sigs[1]->size == 1); REQUIRE(msg->sigs[1]->receiver_name == "XXX"); } + +TEST_CASE("parse_opendbc") { + QDir dir(OPENDBC_FILE_PATH); + QStringList errors; + for (auto fn : dir.entryList({"*.dbc"}, QDir::Files, QDir::Name)) { + try { + auto dbc = DBCFile(dir.filePath(fn)); + } catch (std::exception &e) { + errors.push_back(e.what()); + } + } + INFO(errors.join("\n").toStdString()); + REQUIRE(errors.empty()); +} diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index 82847e3fa1..c6024bf5e4 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -82,9 +82,6 @@ def send_thread(joystick): dat.testJoystick.buttons = [joystick.cancel] joystick_sock.send(dat.to_bytes()) print('\n' + ', '.join(f'{name}: {round(v, 3)}' for name, v in joystick.axes_values.items())) - if "WEB" in os.environ: - import requests - requests.get("http://"+os.environ["WEB"]+":5000/control/%f/%f" % tuple([joystick.axes_values[a] for a in joystick.axes_order][::-1]), timeout=None) rk.keep_time() def joystick_thread(joystick): @@ -101,7 +98,7 @@ if __name__ == '__main__': parser.add_argument('--gamepad', action='store_true', help='Use gamepad configuration instead of joystick') args = parser.parse_args() - if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ and "WEB" not in os.environ: + if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ: print("The car must be off before running joystickd.") exit() diff --git a/tools/lib/auth_config.py b/tools/lib/auth_config.py index bd76761043..c4e7b6261b 100644 --- a/tools/lib/auth_config.py +++ b/tools/lib/auth_config.py @@ -1,21 +1,15 @@ import json import os -from openpilot.common.file_helpers import mkdirs_exists_ok -from openpilot.system.hardware import PC +from openpilot.system.hardware.hw import Paths class MissingAuthConfigError(Exception): pass -if PC: - CONFIG_DIR = os.path.expanduser('~/.comma') -else: - CONFIG_DIR = "/tmp/.comma" - def get_token(): try: - with open(os.path.join(CONFIG_DIR, 'auth.json')) as f: + with open(os.path.join(Paths.config_root(), 'auth.json')) as f: auth = json.load(f) return auth['access_token'] except Exception: @@ -23,13 +17,13 @@ def get_token(): def set_token(token): - mkdirs_exists_ok(CONFIG_DIR) - with open(os.path.join(CONFIG_DIR, 'auth.json'), 'w') as f: + os.makedirs(Paths.config_root(), exist_ok=True) + with open(os.path.join(Paths.config_root(), 'auth.json'), 'w') as f: json.dump({'access_token': token}, f) def clear_token(): try: - os.unlink(os.path.join(CONFIG_DIR, 'auth.json')) + os.unlink(os.path.join(Paths.config_root(), 'auth.json')) except FileNotFoundError: pass diff --git a/tools/lib/cache.py b/tools/lib/cache.py index fd214f6bb5..d5fa288baa 100644 --- a/tools/lib/cache.py +++ b/tools/lib/cache.py @@ -1,12 +1,11 @@ import os import urllib.parse -from openpilot.common.file_helpers import mkdirs_exists_ok DEFAULT_CACHE_DIR = os.getenv("CACHE_ROOT", os.path.expanduser("~/.commacache")) def cache_path_for_file_path(fn, cache_dir=DEFAULT_CACHE_DIR): dir_ = os.path.join(cache_dir, "local") - mkdirs_exists_ok(dir_) + os.makedirs(dir_, exist_ok=True) fn_parsed = urllib.parse.urlparse(fn) if fn_parsed.scheme == '': cache_fn = os.path.abspath(fn).replace("/", "_") diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 315ade514b..a54552d241 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -5,7 +5,7 @@ import pycurl from hashlib import sha256 from io import BytesIO from tenacity import retry, wait_random_exponential, stop_after_attempt -from openpilot.common.file_helpers import mkdirs_exists_ok, atomic_write_in_dir +from openpilot.common.file_helpers import atomic_write_in_dir from openpilot.system.hardware.hw import Paths # Cache chunk size K = 1000 @@ -40,7 +40,7 @@ class URLFile: except AttributeError: self._curl = self._tlocal.curl = pycurl.Curl() if not self._force_download: - mkdirs_exists_ok(Paths.download_cache_root()) + os.makedirs(Paths.download_cache_root(), exist_ok=True) def __enter__(self): return self diff --git a/tools/replay/route.cc b/tools/replay/route.cc index 9168d25b35..d0ddf7f3c8 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -44,7 +44,7 @@ bool Route::loadFromServer() { loop.exit(success ? loadFromJson(json) : 0); }); - http.sendRequest("https://api.commadotai.com/v1/route/" + route_.str + "/files"); + http.sendRequest(CommaApi::BASE_URL + "/v1/route/" + route_.str + "/files"); return loop.exec(); } diff --git a/tools/sim/lib/camerad.py b/tools/sim/lib/camerad.py index 4621347787..cc6f0cd664 100644 --- a/tools/sim/lib/camerad.py +++ b/tools/sim/lib/camerad.py @@ -59,7 +59,7 @@ class Camerad: eof = int(frame_id * 0.05 * 1e9) self.vipc_server.send(yuv_type, yuv, frame_id, eof, eof) - dat = messaging.new_message(pub_type) + dat = messaging.new_message(pub_type, valid=True) msg = { "frameId": frame_id, "transform": [1.0, 0.0, 0.0, diff --git a/tools/sim/lib/simulated_sensors.py b/tools/sim/lib/simulated_sensors.py index 4520ed35ae..764f99f4d5 100644 --- a/tools/sim/lib/simulated_sensors.py +++ b/tools/sim/lib/simulated_sensors.py @@ -23,7 +23,7 @@ class SimulatedSensors: def send_imu_message(self, simulator_state: 'SimulatorState'): for _ in range(5): - dat = messaging.new_message('accelerometer') + dat = messaging.new_message('accelerometer', valid=True) dat.accelerometer.sensor = 4 dat.accelerometer.type = 0x10 dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp @@ -32,7 +32,7 @@ class SimulatedSensors: self.pm.send('accelerometer', dat) # copied these numbers from locationd - dat = messaging.new_message('gyroscope') + dat = messaging.new_message('gyroscope', valid=True) dat.gyroscope.sensor = 5 dat.gyroscope.type = 0x10 dat.gyroscope.timestamp = dat.logMonoTime # TODO: use the IMU timestamp @@ -53,7 +53,7 @@ class SimulatedSensors: ] for _ in range(10): - dat = messaging.new_message('gpsLocationExternal') + dat = messaging.new_message('gpsLocationExternal', valid=True) dat.gpsLocationExternal = { "unixTimestampMillis": int(time.time() * 1000), "flags": 1, # valid fix @@ -94,7 +94,7 @@ class SimulatedSensors: self.pm.send('driverStateV2', dat) # dmonitoringd output - dat = messaging.new_message('driverMonitoringState') + dat = messaging.new_message('driverMonitoringState', valid=True) dat.driverMonitoringState = { "faceDetected": True, "isDistracted": False, diff --git a/tools/sim/tests/test_sim_bridge.py b/tools/sim/tests/test_sim_bridge.py index b00527b322..2654526fa8 100644 --- a/tools/sim/tests/test_sim_bridge.py +++ b/tools/sim/tests/test_sim_bridge.py @@ -24,7 +24,7 @@ class TestSimBridgeBase(unittest.TestCase): p_manager = subprocess.Popen("./launch_openpilot.sh", cwd=SIM_DIR) self.processes.append(p_manager) - sm = messaging.SubMaster(['controlsState', 'carEvents', 'managerState']) + sm = messaging.SubMaster(['controlsState', 'onroadEvents', 'managerState']) q = Queue() carla_bridge = self.create_bridge() p_bridge = carla_bridge.run(q, retries=10) @@ -46,7 +46,7 @@ class TestSimBridgeBase(unittest.TestCase): sm.update() not_running = [p.name for p in sm['managerState'].processes if not p.running and p.shouldBeRunning] - car_event_issues = [event.name for event in sm['carEvents'] if any([event.noEntry, event.softDisable, event.immediateDisable])] + car_event_issues = [event.name for event in sm['onroadEvents'] if any([event.noEntry, event.softDisable, event.immediateDisable])] if sm.all_alive() and len(car_event_issues) == 0 and len(not_running) == 0: no_car_events_issues_once = True