diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 3b0cc29963..f04eaad86f 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -69,52 +69,62 @@ jobs: rm -rf /tmp/scons_cache/* && \ scons -j$(nproc) --cache-populate" - #build_mac: - # name: build macos - # runs-on: macos-10.15 - # timeout-minutes: 60 - # steps: - # - uses: actions/checkout@v2 - # with: - # submodules: true - # - name: Determine pre-existing Homebrew packages - # if: steps.dependency-cache.outputs.cache-hit != 'true' - # run: | - # echo 'EXISTING_CELLAR<> $GITHUB_ENV - # ls -1 /usr/local/Cellar >> $GITHUB_ENV - # echo 'EOF' >> $GITHUB_ENV - # - name: Cache dependencies - # id: dependency-cache - # uses: actions/cache@v2 - # with: - # path: | - # ~/.pyenv - # ~/Library/Caches/pip - # ~/Library/Caches/pipenv - # /usr/local/Cellar - # ~/github_brew_cache_entries.txt - # key: macos-cache-${{ hashFiles('tools/mac_setup.sh') }} - # - name: Brew link restored dependencies - # if: steps.dependency-cache.outputs.cache-hit == 'true' - # run: | - # while read pkg; do - # brew link --force "$pkg" # `--force` for keg-only packages - # done < ~/github_brew_cache_entries.txt - # - name: Install dependencies - # run: ./tools/mac_setup.sh - # - name: Build openpilot - # run: eval "$(pyenv init -)" && scons -j$(nproc) - # - name: Remove pre-existing Homebrew packages for caching - # if: steps.dependency-cache.outputs.cache-hit != 'true' - # run: | - # cd /usr/local/Cellar - # new_cellar=$(ls -1) - # comm -12 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | while read pkg; do - # if [[ $pkg != "zstd" ]]; then # caching step needs zstd - # rm -rf "$pkg" - # fi - # done - # comm -13 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | tee ~/github_brew_cache_entries.txt + build_mac: + name: build macos + runs-on: macos-latest + timeout-minutes: 60 + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - name: Determine pre-existing Homebrew packages + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: | + echo 'EXISTING_CELLAR<> $GITHUB_ENV + ls -1 /usr/local/Cellar >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + - name: Cache dependencies + id: dependency-cache + uses: actions/cache@v2 + with: + path: | + ~/.pyenv + ~/.local/share/virtualenvs/ + /usr/local/Cellar + ~/github_brew_cache_entries.txt + /tmp/scons_cache + key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'Pipfile*') }} + restore-keys: macos- + - name: Brew link restored dependencies + run: | + if [ -f ~/github_brew_cache_entries.txt ]; then + while read pkg; do + brew link --force "$pkg" # `--force` for keg-only packages + done < ~/github_brew_cache_entries.txt + else + echo "Cache entries not found" + fi + - name: Install dependencies + run: ./tools/mac_setup.sh + - name: Build openpilot + run: | + source tools/openpilot_env.sh + pipenv run selfdrive/manager/build.py + + # cleanup scons cache + rm -rf /tmp/scons_cache/ + pipenv run scons -j$(nproc) --cache-populate + - name: Remove pre-existing Homebrew packages for caching + if: steps.dependency-cache.outputs.cache-hit != 'true' + run: | + cd /usr/local/Cellar + new_cellar=$(ls -1) + comm -12 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | while read pkg; do + if [[ $pkg != "zstd" ]]; then # caching step needs zstd + rm -rf "$pkg" + fi + done + comm -13 <(echo "$EXISTING_CELLAR") <(echo "$new_cellar") | tee ~/github_brew_cache_entries.txt build_webcam: name: build webcam @@ -246,6 +256,7 @@ jobs: $UNIT_TEST selfdrive/locationd && \ $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ + $UNIT_TEST selfdrive/hardware/tici && \ $UNIT_TEST tools/lib/tests && \ ./selfdrive/boardd/tests/test_boardd_usbprotocol && \ ./selfdrive/common/tests/test_util && \ @@ -350,11 +361,12 @@ jobs: name: longitudinal path: selfdrive/test/longitudinal_maneuvers/out/longitudinal/ - test_car_models: - name: car models + test_cars: + name: cars runs-on: ubuntu-20.04 timeout-minutes: 50 strategy: + fail-fast: false matrix: job: [0, 1, 2, 3] steps: @@ -384,7 +396,7 @@ jobs: - name: Test car models run: | ${{ env.RUN }} "scons -j$(nproc) --test && \ - FILEREADER_CACHE=1 coverage run selfdrive/test/test_models.py && \ + FILEREADER_CACHE=1 pytest selfdrive/test/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" env: NUM_JOBS: 4 diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index b6b45bb24e..0b0df4c516 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -45,7 +45,20 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - lfs: true + + # HACK: cache LFS objects since they count against our quota + # https://github.com/actions/checkout/issues/165#issuecomment-657673315 + - name: Create LFS file list + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + - name: Restore LFS cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }} + - name: Git LFS Pull + run: git lfs pull + - name: Build Docker image run: | eval "$BUILD" diff --git a/.gitignore b/.gitignore index 3e87dcc433..06c6117b18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ +.env .clang-format .DS_Store .tags diff --git a/.lfsconfig b/.lfsconfig new file mode 100644 index 0000000000..42dfa2d944 --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,4 @@ +[lfs] + url = https://gitlab.com/commaai/openpilot-lfs.git/info/lfs + pushurl = ssh://git@gitlab.com/commaai/openpilot-lfs.git + locksverify = false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d32302e88c..65b60d95ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,14 +13,14 @@ repos: rev: v0.910-1 hooks: - id: mypy - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)/' additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', 'types-pycurl'] - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)|(selfdrive/debug)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/debug/)/' args: - --select=F,E112,E113,E304,E502,E701,E702,E703,E71,E72,E731,W191,W6 - --statistics @@ -31,7 +31,7 @@ repos: entry: pylint language: system types: [python] - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(laika_repo)|(rednose_repo)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)/' - repo: local hooks: - id: cppcheck @@ -39,7 +39,7 @@ repos: entry: cppcheck language: system types: [c++] - exclude: '^(third_party)|(pyextra)|(cereal)|(opendbc)|(panda)|(tools)|(selfdrive/modeld/thneed/debug)|(selfdrive/modeld/test)|(selfdrive/camerad/test)/|(installer)' + exclude: '^(third_party/)|(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)/|(installer/)' args: - --error-exitcode=1 - --language=c++ diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index f906b8f65d..2ea26b854f 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -1,70 +1,32 @@ FROM ubuntu:20.04 + ENV PYTHONUNBUFFERED 1 ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y --no-install-recommends \ - autoconf \ - build-essential \ - bzip2 \ - ca-certificates \ - capnproto \ - clang \ - cmake \ - cppcheck \ - curl \ - ffmpeg \ - gcc-arm-none-eabi \ - git \ - iputils-ping \ - libarchive-dev \ - libbz2-dev \ - libcapnp-dev \ - libcurl4-openssl-dev \ - libeigen3-dev \ - libffi-dev \ - libgles2-mesa-dev \ - libglew-dev \ - libglib2.0-0 \ - liblzma-dev \ - libomp-dev \ - libopencv-dev \ - libqt5sql5-sqlite \ - libqt5svg5-dev \ - libsqlite3-dev \ - libssl-dev \ - libsystemd-dev \ - libusb-1.0-0-dev \ - libzmq3-dev \ - locales \ - ocl-icd-libopencl1 \ - ocl-icd-opencl-dev \ - opencl-headers \ - python-dev \ - qml-module-qtquick2 \ - qt5-default \ - qtlocation5-dev \ - qtmultimedia5-dev \ - qtpositioning5-dev \ - qtwebengine5-dev \ - sudo \ - valgrind \ - wget \ - && rm -rf /var/lib/apt/lists/* +RUN apt-get update && \ + apt-get install -y --no-install-recommends sudo tzdata locales && \ + rm -rf /var/lib/apt/lists/* RUN sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && locale-gen ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 -RUN curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash -ENV PATH="/root/.pyenv/bin:/root/.pyenv/shims:${PATH}" +ENV PIPENV_SYSTEM=1 +ENV PYENV_VERSION=3.8.10 +ENV PYENV_ROOT="/root/.pyenv" +ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" + +COPY Pipfile Pipfile.lock .python-version update_requirements.sh /tmp/ +COPY tools/ubuntu_setup.sh /tmp/tools/ +RUN cd /tmp && \ + tools/ubuntu_setup.sh && \ + rm -rf /var/lib/apt/lists/* && \ + rm -rf /tmp/* && \ + rm -rf /root/.cache && \ + pip uninstall -y pipenv && \ -COPY Pipfile Pipfile.lock /tmp/ -RUN pyenv install 3.8.10 && \ - pyenv global 3.8.10 && \ - pyenv rehash && \ - pip install --no-cache-dir --upgrade pip==21.3.1 && \ - pip install --no-cache-dir pipenv==2021.5.29 && \ - cd /tmp && \ - pipenv install --system --deploy --dev --clear && \ - pip uninstall -y pipenv + # remove unused architectures from gcc for panda + cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ + rm -rf arm/ && \ + rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp diff --git a/Pipfile b/Pipfile index 2cf24e9cc7..bed4cdab73 100644 --- a/Pipfile +++ b/Pipfile @@ -25,16 +25,20 @@ pre-commit = "*" pycurl = "*" pygame = "*" pyprof2calltree = "*" +pytest = "*" +pytest-xdist = "*" reverse_geocoder = "*" scipy = "*" sphinx = "*" +sphinx-sitemap = "*" sphinx-rtd-theme = "*" +breathe = "*" subprocess32 = "*" tenacity = "*" [packages] atomicwrites = "*" -casadi = "*" +casadi = {version = "*", markers="platform_system != 'Darwin'"} cffi = "*" crcmod = "*" cryptography = "*" @@ -50,7 +54,7 @@ libusb1 = "*" nose = "*" numpy = "*" onnx = "*" -onnxruntime-gpu = "*" +onnxruntime-gpu = {version = "*", markers="platform_system != 'Darwin'"} pillow = "*" psutil = "*" pycapnp = "==1.1.0" diff --git a/Pipfile.lock b/Pipfile.lock index 67ea043bba..25cc81cbb2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "3b697db3796ed6f0954ee08eda0b01913add0b5aa83f9f0e484182923fdbc3c2" + "sha256": "40d9fa44e5b593786d40aa8a0276a1fbdfac46fbb78734687e6ef5b870813ed8" }, "pipfile-spec": 6, "requires": { @@ -25,11 +25,11 @@ }, "astroid": { "hashes": [ - "sha256:5939cf55de24b92bda00345d4d0659d01b3c7dafb5055165c330bc7c568ba273", - "sha256:776ca0b748b4ad69c00bfe0fff38fa2d21c338e12c84aa9715ee0d473c422778" + "sha256:72ace9c3333e274e9248168fc4f3e300da8545af1c303bd69197027f49e2bfff", + "sha256:aa296702f1a5c3102c860de49473aaa90a7f6d221555d5cf2678940a9be32a4e" ], - "markers": "python_version ~= '3.6'", - "version": "==2.9.0" + "markers": "python_full_version >= '3.6.2'", + "version": "==2.9.2" }, "atomicwrites": { "hashes": [ @@ -81,6 +81,7 @@ "sha256:fbf39dcd63f1d3b63c300fce59b7ea678bd5ea1d014e1e090a5226600a4132cb" ], "index": "pypi", + "markers": "platform_system != 'Darwin'", "version": "==3.5.5" }, "certifi": { @@ -148,11 +149,11 @@ }, "charset-normalizer": { "hashes": [ - "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", - "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" + "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd", + "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455" ], "markers": "python_version >= '3'", - "version": "==2.0.9" + "version": "==2.0.10" }, "click": { "hashes": [ @@ -174,72 +175,81 @@ }, "cryptography": { "hashes": [ - "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", - "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", - "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", - "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", - "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", - "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", - "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", - "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", - "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", - "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", - "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", - "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", - "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", - "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", - "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", - "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", - "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", - "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", - "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", - "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", - "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8" - ], - "index": "pypi", - "version": "==36.0.0" + "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", + "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31", + "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac", + "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf", + "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316", + "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca", + "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638", + "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94", + "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12", + "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173", + "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b", + "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a", + "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f", + "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2", + "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9", + "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46", + "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903", + "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3", + "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1", + "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee" + ], + "index": "pypi", + "version": "==36.0.1" }, "cython": { "hashes": [ - "sha256:07d5b8ce032110822dad2eb09950a98b9e255d14c2daf094be32d663790b3365", - "sha256:08a502fe08756070276d841c830cfc37254a2383d0a5bea736ffb78eff613c88", - "sha256:0cf7c3033349d10c5eb33ded1a78974f680e95c245a585c18a2046c67f8ed461", - "sha256:0e9e28eb6bb19f5e25f4bf5c8f8ea7db3bc4910309fab2305e5c9c5a5223db77", - "sha256:1825d6f2160188dfe1faa0099d30ed0e5ae56826627bf0de6dcb8dcbcf64c9bd", - "sha256:191978e5839ca425eb78f0f60a84ad5db7a07b97e8076f9853d0d12c3ccec5d4", - "sha256:1c2f262f7d032ec0106534982609ae0148f86ba52fc747df64e645706af20926", - "sha256:3379e67113e92fef490a88eca685b07b711bb4db1ddce66af9e460673a5335cc", - "sha256:3497e366ffed67454162d31bf4bd2ac3aa183dfac089eb4124966c9f98bd9c05", - "sha256:3913f6a50409ab36a5b8edbb4c3e4d441027f43150d8335e5118d34ef04c745c", - "sha256:3e94eb973f99c1963973a46dbd9e3974a03b8fe0af3de02dc5d65b4c6a6f9b3f", - "sha256:44cc749f288423182504a8fc8734070a369bf576734b9f0fafff40cd6b6e1b3e", - "sha256:4dc3d230849d61844e6b5737ee624c896f51e98c8a5d13f965b02a7e735230be", - "sha256:4ee99fab5191f403f33774fc92123291c002947338c2628b1ed42ed0017149dd", - "sha256:4f7b135cba0d2509890e1dcff2005585bc3d51c9f17564b70d8bc82dc7ec3a5e", - "sha256:5d0d97a5f661dccf2f9e14cf27fe9027f772d089fb92fdd3dd8a584d9b8a2916", - "sha256:64394ec94d9a0e5002f77e67ee8ceed97f25b483b18ea6aab547f4d82ca32ef6", - "sha256:6759b73a9a1013cbdac71ebefa284aa50617b5b32957a54eedaa22ac2f6d48de", - "sha256:6efb798993260532879f683dc8ce9e30fd1ec86f02c926f1238a8e6a64576321", - "sha256:79d2f84a6d87d45ef580c0441b5394c4f29344e05126a8e2fb4ba4144425f3b0", - "sha256:7b3f6e4cfcc103bccdcbc666f613d669ac378c8918629296cdf8191c0c2ec418", - "sha256:800cbe944886320e4a4b623becb97960ae9d7d80f2d12980b83bcfb63ff47d5b", - "sha256:8726456c7e376410b3c631427da0a4affe1e481424436d1e3f1888cc3c0f8d2e", - "sha256:a206a1f8ea11314e02dc01bf24f397b8f1b413bbcc0e031396caa1a126b060c2", - "sha256:a87cbe3756e7c464acf3e9420d8741e62d3b2eace0846cb39f664ad378aab284", - "sha256:aa9e1fe5ee0a4f9d2430c1e0665f40b48f4b511150ca02f69e9bb49dc48d4e0e", - "sha256:b5b3e876e617fe2cf466d02198b76924dcda3cc162a1043226a9c181b9a662a6", - "sha256:b6f397256cfab2d0f0af42659fca3232c23f5a570b6c21ed66aaac22dd95da15", - "sha256:b8fc9c78262b140364ce1b28ac40ff505a47ac3fd4f86311d461df04a28b3f23", - "sha256:c204cb2d005a426c5c83309fd7edea335ff5c514ffa6dc72ddac92cfde170b69", - "sha256:d288f25e8abb43b1cfa2fe3d69b2d6236cca3ff6163d090e26c4b1e8ea80dfbf", - "sha256:decd641167e97a3c1f973bf0bbb560d251809f6db8168c10edf94c0a1e5dec65", - "sha256:e6fa0a7cec9461c5ca687f3c4bb59cf2565afb76c60303b2dc8b280c6e112810", - "sha256:e96857ab2dbd8a67852341001f1f2a1ef3f1939d82aea1337497a8f76a9d7f6c", - "sha256:eb64ec369eba2207fbe618650d78d9af0455e0c1abb301ec024fa9f3e17a15cc", - "sha256:f95433e6963164de372fc1ef01574d7419d96ce45274f296299267d874b90800" - ], - "index": "pypi", - "version": "==0.29.25" + "sha256:0205b685802eb4c039b14f67b7ac3f00c55ff04b9e3871df2249576d3e59ba42", + "sha256:0c3093bc99facfc97e5019f6c5bc39987663792265c1334d9fc9e37c3a3dcd6f", + "sha256:0ffce25bf50fa926ec6bf8d6f29650e7cb33fae445938c9880e1ce9b776353ef", + "sha256:10402f0f1564ffc6ecb9c45e07f995771d05bb0b0e1d151e40574638292ee3a5", + "sha256:1519eb639de308f5763eb0666b4cc7bd3958268f3f6228cc610b7b4d6c94b68b", + "sha256:233a87db76941626f1db08f4b25a4a5b425b13b64ed0e673c3641f7b650a48d8", + "sha256:2b834ff6e4d10ba6d7a0d676dd71c1b427a181ddbbbbf79e91d1861557aab59f", + "sha256:362fbb9cb4627c7786231429768b54aaba5459a2a0e46c25e59f202ca6155437", + "sha256:3aed8c642e8fb27024bca46830b7f62335a44a92354acf708d6b8d050f945a3a", + "sha256:41ee918480371ae5e5851ba9b1ead5a183e24aedb27bf807c7405d124e943f40", + "sha256:4b7d04b393d9a4b5fec0cbc4b0f29fe083a9d862d95231a6e7608978bd661d7e", + "sha256:4d868e1a41f5123f51a20c1b8e82f7cb6fa3370c104e98e707f7c910e8cadad1", + "sha256:5041adfef502d67ecd5e291a7cf645a37fed7a9dac557f40d491053f35204d00", + "sha256:51923120f57a42c59f5ee6bba9e89a31a394ae8cd419c753f64d8a3aea1ee8b7", + "sha256:531303085503959338e6cdac630626280ef111aecbb22d48321673a8c3897c0a", + "sha256:5ecf5cf5b57086cc6c1cfc76d6353bbd7023e95da32e0883f1302ca50e481c33", + "sha256:5fd5db458c9d3d2c2abd047f3190624d9cce8a80a8e0ca0baa69cfd133a523bc", + "sha256:6773cce9d4b3b6168d8feb2b6f06b658ef1e11cbfec075041745666d8e2a5e45", + "sha256:6b385f68789c3e8a75b4827e8a4970ff04605ad3cb1c0b41005cc69368dad65d", + "sha256:706ea55f58c2722206e51cd9a8754ed0995c4c4231d24b095875d2641d745222", + "sha256:75eaa22911d2ec37a3841f77b710b178c805cd378b5e1c4fb82dbc35620d2062", + "sha256:77913fe27c5e22c995bac090d01e200ff91e5f58aa944e2d2e94cbf67ea2ae34", + "sha256:7df94e56872df8f396ca669466fee60256f69f678654239f706b1e643c2ac4a5", + "sha256:82881565d04027728d7762edd8c085927a840873af7ee049d703e0ca226bc08d", + "sha256:868f309095e557f06dc58723ae865e8c65cfedb2646a562bd8080c92d69e4e4b", + "sha256:8e07121b34221458a2151d37e137b8f5b011a9c51dd38db2499a6198590aa319", + "sha256:93840f2071c1f15e613509eadee1fbcd335e8666772437fe5038e24059edd48c", + "sha256:a1cc55db32cd39474081d478263b96e036552cdbbab8831c90ea43fb385a9b66", + "sha256:af377d543a762867da11fcf6e558f7a4a535ff8693f30cce123fab10c00fa312", + "sha256:af91dd63ac5f1f7fc70dc91ea063f727db42b5eb934d1f3832611be18e25171e", + "sha256:b3041e45aefaa4449fd671902132c0ac1f72eedaedac745c0e1a70a13bf990bb", + "sha256:b5ca05c2bfba0c2480b5fd390ecffe46b8da574d887d600388d6e3caf3f99a88", + "sha256:bbf0149680c1fca07200a3ed372b22e6bad7851d191b717a61f9a68af370e180", + "sha256:be13be1e2b9b7395588f2a23bfa692f2f3e6f7936ccf7825c83431b8c8c3452b", + "sha256:be550b566345bf53b95616334793ce42a128cf1d9dcde1e28cbf7ce52ea61d6d", + "sha256:c4b003b6b7aa9e74552ef8d4e6009b3e3c3e8fa585710b3a6d062e088e460c1b", + "sha256:c813799d533194b7d85203d881d8b4f567a8c644a67f50d47f1ffbf316df412f", + "sha256:c91b1ba0d43f7f7ccde8121c672207c7891735ddcc83496af1e0ab617ddc4aba", + "sha256:ca10e9fde0eaba1407ab353ff07a26daaa3e4dbe356108a149e482d441f070dd", + "sha256:ce804a021c92fea66c8c100781a111706f21bade7a546895c5a9c57fe2df8b24", + "sha256:d83dad8dc6c63706cb3178dc79010b3865b1345090727189d2cd61758a825ee8", + "sha256:e118525defec3f67471d8ee5ce04340d43195410a87e5d7ec8a1a9e953c0066a", + "sha256:ebe32e002a9e6553de399033e259ece72aa17c77f740b265e66f122572a8a278", + "sha256:ed76fb98979f02b5e89079906071983a36f3634d3028b86f935cf0196f24fcaa", + "sha256:f5e15ff892c8afad64931ee3dd723c4755c2c516606f9aae7613bebfac62b0f6", + "sha256:fec66cd0a48697fab903854566235aecf1084f62e3163d6589ae7335a1b4d448" + ], + "index": "pypi", + "version": "==0.29.26" }, "flake8": { "hashes": [ @@ -329,31 +339,46 @@ }, "lazy-object-proxy": { "hashes": [ - "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653", - "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61", - "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2", - "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837", - "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3", - "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43", - "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726", - "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3", - "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587", - "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8", - "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a", - "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd", - "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f", - "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad", - "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4", - "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b", - "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf", - "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981", - "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741", - "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e", - "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93", - "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'", - "version": "==1.6.0" + "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7", + "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a", + "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c", + "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc", + "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f", + "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09", + "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442", + "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e", + "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029", + "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61", + "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb", + "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0", + "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35", + "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42", + "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1", + "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad", + "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443", + "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd", + "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9", + "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148", + "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38", + "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55", + "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36", + "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a", + "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b", + "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44", + "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6", + "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69", + "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4", + "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84", + "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de", + "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28", + "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c", + "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1", + "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8", + "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b", + "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.1" }, "libusb1": { "hashes": [ @@ -465,39 +490,31 @@ }, "numpy": { "hashes": [ - "sha256:0b78ecfa070460104934e2caf51694ccd00f37d5e5dbe76f021b1b0b0d221823", - "sha256:1247ef28387b7bb7f21caf2dbe4767f4f4175df44d30604d42ad9bd701ebb31f", - "sha256:1403b4e2181fc72664737d848b60e65150f272fe5a1c1cbc16145ed43884065a", - "sha256:170b2a0805c6891ca78c1d96ee72e4c3ed1ae0a992c75444b6ab20ff038ba2cd", - "sha256:2e4ed57f45f0aa38beca2a03b6532e70e548faf2debbeb3291cfc9b315d9be8f", - "sha256:32fe5b12061f6446adcbb32cf4060a14741f9c21e15aaee59a207b6ce6423469", - "sha256:34f3456f530ae8b44231c63082c8899fe9c983fd9b108c997c4b1c8c2d435333", - "sha256:4c9c23158b87ed0e70d9a50c67e5c0b3f75bcf2581a8e34668d4e9d7474d76c6", - "sha256:5d95668e727c75b3f5088ec7700e260f90ec83f488e4c0aaccb941148b2cd377", - "sha256:615d4e328af7204c13ae3d4df7615a13ff60a49cb0d9106fde07f541207883ca", - "sha256:69077388c5a4b997442b843dbdc3a85b420fb693ec8e33020bb24d647c164fa5", - "sha256:74b85a17528ca60cf98381a5e779fc0264b4a88b46025e6bcbe9621f46bb3e63", - "sha256:81225e58ef5fce7f1d80399575576fc5febec79a8a2742e8ef86d7b03beef49f", - "sha256:8890b3360f345e8360133bc078d2dacc2843b6ee6059b568781b15b97acbe39f", - "sha256:92aafa03da8658609f59f18722b88f0a73a249101169e28415b4fa148caf7e41", - "sha256:9864424631775b0c052f3bd98bc2712d131b3e2cd95d1c0c68b91709170890b0", - "sha256:9e6f5f50d1eff2f2f752b3089a118aee1ea0da63d56c44f3865681009b0af162", - "sha256:a3deb31bc84f2b42584b8c4001c85d1934dbfb4030827110bc36bfd11509b7bf", - "sha256:ad010846cdffe7ec27e3f933397f8a8d6c801a48634f419e3d075db27acf5880", - "sha256:b1e2312f5b8843a3e4e8224b2b48fe16119617b8fc0a54df8f50098721b5bed2", - "sha256:bc988afcea53e6156546e5b2885b7efab089570783d9d82caf1cfd323b0bb3dd", - "sha256:c449eb870616a7b62e097982c622d2577b3dbc800aaf8689254ec6e0197cbf1e", - "sha256:c74c699b122918a6c4611285cc2cad4a3aafdb135c22a16ec483340ef97d573c", - "sha256:c885bfc07f77e8fee3dc879152ba993732601f1f11de248d4f357f0ffea6a6d4", - "sha256:e3c3e990274444031482a31280bf48674441e0a5b55ddb168f3a6db3e0c38ec8", - "sha256:e4799be6a2d7d3c33699a6f77201836ac975b2e1b98c2a07f66a38f499cb50ce", - "sha256:e6c76a87633aa3fa16614b61ccedfae45b91df2767cf097aa9c933932a7ed1e0", - "sha256:e89717274b41ebd568cd7943fc9418eeb49b1785b66031bc8a7f6300463c5898", - "sha256:f5162ec777ba7138906c9c274353ece5603646c6965570d82905546579573f73", - "sha256:fde96af889262e85aa033f8ee1d3241e32bf36228318a61f1ace579df4e8170d" - ], - "index": "pypi", - "version": "==1.21.4" + "sha256:0cfe07133fd00b27edee5e6385e333e9eeb010607e8a46e1cd673f05f8596595", + "sha256:11a1f3816ea82eed4178102c56281782690ab5993251fdfd75039aad4d20385f", + "sha256:2762331de395739c91f1abb88041f94a080cb1143aeec791b3b223976228af3f", + "sha256:283d9de87c0133ef98f93dfc09fad3fb382f2a15580de75c02b5bb36a5a159a5", + "sha256:3d22662b4b10112c545c91a0741f2436f8ca979ab3d69d03d19322aa970f9695", + "sha256:41388e32e40b41dd56eb37fcaa7488b2b47b0adf77c66154d6b89622c110dfe9", + "sha256:42c16cec1c8cf2728f1d539bd55aaa9d6bb48a7de2f41eb944697293ef65a559", + "sha256:47ee7a839f5885bc0c63a74aabb91f6f40d7d7b639253768c4199b37aede7982", + "sha256:5a311ee4d983c487a0ab546708edbdd759393a3dc9cd30305170149fedd23c88", + "sha256:5dc65644f75a4c2970f21394ad8bea1a844104f0fe01f278631be1c7eae27226", + "sha256:6ed0d073a9c54ac40c41a9c2d53fcc3d4d4ed607670b9e7b0de1ba13b4cbfe6f", + "sha256:76ba7c40e80f9dc815c5e896330700fd6e20814e69da9c1267d65a4d051080f1", + "sha256:818b9be7900e8dc23e013a92779135623476f44a0de58b40c32a15368c01d471", + "sha256:a024181d7aef0004d76fb3bce2a4c9f2e67a609a9e2a6ff2571d30e9976aa383", + "sha256:a955e4128ac36797aaffd49ab44ec74a71c11d6938df83b1285492d277db5397", + "sha256:a97a954a8c2f046d3817c2bce16e3c7e9a9c2afffaf0400f5c16df5172a67c9c", + "sha256:a97e82c39d9856fe7d4f9b86d8a1e66eff99cf3a8b7ba48202f659703d27c46f", + "sha256:b55b953a1bdb465f4dc181758570d321db4ac23005f90ffd2b434cc6609a63dd", + "sha256:bb02929b0d6bfab4c48a79bd805bd7419114606947ec8284476167415171f55b", + "sha256:bece0a4a49e60e472a6d1f70ac6cdea00f9ab80ff01132f96bd970cdd8a9e5a9", + "sha256:e41e8951749c4b5c9a2dc5fdbc1a4eec6ab2a140fdae9b460b0f557eed870f4d", + "sha256:f71d57cc8645f14816ae249407d309be250ad8de93ef61d9709b45a0ddf4050c" + ], + "index": "pypi", + "version": "==1.22.0" }, "onnx": { "hashes": [ @@ -532,72 +549,64 @@ }, "onnxruntime-gpu": { "hashes": [ - "sha256:064cd6ecbca26bac75299755240f6f6c47933598aa28a4b828fce6e58f49d7ae", - "sha256:123a0aa37026651d88c4f79dce0a104cd3bb7df2592a115f3a8e4b76d4cbda5f", - "sha256:206e04b34c92bfef432f7861a0deb42ca86b8ad9b0cae424ec1bd726bf38890a", - "sha256:b4924b4c7983ee981f76b0d2d9316edd0bf0386983a31c440771832fddbc72d0", - "sha256:cbca998cc4785dfc65f94bfb5af6ff45a1132e60e713e6c83ba755b55bd2b196", - "sha256:e17b5a33cdc209303a8b687c4f5ad06ef2f9bf2b32efe683b0e0c7de1858c97c", - "sha256:e23369b4b9367152f0f299a70543c9001e02667d7a202743f8351ab3a8b06c39", - "sha256:f3f37c105cd1c625069e7713a8627079e4691c0e61e9bec8d9e019be843d9783" + "sha256:1554ae7168cdecea27ac969f5731ca2ff636464117a8e1a3382a4c29510343b6", + "sha256:277b6bdca32ed049a8f387dbd9c871e1088cacede46be9f300c573a5b51526e5", + "sha256:2ffe8009eef07307a836e654a8449c177c0ef84ef29c4619d464d5ad8ab2d01b", + "sha256:8e4c52e6af7e2a10ed41e51986a247b91ef0e568f51c9d88e0eae181cfd04f62", + "sha256:aa00cba93c73117485031f990795ee5702644f7f2885f3d5bb4cdb683924ab6f", + "sha256:eadb86b76fbc0520cca811cde92f0031ebcd0f999df1abd80c4a6dc1941942f4", + "sha256:f1c5e09f3c2cf8d337f964455ab7c6131f911746a55b9f7297de4bc0e906d3fe", + "sha256:f6e056cd38265e1a158b72da234546291bb3cad663d81dcd66cbcbbce028e3a8" ], "index": "pypi", - "version": "==1.9.0" + "markers": "platform_system != 'Darwin'", + "version": "==1.10.0" }, "pillow": { "hashes": [ - "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76", - "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585", - "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b", - "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8", - "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55", - "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc", - "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645", - "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff", - "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc", - "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b", - "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6", - "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20", - "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e", - "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a", - "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779", - "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02", - "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39", - "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f", - "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a", - "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409", - "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c", - "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488", - "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b", - "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d", - "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09", - "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b", - "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153", - "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9", - "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad", - "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df", - "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df", - "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed", - "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed", - "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698", - "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29", - "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649", - "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49", - "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b", - "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2", - "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a", - "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78" - ], - "index": "pypi", - "version": "==8.4.0" + "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6", + "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc", + "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52", + "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4", + "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af", + "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315", + "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4", + "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281", + "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb", + "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9", + "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128", + "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105", + "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553", + "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5", + "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d", + "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6", + "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100", + "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce", + "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd", + "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05", + "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f", + "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f", + "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7", + "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f", + "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762", + "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379", + "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee", + "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925", + "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f", + "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f", + "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e", + "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4" + ], + "index": "pypi", + "version": "==9.0.0" }, "platformdirs": { "hashes": [ - "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", - "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca", + "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda" ], - "markers": "python_version >= '3.6'", - "version": "==2.4.0" + "markers": "python_version >= '3.7'", + "version": "==2.4.1" }, "protobuf": { "hashes": [ @@ -631,37 +640,36 @@ }, "psutil": { "hashes": [ - "sha256:0066a82f7b1b37d334e68697faba68e5ad5e858279fd6351c8ca6024e8d6ba64", - "sha256:02b8292609b1f7fcb34173b25e48d0da8667bc85f81d7476584d889c6e0f2131", - "sha256:0ae6f386d8d297177fd288be6e8d1afc05966878704dad9847719650e44fc49c", - "sha256:0c9ccb99ab76025f2f0bbecf341d4656e9c1351db8cc8a03ccd62e318ab4b5c6", - "sha256:0dd4465a039d343925cdc29023bb6960ccf4e74a65ad53e768403746a9207023", - "sha256:12d844996d6c2b1d3881cfa6fa201fd635971869a9da945cf6756105af73d2df", - "sha256:1bff0d07e76114ec24ee32e7f7f8d0c4b0514b3fae93e3d2aaafd65d22502394", - "sha256:245b5509968ac0bd179287d91210cd3f37add77dad385ef238b275bad35fa1c4", - "sha256:28ff7c95293ae74bf1ca1a79e8805fcde005c18a122ca983abf676ea3466362b", - "sha256:36b3b6c9e2a34b7d7fbae330a85bf72c30b1c827a4366a07443fc4b6270449e2", - "sha256:52de075468cd394ac98c66f9ca33b2f54ae1d9bff1ef6b67a212ee8f639ec06d", - "sha256:5da29e394bdedd9144c7331192e20c1f79283fb03b06e6abd3a8ae45ffecee65", - "sha256:61f05864b42fedc0771d6d8e49c35f07efd209ade09a5afe6a5059e7bb7bf83d", - "sha256:6223d07a1ae93f86451d0198a0c361032c4c93ebd4bf6d25e2fb3edfad9571ef", - "sha256:6323d5d845c2785efb20aded4726636546b26d3b577aded22492908f7c1bdda7", - "sha256:6ffe81843131ee0ffa02c317186ed1e759a145267d54fdef1bc4ea5f5931ab60", - "sha256:74f2d0be88db96ada78756cb3a3e1b107ce8ab79f65aa885f76d7664e56928f6", - "sha256:74fb2557d1430fff18ff0d72613c5ca30c45cdbfcddd6a5773e9fc1fe9364be8", - "sha256:90d4091c2d30ddd0a03e0b97e6a33a48628469b99585e2ad6bf21f17423b112b", - "sha256:90f31c34d25b1b3ed6c40cdd34ff122b1887a825297c017e4cbd6796dd8b672d", - "sha256:99de3e8739258b3c3e8669cb9757c9a861b2a25ad0955f8e53ac662d66de61ac", - "sha256:c6a5fd10ce6b6344e616cf01cc5b849fa8103fbb5ba507b6b2dee4c11e84c935", - "sha256:ce8b867423291cb65cfc6d9c4955ee9bfc1e21fe03bb50e177f2b957f1c2469d", - "sha256:d225cd8319aa1d3c85bf195c4e07d17d3cd68636b8fc97e6cf198f782f99af28", - "sha256:ea313bb02e5e25224e518e4352af4bf5e062755160f77e4b1767dd5ccb65f876", - "sha256:ea372bcc129394485824ae3e3ddabe67dc0b118d262c568b4d2602a7070afdb0", - "sha256:f4634b033faf0d968bb9220dd1c793b897ab7f1189956e1aa9eae752527127d3", - "sha256:fcc01e900c1d7bee2a37e5d6e4f9194760a93597c97fee89c4ae51701de03563" - ], - "index": "pypi", - "version": "==5.8.0" + "sha256:072664401ae6e7c1bfb878c65d7282d4b4391f1bc9a56d5e03b5a490403271b5", + "sha256:1070a9b287846a21a5d572d6dddd369517510b68710fca56b0e9e02fd24bed9a", + "sha256:1d7b433519b9a38192dfda962dd8f44446668c009833e1429a52424624f408b4", + "sha256:3151a58f0fbd8942ba94f7c31c7e6b310d2989f4da74fcbf28b934374e9bf841", + "sha256:3611e87eea393f779a35b192b46a164b1d01167c9d323dda9b1e527ea69d697d", + "sha256:3d00a664e31921009a84367266b35ba0aac04a2a6cad09c550a89041034d19a0", + "sha256:4e2fb92e3aeae3ec3b7b66c528981fd327fb93fd906a77215200404444ec1845", + "sha256:539e429da49c5d27d5a58e3563886057f8fc3868a5547b4f1876d9c0f007bccf", + "sha256:55ce319452e3d139e25d6c3f85a1acf12d1607ddedea5e35fb47a552c051161b", + "sha256:58c7d923dc209225600aec73aa2c4ae8ea33b1ab31bc11ef8a5933b027476f07", + "sha256:7336292a13a80eb93c21f36bde4328aa748a04b68c13d01dfddd67fc13fd0618", + "sha256:742c34fff804f34f62659279ed5c5b723bb0195e9d7bd9907591de9f8f6558e2", + "sha256:7641300de73e4909e5d148e90cc3142fb890079e1525a840cf0dfd39195239fd", + "sha256:76cebf84aac1d6da5b63df11fe0d377b46b7b500d892284068bacccf12f20666", + "sha256:7779be4025c540d1d65a2de3f30caeacc49ae7a2152108adeaf42c7534a115ce", + "sha256:7d190ee2eaef7831163f254dc58f6d2e2a22e27382b936aab51c835fc080c3d3", + "sha256:8293942e4ce0c5689821f65ce6522ce4786d02af57f13c0195b40e1edb1db61d", + "sha256:869842dbd66bb80c3217158e629d6fceaecc3a3166d3d1faee515b05dd26ca25", + "sha256:90a58b9fcae2dbfe4ba852b57bd4a1dded6b990a33d6428c7614b7d48eccb492", + "sha256:9b51917c1af3fa35a3f2dabd7ba96a2a4f19df3dec911da73875e1edaf22a40b", + "sha256:b2237f35c4bbae932ee98902a08050a27821f8f6dfa880a47195e5993af4702d", + "sha256:c3400cae15bdb449d518545cbd5b649117de54e3596ded84aacabfbb3297ead2", + "sha256:cb8d10461c1ceee0c25a64f2dd54872b70b89c26419e147a05a10b753ad36ec2", + "sha256:df2c8bd48fb83a8408c8390b143c6a6fa10cb1a674ca664954de193fdcab36a9", + "sha256:ea42d747c5f71b5ccaa6897b216a7dadb9f52c72a0fe2b872ef7d3e1eacf3ba3", + "sha256:ef216cc9feb60634bda2f341a9559ac594e2eeaadd0ba187a4c2eb5b5d40b91c", + "sha256:ff0d41f8b3e9ebb6b6110057e40019a432e96aae2008951121ba4e56040b84f3" + ], + "index": "pypi", + "version": "==5.9.0" }, "pycapnp": { "hashes": [ @@ -695,7 +703,6 @@ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.21" }, "pycryptodome": { @@ -760,16 +767,16 @@ }, "pyopencl": { "hashes": [ - "sha256:295213968ecffcc02ecbcf8b0ba18886c3d29312f66a64a6cdc04ee2efb09c66", - "sha256:6b2fc3ee18f154fff9a46d7a8d23ae5407ec0339282d57752fca55a23d6a3810", - "sha256:75a1f202741bace9606a8680bbbfac69bf8a73d4e7511fb1a6ce3e48185996ae", - "sha256:7b06f9612079195cfd14347712c52792d8f036d4c6f6831a194cc888ce3dd139", - "sha256:a10fbce5b10efa6fce4974958ad2adccee13a7ddf202cbcd5c3f23ef1b092f25", - "sha256:ba9f1a591f9770d656c9df3379c096e82867bbb10d1fb875ffb120e8926bd0ad", - "sha256:ea991b6e566c215dce0016c6765f2f9160a69d06c2f30ec92d99955b80701f2e" + "sha256:0a7cc1a461a4e57aa142b558b678fe23114aa6314d4a0c969bd2e2b5a02b65ad", + "sha256:2042811175bc8c915f5b56d8aa43561a5c62d6a145d67309e1e3f93d3b964744", + "sha256:334a6cdde7ef18e4370604b9a1d6551b055e8828a4da004893f26091669b561b", + "sha256:3678c8b02494e6d9627647c037aabed8244088d51cc6de8605f3854747985ac1", + "sha256:8bc2495cc4d78e8ca2358d3d14c5ba4b078cdbdb1a38e765a10c70e13df4871c", + "sha256:93f0c93e0fb6607ba0ee9bd11165edaf6406bf303b4ec795c3d2caa2e44f394e", + "sha256:e007e4e932170f0343cf1ab7733a2568aa8fda89f9a62e02aa359066084ee5c9" ], "index": "pypi", - "version": "==2021.2.10" + "version": "==2021.2.11" }, "pyserial": { "hashes": [ @@ -888,11 +895,11 @@ }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.1" }, "scons": { "hashes": [ @@ -904,11 +911,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", - "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" + "sha256:2a1757d6611e4bec7d672c2b7ef45afef79fed201d064f53994753303944f5a8", + "sha256:e4cb107e305b2c1b919414775fa73a9997f996447417d22b98e7610ded1e9eb5" ], "index": "pypi", - "version": "==1.5.0" + "version": "==1.5.1" }, "setproctitle": { "hashes": [ @@ -939,11 +946,11 @@ }, "setuptools": { "hashes": [ - "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", - "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" + "sha256:5c89b1a14a67ac5f0956f1cb0aeb7d1d3f4c8ba4e4e1ab7bf1af4933f9a2f0fe", + "sha256:675fcebecb43c32eb930481abf907619137547f4336206e4d673180242e1a278" ], - "markers": "python_version >= '3.6'", - "version": "==59.5.0" + "markers": "python_version >= '3.7'", + "version": "==60.2.0" }, "six": { "hashes": [ @@ -998,7 +1005,7 @@ "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" ], - "markers": "python_version >= '3.6'", + "markers": "python_version < '3.10'", "version": "==4.0.1" }, "urllib3": { @@ -1018,11 +1025,11 @@ }, "websocket-client": { "hashes": [ - "sha256:0133d2f784858e59959ce82ddac316634229da55b498aac311f1620567a710ec", - "sha256:8dfb715d8a992f5712fff8c843adae94e22b22a99b2c5e6b0ec4a1a981cc4e0d" + "sha256:1315816c0acc508997eb3ae03b9d3ff619c9d12d544c9a9b553704b1cc4f6af5", + "sha256:2eed4cc58e4d65613ed6114af2f380f7910ff416fc8c46947f6e76b6815f56c0" ], "index": "pypi", - "version": "==1.2.1" + "version": "==1.2.3" }, "werkzeug": { "hashes": [ @@ -1100,11 +1107,11 @@ }, "attrs": { "hashes": [ - "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1", - "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb" + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==21.2.0" + "version": "==21.4.0" }, "babel": { "hashes": [ @@ -1114,20 +1121,15 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.9.1" }, - "backports.entry-points-selectable": { - "hashes": [ - "sha256:7fceed9532a7aa2bd888654a7314f864a3c16a4e710b34a58cfc0f08114c663b", - "sha256:914b21a479fde881635f7af5adc7f6e38d6b274be32269070c53b698c60d5386" - ], - "markers": "python_version >= '2.7'", - "version": "==1.1.1" - }, "bcrypt": { "hashes": [ + "sha256:56e5da069a76470679f312a7d3d23deb3ac4519991a0361abc11da837087b61d", "sha256:5b93c1726e50a93a033c36e5ca7fdcd29a5c7395af50a6892f5d9e7c6cfbfb29", "sha256:63d4e3ff96188e5898779b6057878fecf3f11cfe6ec3b313ea09955d587ec7a7", "sha256:81fec756feff5b6818ea7ab031205e1d323d8943d237303baca2c5f9c7846f34", + "sha256:a0584a92329210fcd75eb8a3250c5a941633f8bfaf2a18f81009b097732839b7", "sha256:a67fb841b35c28a59cebed05fbd3e80eea26e6d75851f0574a9273c80f3e9b55", + "sha256:b589229207630484aefe5899122fb938a5b017b0f4349f769b8c13e78d99a8fd", "sha256:c95d4cbebffafcdd28bd28bb4e25b31c50f6da605c81ffd9ad8a3d1b2ab7b1b6", "sha256:cd1ea2ff3038509ea95f687256c46b79f5fc382ad0aa3664d200047546d511d1", "sha256:cdcdcb3972027f83fe24a48b1e90ea4b584d35f1cc279d76de6fc4b13376239d" @@ -1135,6 +1137,14 @@ "markers": "python_version >= '3.6'", "version": "==3.2.0" }, + "breathe": { + "hashes": [ + "sha256:19faef9d63c39acb3026eeaf6e6fdc5edb95334ed36fe0c627b358d6a2d5e0da", + "sha256:925eeff96c6640cd857e4ddeae6f75464a1d5e2e08ee56dccce4043583ae2050" + ], + "index": "pypi", + "version": "==4.31.0" + }, "certifi": { "hashes": [ "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872", @@ -1208,18 +1218,18 @@ }, "charset-normalizer": { "hashes": [ - "sha256:1eecaa09422db5be9e29d7fc65664e6c33bd06f9ced7838578ba40d58bdf3721", - "sha256:b0b883e8e874edfdece9c28f314e3dd5badf067342e42fb162203335ae61aa2c" + "sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd", + "sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455" ], "markers": "python_version >= '3'", - "version": "==2.0.9" + "version": "==2.0.10" }, "control": { "hashes": [ - "sha256:34eeca077cf002a2f22a9334c8998ec5b3bcc0fdae2aac790a923cf8bc80245a" + "sha256:8c9084bf386eafcf5d74008f780fae6dec68d243d18a380c866ac10a3549f8d3" ], "index": "pypi", - "version": "==0.9.0" + "version": "==0.9.1" }, "coverage": { "hashes": [ @@ -1276,30 +1286,29 @@ }, "cryptography": { "hashes": [ - "sha256:2049f8b87f449fc6190350de443ee0c1dd631f2ce4fa99efad2984de81031681", - "sha256:231c4a69b11f6af79c1495a0e5a85909686ea8db946935224b7825cfb53827ed", - "sha256:24469d9d33217ffd0ce4582dfcf2a76671af115663a95328f63c99ec7ece61a4", - "sha256:2deab5ec05d83ddcf9b0916319674d3dae88b0e7ee18f8962642d3cde0496568", - "sha256:494106e9cd945c2cadfce5374fa44c94cfadf01d4566a3b13bb487d2e6c7959e", - "sha256:4c702855cd3174666ef0d2d13dcc879090aa9c6c38f5578896407a7028f75b9f", - "sha256:52f769ecb4ef39865719aedc67b4b7eae167bafa48dbc2a26dd36fa56460507f", - "sha256:5c49c9e8fb26a567a2b3fa0343c89f5d325447956cc2fc7231c943b29a973712", - "sha256:684993ff6f67000a56454b41bdc7e015429732d65a52d06385b6e9de6181c71e", - "sha256:6fbbbb8aab4053fa018984bb0e95a16faeb051dd8cca15add2a27e267ba02b58", - "sha256:8982c19bb90a4fa2aad3d635c6d71814e38b643649b4000a8419f8691f20ac44", - "sha256:9511416e85e449fe1de73f7f99b21b3aa04fba4c4d335d30c486ba3756e3a2a6", - "sha256:97199a13b772e74cdcdb03760c32109c808aff7cd49c29e9cf4b7754bb725d1d", - "sha256:a776bae1629c8d7198396fd93ec0265f8dd2341c553dc32b976168aaf0e6a636", - "sha256:aa94d617a4cd4cdf4af9b5af65100c036bce22280ebb15d8b5262e8273ebc6ba", - "sha256:b17d83b3d1610e571fedac21b2eb36b816654d6f7496004d6a0d32f99d1d8120", - "sha256:d73e3a96c38173e0aa5646c31bf8473bc3564837977dd480f5cbeacf1d7ef3a3", - "sha256:d91bc9f535599bed58f6d2e21a2724cb0c3895bf41c6403fe881391d29096f1d", - "sha256:ef216d13ac8d24d9cd851776662f75f8d29c9f2d05cdcc2d34a18d32463a9b0b", - "sha256:f6a5a85beb33e57998dc605b9dbe7deaa806385fdf5c4810fb849fcd04640c81", - "sha256:f92556f94e476c1b616e6daec5f7ddded2c082efa7cee7f31c7aeda615906ed8" - ], - "index": "pypi", - "version": "==36.0.0" + "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3", + "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31", + "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac", + "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf", + "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316", + "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca", + "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638", + "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94", + "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12", + "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173", + "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b", + "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a", + "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f", + "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2", + "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9", + "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46", + "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903", + "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3", + "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1", + "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee" + ], + "index": "pypi", + "version": "==36.0.1" }, "cycler": { "hashes": [ @@ -1319,10 +1328,10 @@ }, "distlib": { "hashes": [ - "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31", - "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05" + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" ], - "version": "==0.3.3" + "version": "==0.3.4" }, "docutils": { "hashes": [ @@ -1332,6 +1341,14 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==0.17.1" }, + "execnet": { + "hashes": [ + "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5", + "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.9.0" + }, "fastcluster": { "hashes": [ "sha256:181af434d47c0628a98182f6d1483d0fd1da2a65ed4acd5f04f9bd1038098e63", @@ -1357,19 +1374,19 @@ }, "filelock": { "hashes": [ - "sha256:2e139a228bcf56dd8b2274a65174d005c4a6b68540ee0bdbb92c76f43f29f7e8", - "sha256:93d512b32a23baf4cac44ffd72ccf70732aeff7b8050fcaf6d3ec406d954baf4" + "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80", + "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146" ], - "markers": "python_version >= '3.6'", - "version": "==3.4.0" + "markers": "python_version >= '3.7'", + "version": "==3.4.2" }, "fonttools": { "hashes": [ - "sha256:ca6ecc67e5a5620d31754f92147f22f48fd5461fd3fafe6afe031aa9ee079b0f", - "sha256:edb48922873d3fda489ab400bd40888ac239ae8070b53f494b839bcdff0d01f6" + "sha256:545c05d0f7903a863c2020e07b8f0a57517f2c40d940bded77076397872d14ca", + "sha256:edf251d5d2cc0580d5f72de4621c338d8c66c5f61abb50cf486640f73c8194d5" ], "markers": "python_version >= '3.7'", - "version": "==4.28.3" + "version": "==4.28.5" }, "hexdump": { "hashes": [ @@ -1380,19 +1397,19 @@ }, "hypothesis": { "hashes": [ - "sha256:25e0e581f999281ff765aba3c3f1e3ce289e82a1ff1b9de2c5082b37113aa448", - "sha256:adb42a52bc3c1300d0a0a221870ce5c264bee2e54b0473de6767c3ae02649b5f" + "sha256:317f8d2f670fa69e258ab43e21c2befd413c559e386581f7e9641a80460b1063", + "sha256:803792d416ff71307d775fe760e2b2f07ca302a2c941b576629668092b9f3e3d" ], "index": "pypi", - "version": "==6.30.1" + "version": "==6.34.2" }, "identify": { "hashes": [ - "sha256:a33ae873287e81651c7800ca309dc1f84679b763c9c8b30680e16fbfa82f0107", - "sha256:eba31ca80258de6bb51453084bff4a923187cd2193b9c13710f2516ab30732cc" + "sha256:0192893ff68b03d37fed553e261d4a22f94ea974093aefb33b29df2ff35fed3c", + "sha256:64d4885e539f505dd8ffb5e93c142a1db45480452b1594cacd3e91dca9a984e9" ], "markers": "python_full_version >= '3.6.1'", - "version": "==2.4.0" + "version": "==2.4.1" }, "idna": { "hashes": [ @@ -1410,6 +1427,13 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==1.3.0" }, + "iniconfig": { + "hashes": [ + "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", + "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" + ], + "version": "==1.1.1" + }, "inputs": { "hashes": [ "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec", @@ -1485,11 +1509,11 @@ }, "markdown-it-py": { "hashes": [ - "sha256:36be6bb3ad987bfdb839f5ba78ddf094552ca38ccbd784ae4f74a4e1419fc6e3", - "sha256:98080fc0bc34c4f2bcf0846a096a9429acbd9d5d8e67ed34026c03c61c464389" + "sha256:15cc69c5b7c493ba8603722b710e39ce3fab2961994179fb4fa1c99b070d2059", + "sha256:c138a596f6c9988e0b5fa3299bc38ffa76c75076bc178e8dfac40a84343c7022" ], "index": "pypi", - "version": "==1.1.0" + "version": "==2.0.0" }, "markupsafe": { "hashes": [ @@ -1568,81 +1592,86 @@ }, "matplotlib": { "hashes": [ - "sha256:0abf8b51cc6d3ba34d1b15b26e329f23879848a0cf1216954c1f432ffc7e1af7", - "sha256:0e020a42f3338823a393dd2f80e39a2c07b9f941dfe2c778eb104eeb33d60bb5", - "sha256:13930a0c9bec0fd25f43c448b047a21af1353328b946f044a8fc3be077c6b1a8", - "sha256:153a0cf6a6ff4f406a0600d2034710c49988bacc6313d193b32716f98a697580", - "sha256:18f6e52386300db5cc4d1e9019ad9da2e80658bab018834d963ebb0aa5355095", - "sha256:2089b9014792dcc87bb1d620cde847913338abf7d957ef05587382b0cb76d44e", - "sha256:2eea16883aa7724c95eea0eb473ab585c6cf66f0e28f7f13e63deb38f4fd6d0f", - "sha256:38892a254420d95594285077276162a5e9e9c30b6da08bdc2a4d53331ad9a6fa", - "sha256:4b018ea6f26424a0852eb60eb406420d9f0d34f65736ea7bbfbb104946a66d86", - "sha256:65f877882b7ddede7090c7d87be27a0f4720fe7fc6fddd4409c06e1aa0f1ae8d", - "sha256:666d717a4798eb9c5d3ae83fe80c7bc6ed696b93e879cb01cb24a74155c73612", - "sha256:66b172610db0ececebebb09d146f54205f87c7b841454e408fba854764f91bdd", - "sha256:6db02c5605f063b67780f4d5753476b6a4944343284aa4e93c5e8ff6e9ec7f76", - "sha256:6e0e6b2111165522ad336705499b1f968c34a9e84d05d498ee5af0b5697d1efe", - "sha256:71a1851111f23f82fc43d2b6b2bfdd3f760579a664ebc939576fe21cc6133d01", - "sha256:7a7cb59ebd63a8ac4542ec1c61dd08724f82ec3aa7bb6b4b9e212d43c611ce3d", - "sha256:7baf23adb698d8c6ca7339c9dde00931bc47b2dd82fa912827fef9f93db77f5e", - "sha256:970aa97297537540369d05fe0fd1bb952593f9ab696c9b427c06990a83e2418b", - "sha256:9bac8eb1eccef540d7f4e844b6313d9f7722efd48c07e1b4bfec1056132127fd", - "sha256:a07ff2565da72a7b384a9e000b15b6b8270d81370af8a3531a16f6fbcee023cc", - "sha256:a0dcaf5648cecddc328e81a0421821a1f65a1d517b20746c94a1f0f5c36fb51a", - "sha256:a0ea10faa3bab0714d3a19c7e0921279a68d57552414d6eceaea99f97d7735db", - "sha256:a5b62d1805cc83d755972033c05cea78a1e177a159fc84da5c9c4ab6303ccbd9", - "sha256:a6cef5b31e27c31253c0f852b629a38d550ae66ec6850129c49d872f9ee428cb", - "sha256:a7bf8b05c214d32fb7ca7c001fde70b9b426378e897b0adbf77b85ea3569d56a", - "sha256:ac17a7e7b06ee426a4989f0b7f24ab1a592e39cdf56353a90f4e998bc0bf44d6", - "sha256:b3b687e905da32e5f2e5f16efa713f5d1fcd9fb8b8c697895de35c91fedeb086", - "sha256:b5e439d9e55d645f2a4dca63e2f66d68fe974c405053b132d61c7e98c25dfeb2", - "sha256:ba107add08e12600b072cf3c47aaa1ab85dd4d3c48107a5d3377d1bf80f8b235", - "sha256:d092b7ba63182d2dd427904e3eb58dd5c46ec67c5968de14a4b5007010a3a4cc", - "sha256:dc8c5c23e7056e126275dbf29efba817b3d94196690930d0968873ac3a94ab82", - "sha256:df0042cab69f4d246f4cb8fc297770ac4ae6ec2983f61836b04a117722037dcd", - "sha256:ee3d9ff16d749a9aa521bd7d86f0dbf256b2d2ac8ce31b19e4d2c86d2f2ff0b6", - "sha256:f23fbf70d2e80f4e03a83fc1206a8306d9bc50482fee4239f10676ce7e470c83", - "sha256:ff5d9fe518ad2de14ce82ab906b6ab5c2b0c7f4f984400ff8a7a905daa580a0a" - ], - "index": "pypi", - "version": "==3.5.0" + "sha256:14334b9902ec776461c4b8c6516e26b450f7ebe0b3ef8703bf5cdfbbaecf774a", + "sha256:2252bfac85cec7af4a67e494bfccf9080bcba8a0299701eab075f48847cca907", + "sha256:2e3484d8455af3fdb0424eae1789af61f6a79da0c80079125112fd5c1b604218", + "sha256:34a1fc29f8f96e78ec57a5eff5e8d8b53d3298c3be6df61e7aa9efba26929522", + "sha256:3e66497cd990b1a130e21919b004da2f1dc112132c01ac78011a90a0f9229778", + "sha256:40e0d7df05e8efe60397c69b467fc8f87a2affeb4d562fe92b72ff8937a2b511", + "sha256:456cc8334f6d1124e8ff856b42d2cc1c84335375a16448189999496549f7182b", + "sha256:506b210cc6e66a0d1c2bb765d055f4f6bc2745070fb1129203b67e85bbfa5c18", + "sha256:53273c5487d1c19c3bc03b9eb82adaf8456f243b97ed79d09dded747abaf1235", + "sha256:577ed20ec9a18d6bdedb4616f5e9e957b4c08563a9f985563a31fd5b10564d2a", + "sha256:6803299cbf4665eca14428d9e886de62e24f4223ac31ab9c5d6d5339a39782c7", + "sha256:68fa30cec89b6139dc559ed6ef226c53fd80396da1919a1b5ef672c911aaa767", + "sha256:6c094e4bfecd2fa7f9adffd03d8abceed7157c928c2976899de282f3600f0a3d", + "sha256:778d398c4866d8e36ee3bf833779c940b5f57192fa0a549b3ad67bc4c822771b", + "sha256:7a350ca685d9f594123f652ba796ee37219bf72c8e0fc4b471473d87121d6d34", + "sha256:87900c67c0f1728e6db17c6809ec05c025c6624dcf96a8020326ea15378fe8e7", + "sha256:8a77906dc2ef9b67407cec0bdbf08e3971141e535db888974a915be5e1e3efc6", + "sha256:8e70ae6475cfd0fad3816dcbf6cac536dc6f100f7474be58d59fa306e6e768a4", + "sha256:abf67e05a1b7f86583f6ebd01f69b693b9c535276f4e943292e444855870a1b8", + "sha256:b04fc29bcef04d4e2d626af28d9d892be6aba94856cb46ed52bcb219ceac8943", + "sha256:b19a761b948e939a9e20173aaae76070025f0024fc8f7ba08bef22a5c8573afc", + "sha256:b2e9810e09c3a47b73ce9cab5a72243a1258f61e7900969097a817232246ce1c", + "sha256:b71f3a7ca935fc759f2aed7cec06cfe10bc3100fadb5dbd9c435b04e557971e1", + "sha256:b8a4fb2a0c5afbe9604f8a91d7d0f27b1832c3e0b5e365f95a13015822b4cd65", + "sha256:bb1c613908f11bac270bc7494d68b1ef6e7c224b7a4204d5dacf3522a41e2bc3", + "sha256:d24e5bb8028541ce25e59390122f5e48c8506b7e35587e5135efcb6471b4ac6c", + "sha256:d70a32ee1f8b55eed3fd4e892f0286df8cccc7e0475c11d33b5d0a148f5c7599", + "sha256:e293b16cf303fe82995e41700d172a58a15efc5331125d08246b520843ef21ee", + "sha256:e2f28a07b4f82abb40267864ad7b3a4ed76f1b1663e81c7efc84a9b9248f672f", + "sha256:e3520a274a0e054e919f5b3279ee5dbccf5311833819ccf3399dab7c83e90a25", + "sha256:e3b6f3fd0d8ca37861c31e9a7cab71a0ef14c639b4c95654ea1dd153158bf0df", + "sha256:e486f60db0cd1c8d68464d9484fd2a94011c1ac8593d765d0211f9daba2bd535", + "sha256:e8c87cdaf06fd7b2477f68909838ff4176f105064a72ca9d24d3f2a29f73d393", + "sha256:edf5e4e1d5fb22c18820e8586fb867455de3b109c309cb4fce3aaed85d9468d1", + "sha256:fe8d40c434a8e2c68d64c6d6a04e77f21791a93ff6afe0dce169597c110d3079" + ], + "index": "pypi", + "version": "==3.5.1" }, "mdit-py-plugins": { "hashes": [ - "sha256:1833bf738e038e35d89cb3a07eb0d227ed647ce7dd357579b65343740c6d249c", - "sha256:5991cef645502e80a5388ec4fc20885d2313d4871e8b8e320ca2de14ac0c015f" + "sha256:b1279701cee2dbf50e188d3da5f51fee8d78d038cdf99be57c6b9d1aa93b4073", + "sha256:ecc24f51eeec6ab7eecc2f9724e8272c2fb191c2e93cf98109120c2cace69750" ], "markers": "python_version ~= '3.6'", - "version": "==0.2.8" + "version": "==0.3.0" + }, + "mdurl": { + "hashes": [ + "sha256:40654d6dcb8d21501ed13c21cc0bd6fc42ff07ceb8be30029e5ae63ebc2ecfda", + "sha256:94873a969008ee48880fb21bad7de0349fef529f3be178969af5817239e9b990" + ], + "markers": "python_version >= '3.6'", + "version": "==0.1.0" }, "mypy": { "hashes": [ - "sha256:088cd9c7904b4ad80bec811053272986611b84221835e079be5bcad029e79dd9", - "sha256:0aadfb2d3935988ec3815952e44058a3100499f5be5b28c34ac9d79f002a4a9a", - "sha256:119bed3832d961f3a880787bf621634ba042cb8dc850a7429f643508eeac97b9", - "sha256:1a85e280d4d217150ce8cb1a6dddffd14e753a4e0c3cf90baabb32cefa41b59e", - "sha256:3c4b8ca36877fc75339253721f69603a9c7fdb5d4d5a95a1a1b899d8b86a4de2", - "sha256:3e382b29f8e0ccf19a2df2b29a167591245df90c0b5a2542249873b5c1d78212", - "sha256:42c266ced41b65ed40a282c575705325fa7991af370036d3f134518336636f5b", - "sha256:53fd2eb27a8ee2892614370896956af2ff61254c275aaee4c230ae771cadd885", - "sha256:704098302473cb31a218f1775a873b376b30b4c18229421e9e9dc8916fd16150", - "sha256:7df1ead20c81371ccd6091fa3e2878559b5c4d4caadaf1a484cf88d93ca06703", - "sha256:866c41f28cee548475f146aa4d39a51cf3b6a84246969f3759cb3e9c742fc072", - "sha256:a155d80ea6cee511a3694b108c4494a39f42de11ee4e61e72bc424c490e46457", - "sha256:adaeee09bfde366d2c13fe6093a7df5df83c9a2ba98638c7d76b010694db760e", - "sha256:b6fb13123aeef4a3abbcfd7e71773ff3ff1526a7d3dc538f3929a49b42be03f0", - "sha256:b94e4b785e304a04ea0828759172a15add27088520dc7e49ceade7834275bedb", - "sha256:c0df2d30ed496a08de5daed2a9ea807d07c21ae0ab23acf541ab88c24b26ab97", - "sha256:c6c2602dffb74867498f86e6129fd52a2770c48b7cd3ece77ada4fa38f94eba8", - "sha256:ceb6e0a6e27fb364fb3853389607cf7eb3a126ad335790fa1e14ed02fba50811", - "sha256:d9dd839eb0dc1bbe866a288ba3c1afc33a202015d2ad83b31e875b5905a079b6", - "sha256:e4dab234478e3bd3ce83bac4193b2ecd9cf94e720ddd95ce69840273bf44f6de", - "sha256:ec4e0cd079db280b6bdabdc807047ff3e199f334050db5cbb91ba3e959a67504", - "sha256:ecd2c3fe726758037234c93df7e98deb257fd15c24c9180dacf1ef829da5f921", - "sha256:ef565033fa5a958e62796867b1df10c40263ea9ded87164d67572834e57a174d" - ], - "index": "pypi", - "version": "==0.910" + "sha256:0feb82e9fa849affca7edd24713dbe809dce780ced9f3feca5ed3d80e40b777f", + "sha256:1d2296f35aae9802eeb1327058b550371ee382d71374b3e7d2804035ef0b830b", + "sha256:1e689e92cdebd87607a041585f1dc7339aa2e8a9f9bad9ba7e6ece619431b20c", + "sha256:1ea7199780c1d7940b82dbc0a4e37722b4e3851264dbba81e01abecc9052d8a7", + "sha256:221cc94dc6a801ccc2be7c0c9fd791c5e08d1fa2c5e1c12dec4eab15b2469871", + "sha256:2e9c5409e9cb81049bb03fa1009b573dea87976713e3898561567a86c4eaee01", + "sha256:45a4dc21c789cfd09b8ccafe114d6de66f0b341ad761338de717192f19397a8c", + "sha256:51426262ae4714cc7dd5439814676e0992b55bcc0f6514eccb4cf8e0678962c2", + "sha256:554873e45c1ca20f31ddf873deb67fa5d2e87b76b97db50669f0468ccded8fae", + "sha256:5feb56f8bb280468fe5fc8e6f56f48f99aa0df9eed3c507a11505ee4657b5380", + "sha256:601f46593f627f8a9b944f74fd387c9b5f4266b39abad77471947069c2fc7651", + "sha256:70b197dd8c78fc5d2daf84bd093e8466a2b2e007eedaa85e792e513a820adbf7", + "sha256:959319b9a3cafc33a8185f440a433ba520239c72e733bf91f9efd67b0a8e9b30", + "sha256:a9d8dffefba634b27d650e0de2564379a1a367e2e08d6617d8f89261a3bf63b2", + "sha256:b419e9721260161e70d054a15abbd50603c16f159860cfd0daeab647d828fc29", + "sha256:bc1a0607ea03c30225347334af66b0af12eefba018a89a88c209e02b7065ea95", + "sha256:bf4a44e03040206f7c058d1f5ba02ef2d1820720c88bc4285c7d9a4269f54173", + "sha256:db3a87376a1380f396d465bed462e76ea89f838f4c5e967d68ff6ee34b785c31", + "sha256:ed4e0ea066bb12f56b2812a15ff223c57c0a44eca817ceb96b214bb055c7051f", + "sha256:f9f665d69034b1fcfdbcd4197480d26298bbfb5d2dfe206245b6498addb34999" + ], + "index": "pypi", + "version": "==0.930" }, "mypy-extensions": { "hashes": [ @@ -1653,11 +1682,11 @@ }, "myst-parser": { "hashes": [ - "sha256:40124b6f27a4c42ac7f06b385e23a9dcd03d84801e9c7130b59b3729a554b1f9", - "sha256:f7f3b2d62db7655cde658eb5d62b2ec2a4631308137bd8d10f296a40d57bbbeb" + "sha256:617a90ceda2162ebf81cd13ad17d879bd4f49e7fb5c4f177bb905272555a2268", + "sha256:a6473b9735c8c74959b49b36550725464f4aecc4481340c9a5f9153829191f83" ], "index": "pypi", - "version": "==0.15.2" + "version": "==0.16.1" }, "nodeenv": { "hashes": [ @@ -1668,75 +1697,44 @@ }, "numpy": { "hashes": [ - "sha256:0b78ecfa070460104934e2caf51694ccd00f37d5e5dbe76f021b1b0b0d221823", - "sha256:1247ef28387b7bb7f21caf2dbe4767f4f4175df44d30604d42ad9bd701ebb31f", - "sha256:1403b4e2181fc72664737d848b60e65150f272fe5a1c1cbc16145ed43884065a", - "sha256:170b2a0805c6891ca78c1d96ee72e4c3ed1ae0a992c75444b6ab20ff038ba2cd", - "sha256:2e4ed57f45f0aa38beca2a03b6532e70e548faf2debbeb3291cfc9b315d9be8f", - "sha256:32fe5b12061f6446adcbb32cf4060a14741f9c21e15aaee59a207b6ce6423469", - "sha256:34f3456f530ae8b44231c63082c8899fe9c983fd9b108c997c4b1c8c2d435333", - "sha256:4c9c23158b87ed0e70d9a50c67e5c0b3f75bcf2581a8e34668d4e9d7474d76c6", - "sha256:5d95668e727c75b3f5088ec7700e260f90ec83f488e4c0aaccb941148b2cd377", - "sha256:615d4e328af7204c13ae3d4df7615a13ff60a49cb0d9106fde07f541207883ca", - "sha256:69077388c5a4b997442b843dbdc3a85b420fb693ec8e33020bb24d647c164fa5", - "sha256:74b85a17528ca60cf98381a5e779fc0264b4a88b46025e6bcbe9621f46bb3e63", - "sha256:81225e58ef5fce7f1d80399575576fc5febec79a8a2742e8ef86d7b03beef49f", - "sha256:8890b3360f345e8360133bc078d2dacc2843b6ee6059b568781b15b97acbe39f", - "sha256:92aafa03da8658609f59f18722b88f0a73a249101169e28415b4fa148caf7e41", - "sha256:9864424631775b0c052f3bd98bc2712d131b3e2cd95d1c0c68b91709170890b0", - "sha256:9e6f5f50d1eff2f2f752b3089a118aee1ea0da63d56c44f3865681009b0af162", - "sha256:a3deb31bc84f2b42584b8c4001c85d1934dbfb4030827110bc36bfd11509b7bf", - "sha256:ad010846cdffe7ec27e3f933397f8a8d6c801a48634f419e3d075db27acf5880", - "sha256:b1e2312f5b8843a3e4e8224b2b48fe16119617b8fc0a54df8f50098721b5bed2", - "sha256:bc988afcea53e6156546e5b2885b7efab089570783d9d82caf1cfd323b0bb3dd", - "sha256:c449eb870616a7b62e097982c622d2577b3dbc800aaf8689254ec6e0197cbf1e", - "sha256:c74c699b122918a6c4611285cc2cad4a3aafdb135c22a16ec483340ef97d573c", - "sha256:c885bfc07f77e8fee3dc879152ba993732601f1f11de248d4f357f0ffea6a6d4", - "sha256:e3c3e990274444031482a31280bf48674441e0a5b55ddb168f3a6db3e0c38ec8", - "sha256:e4799be6a2d7d3c33699a6f77201836ac975b2e1b98c2a07f66a38f499cb50ce", - "sha256:e6c76a87633aa3fa16614b61ccedfae45b91df2767cf097aa9c933932a7ed1e0", - "sha256:e89717274b41ebd568cd7943fc9418eeb49b1785b66031bc8a7f6300463c5898", - "sha256:f5162ec777ba7138906c9c274353ece5603646c6965570d82905546579573f73", - "sha256:fde96af889262e85aa033f8ee1d3241e32bf36228318a61f1ace579df4e8170d" - ], - "index": "pypi", - "version": "==1.21.4" + "sha256:0cfe07133fd00b27edee5e6385e333e9eeb010607e8a46e1cd673f05f8596595", + "sha256:11a1f3816ea82eed4178102c56281782690ab5993251fdfd75039aad4d20385f", + "sha256:2762331de395739c91f1abb88041f94a080cb1143aeec791b3b223976228af3f", + "sha256:283d9de87c0133ef98f93dfc09fad3fb382f2a15580de75c02b5bb36a5a159a5", + "sha256:3d22662b4b10112c545c91a0741f2436f8ca979ab3d69d03d19322aa970f9695", + "sha256:41388e32e40b41dd56eb37fcaa7488b2b47b0adf77c66154d6b89622c110dfe9", + "sha256:42c16cec1c8cf2728f1d539bd55aaa9d6bb48a7de2f41eb944697293ef65a559", + "sha256:47ee7a839f5885bc0c63a74aabb91f6f40d7d7b639253768c4199b37aede7982", + "sha256:5a311ee4d983c487a0ab546708edbdd759393a3dc9cd30305170149fedd23c88", + "sha256:5dc65644f75a4c2970f21394ad8bea1a844104f0fe01f278631be1c7eae27226", + "sha256:6ed0d073a9c54ac40c41a9c2d53fcc3d4d4ed607670b9e7b0de1ba13b4cbfe6f", + "sha256:76ba7c40e80f9dc815c5e896330700fd6e20814e69da9c1267d65a4d051080f1", + "sha256:818b9be7900e8dc23e013a92779135623476f44a0de58b40c32a15368c01d471", + "sha256:a024181d7aef0004d76fb3bce2a4c9f2e67a609a9e2a6ff2571d30e9976aa383", + "sha256:a955e4128ac36797aaffd49ab44ec74a71c11d6938df83b1285492d277db5397", + "sha256:a97a954a8c2f046d3817c2bce16e3c7e9a9c2afffaf0400f5c16df5172a67c9c", + "sha256:a97e82c39d9856fe7d4f9b86d8a1e66eff99cf3a8b7ba48202f659703d27c46f", + "sha256:b55b953a1bdb465f4dc181758570d321db4ac23005f90ffd2b434cc6609a63dd", + "sha256:bb02929b0d6bfab4c48a79bd805bd7419114606947ec8284476167415171f55b", + "sha256:bece0a4a49e60e472a6d1f70ac6cdea00f9ab80ff01132f96bd970cdd8a9e5a9", + "sha256:e41e8951749c4b5c9a2dc5fdbc1a4eec6ab2a140fdae9b460b0f557eed870f4d", + "sha256:f71d57cc8645f14816ae249407d309be250ad8de93ef61d9709b45a0ddf4050c" + ], + "index": "pypi", + "version": "==1.22.0" }, "opencv-python-headless": { "hashes": [ - "sha256:01f76ca55fdb7e94c3e7eab5035376d06518155e3d88a08096e4670e57a0cee4", - "sha256:03349d9fb28703b2eaa8b1f333a6139b9849596ae4445cb1d76e2a7f5e4a2cf8", - "sha256:29f5372dabdcd571074f0539bd294a2f5a245a00b871827af6d75a971b3f657e", - "sha256:30261b87477a718993fa7cd8a44b7de986b81f8005e23110978c58fd53eb5e43", - "sha256:33e534fbc7a417a05ef6b14812fe8ff6b6b7152c22d502b61536c50ad63f80cb", - "sha256:3a8457918ecbca57669f141e7dba92e56af370876d022d75d58b94174d11e26b", - "sha256:4ef93f338b16e95418b69293924745a36f23e3d05da5ee10dde76af72b0889e3", - "sha256:5009a183be7a6817ff216dcb63ef95022c74e360011fa52aa33bc833256693b5", - "sha256:5331ce17a094bea4f8132ee23b2eaade85904199c0d04501102c9bb889302c67", - "sha256:659107ea6059b5cc953e1a32136a54998540cefea47b01dd62f1e806d10cbe39", - "sha256:6d949ec3e10cffa915ab1853e490674b8c420ba29eb0aeea72785f3e254dc7a1", - "sha256:6e7710aff3a0717f39c9ade77fdd9111203b09589539655044e73cc5d9960666", - "sha256:7b4bd3c6a0d2601b2619ae406eb85a41dff538a7c9cb2a54fb1e90631bf33887", - "sha256:7da49405e163b7a2cf891bf54a877ff3e198bc0bfe55009c1d19eb5a0153921d", - "sha256:7f8dd594ea0b0049d1614d7bfba984ebd926b2f12670edf6ae3d9d5d6ff8f8f0", - "sha256:8f8a06f75dc69631404e0846038d30ff43c9a9d60fcffe07c7a88f8b8c8c776c", - "sha256:99e678db353102119cbfe9d17aef520bacf585a3a287c4278dd1ce6fcd3be8f7", - "sha256:a1f9d41c6afe86fdbe85ac31ff9a6ce893af2d0fce68fbd1581dbbc0e4dfcb25", - "sha256:a1fd5bbf5db00432fb368c73e7d70ead13f69619b33e01dabf2906426a1a9277", - "sha256:a5461ad9789c784e75713d6c213c0e34b709073c71ec8ed94129419ea0ce7c01", - "sha256:a6ba305364df31b8ac8471a719371d0c05e1e5f7cc5b8a2295e7e958f9bc39bb", - "sha256:bbf37d5de98b09e7513e61fca6ebf6466fd82c3c2f0475e51d2a3c80e0bc1a92", - "sha256:bc9502064e8c3ff6f40b74c8a68fb31d0c9eae18c1d3f52d4e3f0ccda986f7cb", - "sha256:cdea7ab1698b69274eb69b16efdd7b16944c5019c06f0ace9530f91862496cf4", - "sha256:cdfec5dedd44617d94725170446cbe77c0b45044188bdc97cd251e698aeee822", - "sha256:db112fe9ffde7af96df09befcefdd33c4338f3a34fbfe894e04e66e14f584d9e", - "sha256:db461f2f0bfac155d56be7688ab6b43c140ce8b944aa5e6cfcb754bfeeeca750", - "sha256:dc303a5e09089001fd4fd51bd18a6d519e81ad5cbc36bb4b5fc3388d22a64be1", - "sha256:eb9e571427b7f44b8d8f9a3b6b7b25e45bc8e8895ed3cf3ecd917c0125cf3477", - "sha256:f4fbd431b2b0014b7d99e870f428eebf50a0149e4be1a72b905569aaadf4b540" - ], - "index": "pypi", - "version": "==4.5.4.60" + "sha256:12aa335156adf62efdaa6dc5966d6c3415a7e2834d336e4f10ee5fccc65202c8", + "sha256:359989d3aeb7b5d01f7f2f0445d448260b955274b7b1803f38e983eb85431e1f", + "sha256:5c716001e76ca356d775875f82576c310e9d7cd38d3979a2616eea5e44c8eccd", + "sha256:a4b21d055036460e2e1f5d97809c299c21790c59fb382fa2b9f6ef6113a97a68", + "sha256:bf84d8e98f6bf38f07fa0ef3a287e087e42fc0d5174ce05292ef322a96c1b4dc", + "sha256:d53f70229d23cd0e54de5b8730a79ae92d3d874eedda8c1effb376aa5a4e6ea6", + "sha256:e8a8c02ee060ae30a31be27fb65527c9698a6aa0fec967b49d6e56ea4473d6be" + ], + "index": "pypi", + "version": "==4.5.5.62" }, "packaging": { "hashes": [ @@ -1756,66 +1754,65 @@ }, "paramiko": { "hashes": [ - "sha256:7b5910f5815a00405af55da7abcc8a9e0d9657f57fcdd9a89894fdbba1c6b8a8", - "sha256:85b1245054e5d7592b9088cc6d08da22445417912d3a3e48138675c7a8616438" + "sha256:a1fdded3b55f61d23389e4fe52d9ae428960ac958d2edf50373faa5d8926edd0", + "sha256:db5d3f19607941b1c90233588d60213c874392c4961c6297037da989c24f8070" ], "index": "pypi", - "version": "==2.8.1" + "version": "==2.9.1" }, "pillow": { "hashes": [ - "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76", - "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585", - "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b", - "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8", - "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55", - "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc", - "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645", - "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff", - "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc", - "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b", - "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6", - "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20", - "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e", - "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a", - "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779", - "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02", - "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39", - "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f", - "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a", - "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409", - "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c", - "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488", - "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b", - "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d", - "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09", - "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b", - "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153", - "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9", - "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad", - "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df", - "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df", - "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed", - "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed", - "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698", - "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29", - "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649", - "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49", - "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b", - "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2", - "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a", - "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78" - ], - "index": "pypi", - "version": "==8.4.0" + "sha256:03b27b197deb4ee400ed57d8d4e572d2d8d80f825b6634daf6e2c18c3c6ccfa6", + "sha256:0b281fcadbb688607ea6ece7649c5d59d4bbd574e90db6cd030e9e85bde9fecc", + "sha256:0ebd8b9137630a7bbbff8c4b31e774ff05bbb90f7911d93ea2c9371e41039b52", + "sha256:113723312215b25c22df1fdf0e2da7a3b9c357a7d24a93ebbe80bfda4f37a8d4", + "sha256:2d16b6196fb7a54aff6b5e3ecd00f7c0bab1b56eee39214b2b223a9d938c50af", + "sha256:2fd8053e1f8ff1844419842fd474fc359676b2e2a2b66b11cc59f4fa0a301315", + "sha256:31b265496e603985fad54d52d11970383e317d11e18e856971bdbb86af7242a4", + "sha256:3586e12d874ce2f1bc875a3ffba98732ebb12e18fb6d97be482bd62b56803281", + "sha256:47f5cf60bcb9fbc46011f75c9b45a8b5ad077ca352a78185bd3e7f1d294b98bb", + "sha256:490e52e99224858f154975db61c060686df8a6b3f0212a678e5d2e2ce24675c9", + "sha256:500d397ddf4bbf2ca42e198399ac13e7841956c72645513e8ddf243b31ad2128", + "sha256:52abae4c96b5da630a8b4247de5428f593465291e5b239f3f843a911a3cf0105", + "sha256:6579f9ba84a3d4f1807c4aab4be06f373017fc65fff43498885ac50a9b47a553", + "sha256:68e06f8b2248f6dc8b899c3e7ecf02c9f413aab622f4d6190df53a78b93d97a5", + "sha256:6c5439bfb35a89cac50e81c751317faea647b9a3ec11c039900cd6915831064d", + "sha256:72c3110228944019e5f27232296c5923398496b28be42535e3b2dc7297b6e8b6", + "sha256:72f649d93d4cc4d8cf79c91ebc25137c358718ad75f99e99e043325ea7d56100", + "sha256:7aaf07085c756f6cb1c692ee0d5a86c531703b6e8c9cae581b31b562c16b98ce", + "sha256:80fe92813d208ce8aa7d76da878bdc84b90809f79ccbad2a288e9bcbeac1d9bd", + "sha256:95545137fc56ce8c10de646074d242001a112a92de169986abd8c88c27566a05", + "sha256:97b6d21771da41497b81652d44191489296555b761684f82b7b544c49989110f", + "sha256:98cb63ca63cb61f594511c06218ab4394bf80388b3d66cd61d0b1f63ee0ea69f", + "sha256:9f3b4522148586d35e78313db4db0df4b759ddd7649ef70002b6c3767d0fdeb7", + "sha256:a09a9d4ec2b7887f7a088bbaacfd5c07160e746e3d47ec5e8050ae3b2a229e9f", + "sha256:b5050d681bcf5c9f2570b93bee5d3ec8ae4cf23158812f91ed57f7126df91762", + "sha256:bb47a548cea95b86494a26c89d153fd31122ed65255db5dcbc421a2d28eb3379", + "sha256:bc462d24500ba707e9cbdef436c16e5c8cbf29908278af053008d9f689f56dee", + "sha256:c2067b3bb0781f14059b112c9da5a91c80a600a97915b4f48b37f197895dd925", + "sha256:d154ed971a4cc04b93a6d5b47f37948d1f621f25de3e8fa0c26b2d44f24e3e8f", + "sha256:d5dcea1387331c905405b09cdbfb34611050cc52c865d71f2362f354faee1e9f", + "sha256:ee6e2963e92762923956fe5d3479b1fdc3b76c83f290aad131a2f98c3df0593e", + "sha256:fd0e5062f11cb3e730450a7d9f323f4051b532781026395c4323b8ad055523c4" + ], + "index": "pypi", + "version": "==9.0.0" }, "platformdirs": { "hashes": [ - "sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2", - "sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d" + "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca", + "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda" + ], + "markers": "python_version >= '3.7'", + "version": "==2.4.1" + }, + "pluggy": { + "hashes": [ + "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", + "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" ], "markers": "python_version >= '3.6'", - "version": "==2.4.0" + "version": "==1.0.0" }, "pprofile": { "hashes": [ @@ -1832,12 +1829,19 @@ "index": "pypi", "version": "==2.16.0" }, + "py": { + "hashes": [ + "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719", + "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==1.11.0" + }, "pycparser": { "hashes": [ "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", "version": "==2.21" }, "pycurl": { @@ -1849,75 +1853,75 @@ }, "pygame": { "hashes": [ - "sha256:0227728f2ef751fac43b89f4bcc5c65ce39c855b2a3391ddf2e6024dd667e6bd", - "sha256:02a26b3be6cc478f18f4efa506ee5a585f68350857ac5e68e187301e943e3d6d", - "sha256:0d2f80b501aacd74a660d4422793ea1cd4e209bee385aac18d0a07bd671511ee", - "sha256:15d4e42214f93d8c60120e16b690ad03da7f0b3b66f75db8966bccf8c66c4690", - "sha256:232e51104db0e573221660d172af8e6fc2c0fda183c5dbf2aa52170f29aa9ec9", - "sha256:2bfefabe78bda7a1bfba253cbe2131038402ce2b32e4218feeba6431fe429abb", - "sha256:32cb64627c2eb5c4c067ffe614e08ccb8987d096100d225e070dddce05725b63", - "sha256:3804476fab6ec7230aa817ee5c3b378ba956321fdd5f91f51c97452c588869d2", - "sha256:38b5a43ab02c162501e62b857ff2cb128076b0786dd4e1d8bea63db8326f9da1", - "sha256:3d5a76fa826202182d989e8399fca0c3c163fbb4f8ece773e77955a7a62cbed3", - "sha256:472b81ba6b61ffe5879ac3d0da2e5cb235e0e4da471ad4038f013a7710ab53ab", - "sha256:49e5fb589a86169aa95b83d3429ee034799792374e13dbc0da83091d86365a4b", - "sha256:4ab5aba8677d135b94c4714e8256efdfffefc164f354a4d05b846588caf43b99", - "sha256:4eff1db92d53dc2e49ed832dd6c76530e1e2b5954eef091f6af41b41d2d5c3ac", - "sha256:4f73058569573af12c8181e032745f11d85f0799510965d938b1f16c7f13afcb", - "sha256:53c6fa767e3eef52d403eda5d032e48b6040ccce03fbd64af2f71843168118da", - "sha256:594234050b50b57c538842155dc3095c9d4f994266325adb4dd008aee526157f", - "sha256:59a5461ef317e4d233d1bb5ce63311ccad3e911a652bda159d3922351050158c", - "sha256:5a3edc8211d0cf39d1e4d7ded1a0727c53aeb21205963f184199521708bbb05c", - "sha256:5d36d530a8994c5bb8889816981f82b7942d8ec7651ca1d922d01302c1feecd2", - "sha256:5eb3dede55d005adea8504f8c9230b9dc2c84c1c728efe93a9718fa1af824dc8", - "sha256:646e871ff5ab7f933cde5ea2bff7b6cd74d7369f43e84a291baebe00bb9a8f6f", - "sha256:64ec45215c2cfc4051bb0f58d26aee3b50a39b1b0a2e6fe8417bb352a6443aad", - "sha256:692fe4498c353d663d45d05354fb47c9f6bf324d10b53844b9ed7f60e6c8cefa", - "sha256:6efa3fa472acb97c784224b59a89e80da6231f0dbf54df8442ffa3352c0534d6", - "sha256:70a11eec9bae6e8970c5bc4b3d0908eb2c42d4bd4ed488e41e49774b7cb41f57", - "sha256:7281366b4ebd7f16eac8ec6a6e2adb4c729beda178ea82637d9981e93dd40c9b", - "sha256:7a305dcf44f03a8dd7baefb97dc24949d7e719fd686cd3211121639aec4ce464", - "sha256:847b4bc22edb1d77c992b5d56b19e1ab52e14687adb8bc3ed12a8a98fbd7e1ff", - "sha256:85844714f82a5379100825473b1a7b24192b4a944aed3128da9386e26adc3bed", - "sha256:86c66b917afc6330a91ac8c7169c36c77ec536578d1d7724644d41f904e2d146", - "sha256:88a2dabe617e6173003b65762c636947719da3e2d881a4ea47298e8d70886386", - "sha256:9284e76923777c21b8bea19d8528be9cd62d0915139ed3c3cde6c43f849466f5", - "sha256:987c0d5fcd7737c31b35df06f78932c48eeff2c97473001e224fdebd3292b2db", - "sha256:9a81d057a7dea95850e44118f141a892fde93c938ccb08fbc5dd7f1a26c2f1fe", - "sha256:9b2ad10ffaa226ca40ae229143b0a118426aff42e2459b626d355846c59a765d", - "sha256:a0842458b49257ab539b7b6622a242cabcddcb61178b8ae074aaceb890be75b6", - "sha256:a0ab3e4763e0cebf08c55154f4167cdae3683674604a71e1437123225f2a9b36", - "sha256:ada3d33e7e6907d5c3bf771dc58c47ee6994a1e28fed55e4f8f8b817367beb8f", - "sha256:add546fcbf8954f00647f5e7d595ab9389f6a7542a99fc5dca514e14fd799773", - "sha256:b0e405fdde643f14d60c2dd140f110a5a38f588396a8b61a1a86374f25cba589", - "sha256:b0e96c0f68f6bb88da216765920c6dbc55ae83e70435d8ebac87d271fc058646", - "sha256:b400edd7391972e75b4243113089d6ea10b032e1306e8721efabb36d33c2d0f2", - "sha256:b545634f96132af1d31dcb873cf03a9c4a5654ae39d9ee126db0b2eba2806788", - "sha256:ba5bf655c892bbf4a9bafb4fcbc4c71023cc9a65f0cae0f3eba09a11018a858e", - "sha256:bb55368d455ab9518b97febd33a8d417988397b019c9408993be034e0b5a7db6", - "sha256:c1eb91198fc47c2e4fdc19c544b5d94534a70fd877f5c342228feb05e9fc4bef", - "sha256:c28c6f764aa03a0245db12346f1da327c6f49bcc20e53aefec6eed57e4fbe1ce", - "sha256:c6ee571995527e779b46cafee7ebef2dceb1a9c375143828e019293ff0efa167", - "sha256:c84a93e6d33dafce9e25080ac557342333e15ef7e378ba84cb6181c52a8fd663", - "sha256:d4061ac4e81bb36ec8f0a7027582c1c4dd32a939882e008165627103cb0b3985", - "sha256:d5c62fbdb30082f7e1dcfa253da48e7b4be7342d275b34b2efa51f6cffc5942b", - "sha256:e533f4bf9dc1a91cfd608b9bfb028c6a92383e731c502660933f0f9b812045a6", - "sha256:e9368c105a8bccc8adfe7fd7fa5220d2b6c03979a3a57a8178c42f6fa9914ebc", - "sha256:f628f9f26c8dadf72fabc9ae0ce5fe7f60d76be71a3407abc756b4d1fd030fa0", - "sha256:f8379052cfbc278b11e31bc97f2e7f5998959c50837c4d54f4e424a541e0c5d9", - "sha256:fad7b5351931cb68d19d7ecc0b21021fe23237d8fba8c455b5af4a79e1c7c536", - "sha256:fdd488daa4ad33748d5ea806e311bfe01b9cc506def5288400072fcd66d226cf" - ], - "index": "pypi", - "version": "==2.1.0" + "sha256:0427c103f741234336e5606d2fad86f5403c1a3d1dc55c309fbff3c984f0c9ae", + "sha256:07ca9f683075aea9bd977af9f09a720ebf747343d3ea8103e4f1735283b02330", + "sha256:0e06ae8e1c830f1b9c36a2bc6bb11de840232e95b78e2c349c6ed803a303be19", + "sha256:0e97d38308c441942577fea7fcd1326308bc56d6be6c024218e94d075d322e0f", + "sha256:119dee20c372c85dc47b717119534d15a60c64ceab8b0eb09278866d10486afe", + "sha256:1219a963941bd53aa754e8449364c142004fe706c33a9c22ff2a76521a82d078", + "sha256:1fddec8829e96424800c806582d73a5173b7d48946cccf7d035839ca09850db8", + "sha256:20676da24e3e3e6b9fc4eecc7ba09d77ef46c3a83a028763ba1d222476c2e3fb", + "sha256:2405414d8c572668e04739875661e030a0c588e197fa95463fe301c3d0a0510b", + "sha256:24254c4244f0d9bdc904f5d3f38e86757ca4c6aa0e44a6d55ef5e016bc7274d6", + "sha256:24b4f7f30fa2b3d092b60be6fcc725fb91d569fc87a9bcc91614ee8b0c005726", + "sha256:3bb0674aa789848ddc264bfc60c54965bf3bb659c141de4f600e379acc9b944c", + "sha256:3c8d6637ff75351e581327efefa9d04eeb0f257b533392b6cc6b15ceca4f7c5e", + "sha256:40e4d8d65985bb467d9c5a1305fb53fd6820c61d764979600becab973339676f", + "sha256:4aa3ae32320cc704d63e185864e44f6265c2a6e52c9384afe152cc3d51b3a2ef", + "sha256:50d9a21edd551669862c27c9272747401b20b1939abaacb842c08ea1cdd1c04d", + "sha256:5c7600bf307de1ca1dca0cc7840e34604d5b0b0a5a5dad345c3fa62b054b886d", + "sha256:5d0c14152d0ca8ef5fbcc5ed9981462bdf59a9ae85a291e62d8a8d0b7e5cbe43", + "sha256:5e88b0d4338b94960686f59396f23f7f684fed4859fcc3b9f40286d72c1c61af", + "sha256:5ebbefb8b576572c8fc97a3321d37dc2b4afea6b6e3877a67f7158d8c2c4cefe", + "sha256:636f51f56615d67459b11918206bb4da30cd7d7042027bf997c218ccd6c77902", + "sha256:660c80c0b2e80f1f801715583b759fb4c7bc0c11eb3b534e89c9fc4bfbc38acd", + "sha256:6ecda8dd4583982bb65f9c682f244a5e94524dcf628379766227e9ed97201a49", + "sha256:754c2906f2ef47173a14493e1de116b2a56a2c8e1764f1202ba844d080248a5b", + "sha256:7889dce887ec83c9a0bef8d9eb3669d8863fdaf37c45bacec707d8ad90b24a38", + "sha256:7fdb93b4282962c9a2ebf1af994ee698be823dd913218ed97a5f2fb372b10b66", + "sha256:8e87716114e97322fb177e223d889a4be369a0f73212f4f8507fe0cd43253b23", + "sha256:93c4cbfc942dd00410eaa9e84252129f9f9993f37f683006d7b49ab245342254", + "sha256:9649419254d3282dae41f23837de4108b17bc62187c3acd8af2ae3801b765cbd", + "sha256:97a74ba186deee68318a52637012ef6abf5be6282c659e1d1ba6ad08cf35ec85", + "sha256:9d6452419e01a0f848aed0597f69fd10a4c2a7750c15d1b0607f86090a39dcf3", + "sha256:9d7b021b8dde5d528363e474bc18bd6f79a9666eef89fb4859bcb8f0a536c9de", + "sha256:a0ccf8e3dce7ca67d523a6020b7e3dbf4b26797a9a8db5cc4c7b5ef20fb64701", + "sha256:a56a811d8821f7b9a594e3d0e0dd8bd39b25e3eea8963d5963263b90fd2ea5c2", + "sha256:c5ea87da5fe4b6164c3854f3b0c9146811dbad0dd7fa74297683dfacc485ae1c", + "sha256:c99b95e62cdda29c2e60235d7763447c168a6a877403e6f9ca5b2e2bb297c2ce", + "sha256:c9ce7f3d8af14d7e04eb7eb41c5e5313c43508c252bb2b9eb53e51fc87ada9fd", + "sha256:ca5ef1315fa67c241a657ab077be44f230c05740c95f0b46409457dceefdc7e5", + "sha256:d2d3c50ee9847b743db6cd7b1bb17a94c2c2abc16679d70f5e745cabdf19e655", + "sha256:d6d0eca28f886f0477cd0721ac688189155a587f2bb8eae740e52ca56c3ad23c", + "sha256:dad6bf3fdd3752d7519422f3732be779b98fe7c87d32c3efe2fdffdcbeebb6ca", + "sha256:db2f40d5a75fd9cdda473c58b0d8b294da6e0179f00bb3b1fc2f7f29cac09bea", + "sha256:dc4444d61d48c5546df5137cdf81554887ddb6e2ef1be7f51eb77ea3b6bdd56f", + "sha256:dcc285ee1f1d0e2672cc52f880fd3f564b1505de710e817f692fbf64a72ca657", + "sha256:dd528dbb91eca16f7522c975d0f9e94b95f6b5024c82c3247dc0383d242d33c6", + "sha256:e09044e9e1aa8512d6a9c7ce5f94b881824bcfc401105f3c24f546dfc3bb4aa5", + "sha256:e18c9466131378421d00fc40b637425229238d506a073d9c537b230b355a25d6", + "sha256:e1bb25986db77a48f632469c6bc61baf7508ce945aa6161c02180d4ee5ac5b8d", + "sha256:e4b4cd440d50a9f8551b8989e856aab175593af07eb825cad22fd2f8f6f2ffce", + "sha256:e627300a66a90651fb39e41601d447b1fdbbfffca3f08ef0278d6cc0436b2160", + "sha256:e7a8e18677e0064b7a422f6653a622652d932826a27e50f279d55a8b122a1a83", + "sha256:e8632f6b2ddb90f6f3950744bd65d5ef15af615e3034057fa30ff836f48a7179", + "sha256:ea36f4f93524554a35cac2359df63b50af6556ed866830aa1f07f0d8580280ea", + "sha256:f149e182d0eeef15d8a9b4c9dad1b87dc6eba3a99bd3c44a777a3a2b053a3dca", + "sha256:fc2e5db54491e8f27785fc5204c96f540d3557dcf5b0a9a857b6594d6b32561b", + "sha256:fc30e834f65b893d1b4c230070183bf98e6b70c41c1511687e8436a33d5ce49d", + "sha256:fcc9586e17875c0cdf8764597955f9daa979098fd4f80be07ed68276ac225480", + "sha256:ff961c3280d6ee5f4163f4772f963d7a4dbe42e36c6dd54b79ad436c1f046e5d" + ], + "index": "pypi", + "version": "==2.1.2" }, "pygments": { "hashes": [ - "sha256:b8e67fe6af78f492b3c4b3e2970c0624cbf08beb1e493b2c99b9fa1b67a20380", - "sha256:f398865f7eb6874156579fdf36bc840a03cab64d1cde9e93d68f46a425ec52c6" + "sha256:59b895e326f0fb0d733fd28c6839bd18ad0687ba20efc26d4277fd1d30b971f4", + "sha256:9135c1af61eec0f650cd1ea1ed8ce298e54d56bcd8cc2ef46edd7702c171337c" ], "markers": "python_version >= '3.5'", - "version": "==2.10.0" + "version": "==2.11.1" }, "pynacl": { "hashes": [ @@ -1958,6 +1962,30 @@ "index": "pypi", "version": "==1.4.5" }, + "pytest": { + "hashes": [ + "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89", + "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134" + ], + "index": "pypi", + "version": "==6.2.5" + }, + "pytest-forked": { + "hashes": [ + "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e", + "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8" + ], + "markers": "python_version >= '3.6'", + "version": "==1.4.0" + }, + "pytest-xdist": { + "hashes": [ + "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf", + "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65" + ], + "index": "pypi", + "version": "==2.5.0" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -2014,11 +2042,11 @@ }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61", + "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.1" }, "reverse-geocoder": { "hashes": [ @@ -2064,19 +2092,11 @@ }, "setuptools": { "hashes": [ - "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", - "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" + "sha256:5c89b1a14a67ac5f0956f1cb0aeb7d1d3f4c8ba4e4e1ab7bf1af4933f9a2f0fe", + "sha256:675fcebecb43c32eb930481abf907619137547f4336206e4d673180242e1a278" ], - "markers": "python_version >= '3.6'", - "version": "==59.5.0" - }, - "setuptools-scm": { - "hashes": [ - "sha256:4c64444b1d49c4063ae60bfe1680f611c8b13833d556fd1d6050c0023162a119", - "sha256:a49aa8081eeb3514eb9728fa5040f2eaa962d6c6f4ec9c32f6c1fba88f88a0f2" - ], - "markers": "python_version >= '3.6'", - "version": "==6.3.2" + "markers": "python_version >= '3.7'", + "version": "==60.2.0" }, "six": { "hashes": [ @@ -2102,11 +2122,11 @@ }, "sphinx": { "hashes": [ - "sha256:048dac56039a5713f47a554589dc98a442b39226a2b9ed7f82797fcb2fe9253f", - "sha256:32a5b3e9a1b176cc25ed048557d4d3d01af635e6b76c5bc7a43b0a34447fbd45" + "sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c", + "sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851" ], "index": "pypi", - "version": "==4.3.1" + "version": "==4.3.2" }, "sphinx-rtd-theme": { "hashes": [ @@ -2116,6 +2136,13 @@ "index": "pypi", "version": "==1.0.0" }, + "sphinx-sitemap": { + "hashes": [ + "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737" + ], + "index": "pypi", + "version": "==2.2.0" + }, "sphinxcontrib-applehelp": { "hashes": [ "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", @@ -2191,18 +2218,18 @@ }, "tomli": { "hashes": [ - "sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee", - "sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade" + "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224", + "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1" ], - "markers": "python_version >= '3.6'", - "version": "==1.2.2" + "markers": "python_version >= '3.7'", + "version": "==2.0.0" }, "typing-extensions": { "hashes": [ "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" ], - "markers": "python_version >= '3.6'", + "markers": "python_version < '3.10'", "version": "==4.0.1" }, "urllib3": { @@ -2215,11 +2242,11 @@ }, "virtualenv": { "hashes": [ - "sha256:4b02e52a624336eece99c96e3ab7111f469c24ba226a53ec474e8e787b365814", - "sha256:576d05b46eace16a9c348085f7d0dc8ef28713a2cabaa1cf0aea41e8f12c9218" + "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09", + "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==20.10.0" + "version": "==20.13.0" } } } diff --git a/RELEASES.md b/RELEASES.md index 79ad237b50..efbd5bf91c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,9 +1,28 @@ -Version 0.8.12 (2021-12-XX) +Version 0.8.13 (2022-XX-XX) ======================== + * Improved driver monitoring + * Improved camera focus on the comma two + * Subaru ECU firmware fingerprinting thanks to martinl! + * Hyundai Santa Fe Plug-in Hybrid 2022 support thanks to sunnyhaibin! + * Subaru Impreza 2020 support thanks to martinl! + +Version 0.8.12 (2021-12-15) +======================== + * New driving model + * Improved behavior around exits + * Better pose accuracy at high speeds, allowing max speed of 90mph + * Fully incorporated comma three data into all parts of training stack + * Improved follow distance + * Better longitudinal policy, especially in low speed traffic * New alert sounds + * AGNOS 3 + * Display burn in mitigation + * Improved audio amplifier configuration + * System reliability improvements + * Update Python to 3.8.10 + * Raw logs upload moved to connect.comma.ai + * Fixed HUD alerts on newer Honda Bosch thanks to csouers! * Audi Q3 2020-21 support thanks to jyoung8607! - * Honda Accord 2021 support thanks to csouers! - * Honda Accord Hybrid 2021 support thanks to csouers! * Lexus RC 2020 support thanks to ErichMoraga! Version 0.8.11 (2021-11-29) diff --git a/SConstruct b/SConstruct index 14450cec9f..832550b14b 100644 --- a/SConstruct +++ b/SConstruct @@ -66,7 +66,7 @@ lenv = { "LD_LIBRARY_PATH": [Dir(f"#third_party/acados/{arch}/lib").abspath], "PYTHONPATH": Dir("#").abspath + ":" + Dir("#pyextra/").abspath, - "ACADOS_SOURCE_DIR": Dir("#third_party/acados/acados").abspath, + "ACADOS_SOURCE_DIR": Dir("#third_party/acados/include/acados").abspath, "ACADOS_PYTHON_INTERFACE_PATH": Dir("#pyextra/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer", } @@ -125,14 +125,18 @@ else: f"#third_party/libyuv/{yuv_dir}/lib", "/usr/local/lib", "/opt/homebrew/lib", + "/usr/local/Homebrew/Library", "/usr/local/opt/openssl/lib", "/opt/homebrew/opt/openssl/lib", + "/usr/local/Cellar", + f"#third_party/acados/{arch}/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", ] cflags += ["-DGL_SILENCE_DEPRECATION"] cxxflags += ["-DGL_SILENCE_DEPRECATION"] cpppath += [ "/opt/homebrew/include", + "/usr/local/include", "/usr/local/opt/openssl/include", "/opt/homebrew/opt/openssl/include" ] @@ -234,6 +238,9 @@ env = Environment( tools=["default", "cython", "compilation_db"], ) +if arch == "Darwin": + env['RPATHPREFIX'] = "-rpath " + if GetOption('compile_db'): env.CompilationDatabase('compile_commands.json') @@ -298,6 +305,7 @@ if arch == "Darwin": qt_dirs += [f"{qt_env['QTDIR']}/include/Qt{m}" for m in qt_modules] qt_env["LINKFLAGS"] += ["-F" + os.path.join(qt_env['QTDIR'], "lib")] qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] + qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) elif arch == "aarch64": qt_env['QTDIR'] = "/system/comma/usr" qt_dirs = [ diff --git a/cereal b/cereal index e5a04ab458..42542ee96c 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit e5a04ab458afd52cf630cc9e35ccdc10efba6688 +Subproject commit 42542ee96ca00744e6117d57533defe6f01ba14d diff --git a/common/ffi_wrapper.py b/common/ffi_wrapper.py index e6ee2c23f7..a228b40256 100644 --- a/common/ffi_wrapper.py +++ b/common/ffi_wrapper.py @@ -28,7 +28,7 @@ def ffi_wrap(name, c_code, c_header, tmpdir="/tmp/ccache", cflags="", libraries= try: mod = __import__(cache) except Exception: - print("cache miss {0}".format(cache)) + print(f"cache miss {cache}") compile_code(cache, c_code, c_header, tmpdir, cflags, libraries) mod = __import__(cache) finally: diff --git a/common/file_helpers.py b/common/file_helpers.py index 2037a665b0..592b2a199a 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -35,7 +35,7 @@ def get_tmpdir_on_same_filesystem(path): if len(parts) > 1 and parts[1] == "scratch": return "/scratch/tmp" elif len(parts) > 2 and parts[2] == "runner": - return "/{}/runner/tmp".format(parts[1]) + return f"/{parts[1]}/runner/tmp" return "/tmp" @@ -81,6 +81,17 @@ def _get_fileobject_func(writer, temp_dir): return writer.get_fileobject(dir=temp_dir) return _get_fileobject +def monkeypatch_os_link(): + # This is neccesary on EON/C2, where os.link is patched out of python + if not hasattr(os, 'link'): + from cffi import FFI + ffi = FFI() + ffi.cdef("int link(const char *oldpath, const char *newpath);") + libc = ffi.dlopen(None) + + def link(src, dest): + return libc.link(src.encode(), dest.encode()) + os.link = link def atomic_write_on_fs_tmp(path, **kwargs): """Creates an atomic writer using a temporary file in a temporary directory @@ -88,6 +99,7 @@ def atomic_write_on_fs_tmp(path, **kwargs): """ # TODO(mgraczyk): This use of AtomicWriter relies on implementation details to set the temp # directory. + monkeypatch_os_link() writer = AtomicWriter(path, **kwargs) return writer._open(_get_fileobject_func(writer, get_tmpdir_on_same_filesystem(path))) @@ -96,5 +108,6 @@ def atomic_write_in_dir(path, **kwargs): """Creates an atomic writer using a temporary file in the same directory as the destination file. """ + monkeypatch_os_link() writer = AtomicWriter(path, **kwargs) return writer._open(_get_fileobject_func(writer, os.path.dirname(path))) diff --git a/common/profiler.py b/common/profiler.py index e4d208acae..8b1a7a8cfa 100644 --- a/common/profiler.py +++ b/common/profiler.py @@ -42,4 +42,4 @@ class Profiler(): print("%30s: %9.2f avg: %7.2f percent: %3.0f IGNORED" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) else: print("%30s: %9.2f avg: %7.2f percent: %3.0f" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) - print("Iter clock: %2.6f TOTAL: %2.2f" % (self.tot/self.iter, self.tot)) + print(f"Iter clock: {self.tot / self.iter:2.6f} TOTAL: {self.tot:2.2f}") diff --git a/common/realtime.py b/common/realtime.py index 762410f09e..d577680aee 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -39,7 +39,7 @@ def set_realtime_priority(level: int) -> None: def set_core_affinity(core: int) -> None: if not PC: - os.sched_setaffinity(0, [core,]) + os.sched_setaffinity(0, [core,]) # type: ignore[attr-defined] def config_realtime_process(core: int, priority: int) -> None: @@ -79,7 +79,7 @@ class Ratekeeper: remaining = self._next_frame_time - sec_since_boot() self._next_frame_time += self._interval if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold: - print("%s lagging by %.2f ms" % (self._process_name, -remaining * 1000)) + print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms") lagged = True self._frame += 1 self._remaining = remaining diff --git a/common/spinner.py b/common/spinner.py index 27b765196f..57242d644d 100644 --- a/common/spinner.py +++ b/common/spinner.py @@ -24,7 +24,7 @@ class Spinner(): except BrokenPipeError: pass - def update_progress(self, cur: int, total: int): + def update_progress(self, cur: float, total: float): self.update(str(round(100 * cur / total))) def close(self): diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index 344455ba6c..d39e66de13 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -8,7 +8,7 @@ from common.file_helpers import atomic_write_in_dir class TestFileHelpers(unittest.TestCase): def run_atomic_write_func(self, atomic_write_func): - path = "/tmp/tmp{}".format(uuid4()) + path = f"/tmp/tmp{uuid4()}" with atomic_write_func(path) as f: f.write("test") diff --git a/common/timeout.py b/common/timeout.py index 4d424cdc0a..d0b0ce0630 100644 --- a/common/timeout.py +++ b/common/timeout.py @@ -12,7 +12,7 @@ class Timeout: """ def __init__(self, seconds, error_msg=None): if error_msg is None: - error_msg = 'Timed out after {} seconds'.format(seconds) + error_msg = f'Timed out after {seconds} seconds' self.seconds = seconds self.error_msg = error_msg diff --git a/common/transformations/camera.py b/common/transformations/camera.py index d71f865ca9..7573877a3e 100644 --- a/common/transformations/camera.py +++ b/common/transformations/camera.py @@ -125,7 +125,7 @@ def normalize(img_pts, intrinsics=fcam_intrinsics): return img_pts_normalized[:, :2].reshape(input_shape) -def denormalize(img_pts, intrinsics=fcam_intrinsics, width=W, height=H): +def denormalize(img_pts, intrinsics=fcam_intrinsics, width=np.inf, height=np.inf): # denormalizes image coordinates # accepts single pt or array of pts img_pts = np.array(img_pts) @@ -133,10 +133,12 @@ def denormalize(img_pts, intrinsics=fcam_intrinsics, width=W, height=H): img_pts = np.atleast_2d(img_pts) img_pts = np.hstack((img_pts, np.ones((img_pts.shape[0], 1), dtype=img_pts.dtype))) img_pts_denormalized = img_pts.dot(intrinsics.T) - img_pts_denormalized[img_pts_denormalized[:, 0] > width] = np.nan - img_pts_denormalized[img_pts_denormalized[:, 0] < 0] = np.nan - img_pts_denormalized[img_pts_denormalized[:, 1] > height] = np.nan - img_pts_denormalized[img_pts_denormalized[:, 1] < 0] = np.nan + if np.isfinite(width): + img_pts_denormalized[img_pts_denormalized[:, 0] > width] = np.nan + img_pts_denormalized[img_pts_denormalized[:, 0] < 0] = np.nan + if np.isfinite(height): + img_pts_denormalized[img_pts_denormalized[:, 1] > height] = np.nan + img_pts_denormalized[img_pts_denormalized[:, 1] < 0] = np.nan return img_pts_denormalized[:, :2].reshape(input_shape) diff --git a/common/transformations/model.py b/common/transformations/model.py index 1215909fc4..5d6458fad3 100644 --- a/common/transformations/model.py +++ b/common/transformations/model.py @@ -84,6 +84,12 @@ bigmodel_frame_from_road_frame = np.dot(bigmodel_intrinsics, bigmodel_frame_from_calib_frame = np.dot(bigmodel_intrinsics, get_view_frame_from_calib_frame(0, 0, 0, 0)) +sbigmodel_frame_from_road_frame = np.dot(sbigmodel_intrinsics, + get_view_frame_from_road_frame(0, 0, 0, model_height)) + +sbigmodel_frame_from_calib_frame = np.dot(sbigmodel_intrinsics, + get_view_frame_from_calib_frame(0, 0, 0, 0)) + medmodel_frame_from_road_frame = np.dot(medmodel_intrinsics, get_view_frame_from_road_frame(0, 0, 0, model_height)) diff --git a/docs/CARS.md b/docs/CARS.md index 9c762ee7bf..1f641a0109 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -62,7 +62,7 @@ | Toyota | Highlander Hybrid 2020-22 | All | openpilot | 0mph | 0mph | | Toyota | Mirai 2021 | All | openpilot | 0mph | 0mph | | Toyota | Prius 2016-20 | TSS-P | Stock3| 0mph | 0mph | -| Toyota | Prius 2021 | All | openpilot | 0mph | 0mph | +| Toyota | Prius 2021-22 | All | openpilot | 0mph | 0mph | | Toyota | Prius Prime 2017-20 | All | Stock3| 0mph | 0mph | | Toyota | Prius Prime 2021-22 | All | openpilot | 0mph | 0mph | | Toyota | Rav4 2016-18 | TSS-P | Stock3| 20mph1 | 0mph | @@ -115,6 +115,7 @@ | Hyundai | Santa Fe 2019-20 | All | Stock | 0mph | 0mph | | Hyundai | Santa Fe 2021-22 | All | Stock | 0mph | 0mph | | Hyundai | Santa Fe Hybrid 2022 | All | Stock | 0mph | 0mph | +| Hyundai | Santa Fe Plug-in Hybrid 2022 | All | Stock | 0mph | 0mph | | Hyundai | Sonata 2018-2019 | SCC + LKAS | Stock | 0mph | 0mph | | Hyundai | Sonata Hybrid 2021-22 | All | Stock | 0mph | 0mph | | Hyundai | Veloster 2019-20 | SCC + LKAS | Stock | 5mph | 0mph | @@ -123,7 +124,7 @@ | Kia | Ceed 2019 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Forte 2018-21 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | K5 2021-22 | SCC + LFA | Stock | 0mph | 0mph | -| Kia | Niro EV 2019-21 | SCC + LKAS | Stock | 0mph | 0mph | +| Kia | Niro EV 2019-22 | All | Stock | 0mph | 0mph | | Kia | Niro Hybrid 2021 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Niro PHEV 2019 | SCC + LKAS | Stock | 10mph | 32mph | | Kia | Optima 2017 | SCC + LKAS | Stock | 0mph | 32mph | @@ -147,10 +148,10 @@ | Škoda | Scala 2020 | Driver Assistance | Stock | 0mph | 0mph | | Škoda | Superb 2015-18 | Driver Assistance | Stock | 0mph | 0mph | | Subaru | Ascent 2019 | EyeSight | Stock | 0mph | 0mph | -| Subaru | Crosstrek 2018-19 | EyeSight | Stock | 0mph | 0mph | +| Subaru | Crosstrek 2018-20 | EyeSight | Stock | 0mph | 0mph | | Subaru | Forester 2019-21 | EyeSight | Stock | 0mph | 0mph | | Subaru | Impreza 2017-19 | EyeSight | Stock | 0mph | 0mph | -| Volkswagen| Arteon 20214 | Driver Assistance | Stock | 0mph | 0mph | +| Volkswagen| Arteon 2018, 20214 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Atlas 2018-19 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| California 20214 | Driver Assistance | Stock | 0mph | 32mph | | Volkswagen| e-Golf 2014, 2019-20 | Driver Assistance | Stock | 0mph | 0mph | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8dcdeee509..7a074f12de 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,46 +1,48 @@ # How to contribute -Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. +Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available on our [blog](https://blog.comma.ai/). +Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available at https://docs.comma.ai and on our [blog](https://blog.comma.ai/). -## Getting Started +### Getting Started * Setup your [development environment](../tools/) * Join our [Discord](https://discord.comma.ai) * Make sure you have a [GitHub account](https://github.com/signup/free) * Fork [our repositories](https://github.com/commaai) on GitHub -## Testing +### First contribution +Try out some of these first pull requests ideas to dive into the codebase: -### Automated Testing +* Increase our [mypy](http://mypy-lang.org/) coverage +* Write some documentation +* Tackle an open [good first issue](https://github.com/commaai/openpilot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. +## Pull Requests -### Code Style and Linting +Pull requests should be against the master branch. Welcomed contributions include bug reports, car ports, and any [open issue](https://github.com/commaai/openpilot/issues). If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve. -Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. +A good pull request has all of the following: +* a clearly stated purpose +* every line changed directly contributes to the stated purpose +* verification, i.e. how did you test your PR? +* justification + * if you've optimized something, post benchmarks to prove it's better + * if you've improved your car's tuning, post before and after plots +* passes the CI tests -## Car Ports +### Car Ports We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) for porting to Toyota/Lexus models. If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). -## Pull Requests +## Testing -Pull requests should be against the master branch. Before running master on in-car hardware, you'll need to clone the submodules too. That can be done by recursively cloning the repository: -``` -git clone https://github.com/commaai/openpilot.git --recursive -``` -Or alternatively, when on the master branch: -``` -git submodule update --init -``` -The reasons for having submodules on a dedicated repository and our new development philosophy can be found in our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Modules that are in seperate repositories include: -* cereal -* laika -* opendbc -* panda -* rednose +### Automated Testing + +All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. + +### Code Style and Linting + +Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000..976929954f Binary files /dev/null and b/docs/_static/favicon.ico differ diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000..2699565085 Binary files /dev/null and b/docs/_static/logo.png differ diff --git a/docs/_static/robots.txt b/docs/_static/robots.txt new file mode 100644 index 0000000000..3bcd24fb5d --- /dev/null +++ b/docs/_static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: https://docs.comma.ai/sitemap.xml \ No newline at end of file diff --git a/docs/c_docs.rst b/docs/c_docs.rst new file mode 100644 index 0000000000..6cf5f268c5 --- /dev/null +++ b/docs/c_docs.rst @@ -0,0 +1,105 @@ +openpilot +========== + + +opendbc +------ +.. autodoxygenindex:: + :project: opendbc_can + + +cereal +------ + +messaging +^^^^^^^^^ +.. autodoxygenindex:: + :project: cereal_messaging + +visionipc +^^^^^^^^^ +.. autodoxygenindex:: + :project: cereal_visionipc + + +selfdrive +--------- + +camerad +^^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_camerad_cameras +.. autodoxygenindex:: + :project: selfdrive_camerad_transforms +.. autodoxygenindex:: + :project: selfdrive_camerad_imgproc + +locationd +^^^^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_locationd + +ui +^^ + +.. autodoxygenindex:: + :project: selfdrive_ui + +soundd +"""""" +.. autodoxygenindex:: + :project: selfdrive_ui_soundd + +navd +"""" +.. autodoxygenindex:: + :project: selfdrive_ui_navd + +replay +"""""" +.. autodoxygenindex:: + :project: selfdrive_ui_replay + +qt +"" +.. autodoxygenindex:: + :project: selfdrive_ui_qt_offroad +.. autodoxygenindex:: + :project: selfdrive_ui_qt_maps + +proclogd +^^^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_proclogd + +modeld +^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_modeld_transforms +.. autodoxygenindex:: + :project: selfdrive_modeld_models +.. autodoxygenindex:: + :project: selfdrive_modeld_thneed +.. autodoxygenindex:: + :project: selfdrive_modeld_runners + +common +^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_common + +sensorsd +^^^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_sensord_sensors + +boardd +^^^^^^ +.. autodoxygenindex:: + :project: selfdrive_boardd + + +rednose +------- +.. autodoxygenindex:: + :project: rednose_repo_rednose_helpers diff --git a/docs/conf.py b/docs/conf.py index 0d33637a14..5715a40b8b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,17 +14,24 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os +from os.path import exists import sys +from selfdrive.version import get_version +from common.basedir import BASEDIR sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, os.path.abspath('..')) +VERSION = get_version() # -- Project information ----------------------------------------------------- -project = 'openpilot' +project = 'openpilot docs' copyright = '2021, comma.ai' author = 'comma.ai' +version = VERSION +release = VERSION +language = 'en' # -- General configuration --------------------------------------------------- @@ -33,12 +40,39 @@ author = 'comma.ai' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', # Auto-generate docs - 'sphinx.ext.viewcode', # Add view code link to modules - 'sphinx_rtd_theme', # Read The Docs theme - 'myst_parser', # Markdown parsing + 'sphinx.ext.autodoc', # Auto-generate docs + 'sphinx.ext.viewcode', # Add view code link to modules + 'sphinx_rtd_theme', # Read The Docs theme + 'myst_parser', # Markdown parsing + 'breathe', # Doxygen C/C++ integration + 'sphinx_sitemap', # sitemap generation for SEO ] +myst_html_meta = { + "description": "openpilot docs", + "keywords": "op, openpilot, docs, documentation", + "robots": "all,follow", + "googlebot": "index,follow,snippet,archive", + "property=og:locale": "en_US", + "property=og:site_name": "docs.comma.ai", + "property=og:url": "https://docs.comma.ai", + "property=og:title": "openpilot Docuemntation", + "property=og:type": "website", + "property=og:image:type": "image/jpeg", + "property=og:image:width": "400", + "property=og:image": "https://docs.comma.ai/_static/logo.png", + "property=og:image:url": "https://docs.comma.ai/_static/logo.png", + "property=og:image:secure_url": "https://docs.comma.ai/_static/logo.png", + "property=og:description": "openpilot Documentation", + "property=twitter:card": "summary_large_image", + "property=twitter:logo": "https://docs.comma.ai/_static/logo.png", + "property=twitter:title": "openpilot Documentation", + "property=twitter:description": "openpilot Documentation" +} + +html_baseurl = 'https://docs.comma.ai/' +sitemap_filename = "sitemap.xml" + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -48,14 +82,65 @@ templates_path = ['_templates'] exclude_patterns = [] +# -- c docs configuration --------------------------------------------------- + +# Breathe Configuration +# breathe_default_project = "c_docs" +breathe_build_directory = f"{BASEDIR}/build/docs/html/xml" +breathe_separate_member_pages = True +breathe_default_members = ('members', 'private-members', 'undoc-members') +breathe_domain_by_extension = { + "h": "cc", +} +breathe_implementation_filename_extensions = ['.c', '.cc'] +breathe_doxygen_config_options = {} +breathe_projects_source = {} + +# only document files that have accompanying .cc files next to them +print("searching for c_docs...") +for root, dirs, files in os.walk(BASEDIR): + found = False + breath_src = {} + breathe_srcs_list = [] + + for file in files: + ccFile = os.path.join(root, file)[:-2] + ".cc" + + if file.endswith(".h") and exists(ccFile): + f = os.path.join(root, file) + + parent_dir_abs = os.path.dirname(f) + parent_dir = parent_dir_abs[len(BASEDIR) + 1:] + parent_project = parent_dir.replace('/', '_') + print(f"\tFOUND: {f} in {parent_project}") + + breathe_srcs_list.append(file) + found = True + + if found: + breath_src[parent_project] = (parent_dir_abs, breathe_srcs_list) + breathe_projects_source.update(breath_src) + +print(f"breathe_projects_source: {breathe_projects_source.keys()}") + # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'sphinx_rtd_theme' +html_show_copyright = True # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] +html_logo = '_static/logo.png' +html_favicon = '_static/favicon.ico' +html_theme_options = { + 'logo_only': False, + 'display_version': True, + 'vcs_pageview_mode': 'blob', + 'style_nav_header_background': '#000000', +} +html_extra_path = ['_static'] diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile index 3444e31a61..124feb1bfc 100644 --- a/docs/docker/Dockerfile +++ b/docs/docker/Dockerfile @@ -34,6 +34,7 @@ COPY ./*.md ${OPENPILOT_PATH}/ RUN scons -j$(nproc) +RUN apt update && apt install doxygen -y COPY ./docs ${OPENPILOT_PATH}/docs RUN git init . WORKDIR ${OPENPILOT_PATH}/docs diff --git a/docs/index.md b/docs/index.md index 5a171ae91d..0fb2617a5b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -28,8 +28,15 @@ overview.rst - {ref}`search` ```{toctree} -:caption: 'Modules' +:caption: 'Python API' :maxdepth: 2 modules.rst ``` + +```{toctree} +:caption: 'C/C++ API' +:maxdepth: 4 + +c_docs.rst +``` \ No newline at end of file diff --git a/launch_env.sh b/launch_env.sh index 47d98cc923..cb0a0572d0 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -11,7 +11,7 @@ if [ -z "$REQUIRED_NEOS_VERSION" ]; then fi if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="2" + export AGNOS_VERSION="3" fi if [ -z "$PASSIVE" ]; then diff --git a/models/supercombo.dlc b/models/supercombo.dlc index 7a92a33a67..2ebf4fa828 100644 --- a/models/supercombo.dlc +++ b/models/supercombo.dlc @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebb75bbd4c19994752f038f181359dce358a2c95c4ad1760d2d1ee346a45503d +oid sha256:209e9544e456dbc2a7d60490da65154e129bc84830909d8d931f97b3df93949b size 56684955 diff --git a/models/supercombo.onnx b/models/supercombo.onnx index 7aa47f83ad..17d233dad7 100644 --- a/models/supercombo.onnx +++ b/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e004a9ff90304fd3dfd661f3f52886affa677d486ff78c131d8a4887757b5f1 -size 57552499 +oid sha256:2365bae967cce21ce68707c30bf2981bb7081ee5c3e6a3dff793e660f23ff622 +size 57554657 diff --git a/opendbc b/opendbc index 6efaf246a9..e286083a49 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 6efaf246a90b513995a374a9939e4e051cf1b41c +Subproject commit e286083a49d9e386e778dcbcd41de4829a958735 diff --git a/panda b/panda index d1efddfaa9..f5857bc8fd 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit d1efddfaa9ebf0ef82db3ba2842f35ce22e06f7e +Subproject commit f5857bc8fd3e1d2c27df342df85e202c7e844911 diff --git a/release/files_common b/release/files_common index 3fc354396a..f2b10004b9 100644 --- a/release/files_common +++ b/release/files_common @@ -77,6 +77,7 @@ selfdrive/tombstoned.py selfdrive/pandad.py selfdrive/updated.py selfdrive/rtshield.py +selfdrive/statsd.py selfdrive/athena/__init__.py selfdrive/athena/athenad.py @@ -596,7 +597,6 @@ opendbc/lexus_rx_350_2016_pt_generated.dbc opendbc/lexus_rx_hybrid_2017_pt_generated.dbc opendbc/toyota_nodsu_pt_generated.dbc opendbc/toyota_nodsu_hybrid_pt_generated.dbc -opendbc/toyota_camry_hybrid_2018_pt_generated.dbc opendbc/toyota_highlander_2017_pt_generated.dbc opendbc/toyota_highlander_hybrid_2018_pt_generated.dbc opendbc/toyota_avalon_2017_pt_generated.dbc diff --git a/selfdrive/assets/sounds/disengage.wav b/selfdrive/assets/sounds/disengage.wav old mode 100755 new mode 100644 diff --git a/selfdrive/assets/sounds/engage.wav b/selfdrive/assets/sounds/engage.wav old mode 100755 new mode 100644 diff --git a/selfdrive/assets/sounds/prompt.wav b/selfdrive/assets/sounds/prompt.wav old mode 100755 new mode 100644 diff --git a/selfdrive/assets/sounds/prompt_distracted.wav b/selfdrive/assets/sounds/prompt_distracted.wav old mode 100755 new mode 100644 diff --git a/selfdrive/assets/sounds/refuse.wav b/selfdrive/assets/sounds/refuse.wav old mode 100755 new mode 100644 diff --git a/selfdrive/assets/sounds/warning_immediate.wav b/selfdrive/assets/sounds/warning_immediate.wav old mode 100755 new mode 100644 index 453f84855d..9f6f672e28 Binary files a/selfdrive/assets/sounds/warning_immediate.wav and b/selfdrive/assets/sounds/warning_immediate.wav differ diff --git a/selfdrive/assets/sounds/warning_soft.wav b/selfdrive/assets/sounds/warning_soft.wav old mode 100755 new mode 100644 diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index d196926611..a0c6a0c844 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -11,6 +11,7 @@ import select import socket import threading import time +import tempfile from collections import namedtuple from functools import partial from typing import Any @@ -31,10 +32,11 @@ from selfdrive.loggerd.config import ROOT from selfdrive.loggerd.xattr_cache import getxattr, setxattr from selfdrive.swaglog import cloudlog, SWAGLOG_DIR from selfdrive.version import get_version, get_origin, get_short_branch, get_commit +from selfdrive.statsd import STATS_DIR ATHENA_HOST = os.getenv('ATHENA_HOST', 'wss://athena.comma.ai') HANDLER_THREADS = int(os.getenv('HANDLER_THREADS', "4")) -LOCAL_PORT_WHITELIST = set([8022]) +LOCAL_PORT_WHITELIST = {8022} LOG_ATTR_NAME = 'user.upload' LOG_ATTR_VALUE_MAX_UNIX_TIME = int.to_bytes(2147483647, 4, sys.byteorder) @@ -48,7 +50,7 @@ dispatcher["echo"] = lambda s: s recv_queue: Any = queue.Queue() send_queue: Any = queue.Queue() upload_queue: Any = queue.Queue() -log_send_queue: Any = queue.Queue() +low_priority_send_queue: Any = queue.Queue() log_recv_queue: Any = queue.Queue() cancelled_uploads: Any = set() UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count', 'current', 'progress'], defaults=(0, False, 0)) @@ -56,6 +58,28 @@ UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', ' cur_upload_items = {} +class UploadQueueCache(): + params = Params() + + @staticmethod + def initialize(upload_queue): + try: + upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") + if upload_queue_json is not None: + for item in json.loads(upload_queue_json): + upload_queue.put(UploadItem(**item)) + except Exception: + cloudlog.exception("athena.UploadQueueCache.initialize.exception") + + @staticmethod + def cache(upload_queue): + try: + items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads] + UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) + except Exception: + cloudlog.exception("athena.UploadQueueCache.cache.exception") + + def handle_long_poll(ws): end_event = threading.Event() @@ -64,6 +88,7 @@ def handle_long_poll(ws): threading.Thread(target=ws_send, args=(ws, end_event), name='ws_send'), threading.Thread(target=upload_handler, args=(end_event,), name='upload_handler'), threading.Thread(target=log_handler, args=(end_event,), name='log_handler'), + threading.Thread(target=stat_handler, args=(end_event,), name='stat_handler'), ] + [ threading.Thread(target=jsonrpc_handler, args=(end_event,), name=f'worker_{x}') for x in range(HANDLER_THREADS) @@ -111,6 +136,7 @@ def upload_handler(end_event): try: cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True) + if cur_upload_items[tid].id in cancelled_uploads: cancelled_uploads.remove(cur_upload_items[tid].id) continue @@ -120,6 +146,7 @@ def upload_handler(end_event): cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1) _do_upload(cur_upload_items[tid], cb) + UploadQueueCache.cache(upload_queue) except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError) as e: cloudlog.warning(f"athena.upload_handler.retry {e} {cur_upload_items[tid]}") @@ -131,6 +158,8 @@ def upload_handler(end_event): current=False ) upload_queue.put_nowait(item) + UploadQueueCache.cache(upload_queue) + cur_upload_items[tid] = None for _ in range(RETRY_DELAY): @@ -237,19 +266,35 @@ def reboot(): @dispatcher.add_method def uploadFileToUrl(fn, url, headers): - if len(fn) == 0 or fn[0] == '/' or '..' in fn: - return 500 - path = os.path.join(ROOT, fn) - if not os.path.exists(path): - return 404 + return uploadFilesToUrls([[fn, url, headers]]) + + +@dispatcher.add_method +def uploadFilesToUrls(files_data): + items = [] + failed = [] + for fn, url, headers in files_data: + if len(fn) == 0 or fn[0] == '/' or '..' in fn: + failed.append(fn) + continue + path = os.path.join(ROOT, fn) + if not os.path.exists(path): + failed.append(fn) + continue - item = UploadItem(path=path, url=url, headers=headers, created_at=int(time.time() * 1000), id=None) - upload_id = hashlib.sha1(str(item).encode()).hexdigest() - item = item._replace(id=upload_id) + item = UploadItem(path=path, url=url, headers=headers, created_at=int(time.time() * 1000), id=None) + upload_id = hashlib.sha1(str(item).encode()).hexdigest() + item = item._replace(id=upload_id) + upload_queue.put_nowait(item) + items.append(item._asdict()) - upload_queue.put_nowait(item) + UploadQueueCache.cache(upload_queue) - return {"enqueued": 1, "item": item._asdict()} + resp = {"enqueued": len(items), "items": items} + if failed: + resp["failed"] = failed + + return resp @dispatcher.add_method @@ -260,11 +305,15 @@ def listUploadQueue(): @dispatcher.add_method def cancelUpload(upload_id): - upload_ids = set(item.id for item in list(upload_queue.queue)) - if upload_id not in upload_ids: + if not isinstance(upload_id, list): + upload_id = [upload_id] + + uploading_ids = {item.id for item in list(upload_queue.queue)} + cancelled_ids = uploading_ids.intersection(upload_id) + if len(cancelled_ids) == 0: return 404 - cancelled_uploads.add(upload_id) + cancelled_uploads.update(cancelled_ids) return {"success": 1} @@ -280,8 +329,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port): cloudlog.debug("athena.startLocalProxy.starting") - params = Params() - dongle_id = params.get("DongleId").decode('utf8') + dongle_id = Params().get("DongleId").decode('utf8') identity_token = Api(dongle_id).get_token() ws = create_connection(remote_ws_uri, cookie="jwt=" + identity_token, @@ -312,7 +360,7 @@ def getPublicKey(): if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): return None - with open(PERSIST + '/comma/id_rsa.pub', 'r') as f: + with open(PERSIST + '/comma/id_rsa.pub') as f: return f.read() @@ -393,7 +441,7 @@ def log_handler(end_event): curr_time = int(time.time()) log_path = os.path.join(SWAGLOG_DIR, log_entry) setxattr(log_path, LOG_ATTR_NAME, int.to_bytes(curr_time, 4, sys.byteorder)) - with open(log_path, "r") as f: + with open(log_path) as f: jsonrpc = { "method": "forwardLogs", "params": { @@ -402,7 +450,7 @@ def log_handler(end_event): "jsonrpc": "2.0", "id": log_entry } - log_send_queue.put_nowait(json.dumps(jsonrpc)) + low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) curr_log = log_entry except OSError: pass # file could be deleted by log rotation @@ -433,6 +481,32 @@ def log_handler(end_event): cloudlog.exception("athena.log_handler.exception") +def stat_handler(end_event): + while not end_event.is_set(): + last_scan = 0 + curr_scan = sec_since_boot() + try: + if curr_scan - last_scan > 10: + stat_filenames = list(filter(lambda name: not name.startswith(tempfile.gettempprefix()), os.listdir(STATS_DIR))) + if len(stat_filenames) > 0: + stat_path = os.path.join(STATS_DIR, stat_filenames[0]) + with open(stat_path) as f: + jsonrpc = { + "method": "storeStats", + "params": { + "stats": f.read() + }, + "jsonrpc": "2.0", + "id": stat_filenames[0] + } + low_priority_send_queue.put_nowait(json.dumps(jsonrpc)) + os.remove(stat_path) + last_scan = curr_scan + except Exception: + cloudlog.exception("athena.stat_handler.exception") + time.sleep(0.1) + + def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): while not (end_event.is_set() or global_end_event.is_set()): try: @@ -505,7 +579,7 @@ def ws_send(ws, end_event): try: data = send_queue.get_nowait() except queue.Empty: - data = log_send_queue.get(timeout=1) + data = low_priority_send_queue.get(timeout=1) for i in range(0, len(data), WS_FRAME_SIZE): frame = data[i:i+WS_FRAME_SIZE] last = i + WS_FRAME_SIZE >= len(data) @@ -525,6 +599,7 @@ def backoff(retries): def main(): params = Params() dongle_id = params.get("DongleId", encoding='utf-8') + UploadQueueCache.initialize(upload_queue) ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index fa95eacd8e..58ad58310f 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -6,7 +6,7 @@ from multiprocessing import Process from common.params import Params from selfdrive.manager.process import launcher from selfdrive.swaglog import cloudlog -from selfdrive.version import get_version, get_dirty +from selfdrive.version import get_version, is_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -14,12 +14,12 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') - cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty()) + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty()) try: while 1: cloudlog.info("starting athena daemon") - proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad',)) + proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad', 'athenad')) proc.start() proc.join() cloudlog.event("athenad exited", exitcode=proc.exitcode) diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index e06bae5060..f19540eb87 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -1,4 +1,4 @@ -#/!/usr/bin/env python3 +#!/usr/bin/env python3 import time import json import jwt diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 831b668297..26655b4a37 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -50,18 +50,28 @@ class MockApi(): class MockParams(): - def __init__(self): - self.params = { - "DongleId": b"0000000000000000", - "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private" # noqa: E501 - } + default_params = { + "DongleId": b"0000000000000000", + "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 + "AthenadUploadQueue": '[]' + } + params = default_params.copy() + + @staticmethod + def restore_defaults(): + MockParams.params = MockParams.default_params.copy() def get(self, k, encoding=None): - ret = self.params.get(k) + ret = MockParams.params.get(k) if ret is not None and encoding is not None: ret = ret.decode(encoding) return ret + def put(self, k, v): + if k not in MockParams.params: + raise KeyError(f"key: {k} not in MockParams") + MockParams.params[k] = v + class MockWebsocket(): def __init__(self, recv_queue, send_queue): diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 5bf34d1bc7..06d762c180 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -21,19 +21,22 @@ from selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher from selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server from cereal import messaging + class TestAthenadMethods(unittest.TestCase): @classmethod def setUpClass(cls): cls.SOCKET_PORT = 45454 + athenad.Params = MockParams athenad.ROOT = tempfile.mkdtemp() athenad.SWAGLOG_DIR = swaglog.SWAGLOG_DIR = tempfile.mkdtemp() - athenad.Params = MockParams athenad.Api = MockApi - athenad.LOCAL_PORT_WHITELIST = set([cls.SOCKET_PORT]) + athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): + MockParams.restore_defaults() athenad.upload_queue = queue.Queue() athenad.cur_upload_items.clear() + athenad.cancelled_uploads.clear() for i in os.listdir(athenad.ROOT): p = os.path.join(athenad.ROOT, i) @@ -131,15 +134,16 @@ class TestAthenadMethods(unittest.TestCase): @with_http_server def test_uploadFileToUrl(self, host): not_exists_resp = dispatcher["uploadFileToUrl"]("does_not_exist.bz2", "http://localhost:1238", {}) - self.assertEqual(not_exists_resp, 404) + self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) fn = os.path.join(athenad.ROOT, 'qlog.bz2') Path(fn).touch() resp = dispatcher["uploadFileToUrl"]("qlog.bz2", f"{host}/qlog.bz2", {}) self.assertEqual(resp['enqueued'], 1) - self.assertDictContainsSubset({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}, resp['item']) - self.assertIsNotNone(resp['item'].get('id')) + self.assertNotIn('failed', resp) + self.assertDictContainsSubset({"path": fn, "url": f"{host}/qlog.bz2", "headers": {}}, resp['items'][0]) + self.assertIsNotNone(resp['items'][0].get('id')) self.assertEqual(athenad.upload_queue.qsize(), 1) @with_http_server @@ -249,6 +253,26 @@ class TestAthenadMethods(unittest.TestCase): items = dispatcher["listUploadQueue"]() self.assertEqual(len(items), 0) + def test_upload_queue_persistence(self): + item1 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id1') + item2 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id2') + + athenad.upload_queue.put_nowait(item1) + athenad.upload_queue.put_nowait(item2) + + # Ensure cancelled items are not persisted + athenad.cancelled_uploads.add(item2.id) + + # serialize item + athenad.UploadQueueCache.cache(athenad.upload_queue) + + # deserialize item + athenad.upload_queue.queue.clear() + athenad.UploadQueueCache.initialize(athenad.upload_queue) + + self.assertEqual(athenad.upload_queue.qsize(), 1) + self.assertDictEqual(athenad.upload_queue.queue[-1]._asdict(), item1._asdict()) + @mock.patch('selfdrive.athena.athenad.create_connection') def test_startLocalProxy(self, mock_create_connection): end_event = threading.Event() diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 3f66efc9fe..1178b4cafa 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -205,17 +205,17 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) { } void can_send_thread(std::vector pandas, bool fake_send) { - set_thread_name("boardd_can_send"); + util::set_thread_name("boardd_can_send"); AlignedBuffer aligned_buf; - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "sendcan"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "sendcan")); assert(subscriber != NULL); subscriber->setTimeout(100); // run as fast as messages come in while (!do_exit && check_all_connected(pandas)) { - Message * msg = subscriber->receive(); + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -223,27 +223,20 @@ void can_send_thread(std::vector pandas, bool fake_send) { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); //Dont send if older than 1 second - if (nanos_since_boot() - event.getLogMonoTime() < 1e9) { - if (!fake_send) { - for (const auto& panda : pandas) { - panda->can_send(event.getSendcan()); - } + if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { + for (const auto& panda : pandas) { + panda->can_send(event.getSendcan()); } } - - delete msg; } - - delete subscriber; - delete context; } void can_recv_thread(std::vector pandas) { - set_thread_name("boardd_can_recv"); + util::set_thread_name("boardd_can_recv"); // can = 8006 PubMaster pm({"can"}); @@ -415,9 +408,11 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { } void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofing_started) { - set_thread_name("boardd_panda_state"); + util::set_thread_name("boardd_panda_state"); Params params; + SubMaster sm({"controlsState"}); + Panda *peripheral_panda = pandas[0]; bool ignition_last = false; std::future safety_future; @@ -452,8 +447,11 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin ignition_last = ignition; + sm.update(0); + const bool engaged = sm.allAliveAndValid({"controlsState"}) && sm["controlsState"].getControlsState().getEnabled(); + for (const auto &panda : pandas) { - panda->send_heartbeat(); + panda->send_heartbeat(engaged); } util::sleep_for(500); } @@ -461,7 +459,7 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin void peripheral_control_thread(Panda *panda) { - set_thread_name("boardd_peripheral_control"); + util::set_thread_name("boardd_peripheral_control"); SubMaster sm({"deviceState", "driverCameraState"}); @@ -547,12 +545,12 @@ static void pigeon_publish_raw(PubMaster &pm, const std::string &dat) { } void pigeon_thread(Panda *panda) { - set_thread_name("boardd_pigeon"); + util::set_thread_name("boardd_pigeon"); PubMaster pm({"ubloxRaw"}); bool ignition_last = false; - Pigeon *pigeon = Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda); + std::unique_ptr pigeon(Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda)); std::unordered_map last_recv_time; std::unordered_map cls_max_dt = { @@ -620,8 +618,6 @@ void pigeon_thread(Panda *panda) { // 10ms - 100 Hz util::sleep_for(10); } - - delete pigeon; } int main(int argc, char *argv[]) { @@ -629,9 +625,9 @@ int main(int argc, char *argv[]) { if (!Hardware::PC()) { int err; - err = set_realtime_priority(54); + err = util::set_realtime_priority(54); assert(err == 0); - err = set_core_affinity({Hardware::TICI() ? 4 : 3}); + err = util::set_core_affinity({Hardware::TICI() ? 4 : 3}); assert(err == 0); } @@ -662,6 +658,8 @@ int main(int argc, char *argv[]) { Panda *peripheral_panda = pandas[0]; std::vector threads; + Params().put("LastPeripheralPandaType", std::to_string((int) peripheral_panda->get_hw_type())); + threads.emplace_back(panda_state_thread, &pm, pandas, getenv("STARTED") != nullptr); threads.emplace_back(peripheral_control_thread, peripheral_panda); threads.emplace_back(pigeon_thread, peripheral_panda); diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 751f735ca5..6850ad41b2 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -340,8 +340,8 @@ void Panda::set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode) usb_write(0xe6, (uint16_t)power_mode, 0); } -void Panda::send_heartbeat() { - usb_write(0xf3, 1, 0); +void Panda::send_heartbeat(bool engaged) { + usb_write(0xf3, engaged, 0); } void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index fe69189489..1a18a7f15a 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -108,7 +108,7 @@ class Panda { std::optional get_serial(); void set_power_saving(bool power_saving); void set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode); - void send_heartbeat(); + void send_heartbeat(bool engaged); void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed); void can_send(capnp::List::Reader can_data_list); diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index 24c6f2e300..631b4c987f 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -69,7 +69,7 @@ class TestBoardd(unittest.TestCase): for __ in range(random.randrange(100)): bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3]) addr = random.randrange(1, 1<<29) - dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9))) sent_msgs[bus].add((addr, dat)) to_send.append(make_can_msg(addr, dat, bus)) sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan')) diff --git a/selfdrive/camerad/cameras/camera_common.cc b/selfdrive/camerad/cameras/camera_common.cc index b901dc39d3..424ad78ab3 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/selfdrive/camerad/cameras/camera_common.cc @@ -200,7 +200,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setMeasuredGreyFraction(frame_data.measured_grey_fraction); framed.setTargetGreyFraction(frame_data.target_grey_fraction); framed.setLensPos(frame_data.lens_pos); - framed.setLensSag(frame_data.lens_sag); framed.setLensErr(frame_data.lens_err); framed.setLensTruePos(frame_data.lens_true_pos); } @@ -351,7 +350,7 @@ void *processing_thread(MultiCameraState *cameras, CameraState *cs, process_thre } else { thread_name = "WideRoadCamera"; } - set_thread_name(thread_name); + util::set_thread_name(thread_name); uint32_t cnt = 0; while (!do_exit) { @@ -408,11 +407,11 @@ static void driver_cam_auto_exposure(CameraState *c, SubMaster &sm) { camera_autoexposure(c, set_exposure_target(b, rect.x1, rect.x2, rect.x_skip, rect.y1, rect.y2, rect.y_skip)); } -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt) { +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { int j = Hardware::TICI() ? 1 : 3; if (cnt % j == 0) { - sm->update(0); - driver_cam_auto_exposure(c, *sm); + s->sm->update(0); + driver_cam_auto_exposure(c, *(s->sm)); } MessageBuilder msg; auto framed = msg.initEvent().initDriverCameraState(); @@ -421,5 +420,5 @@ void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, if (env_send_driver) { framed.setImage(get_frame_image(&c->buf)); } - pm->send("driverCameraState", msg); + s->pm->send("driverCameraState", msg); } diff --git a/selfdrive/camerad/cameras/camera_common.h b/selfdrive/camerad/cameras/camera_common.h index 20c00c5b5b..75bd79bfdf 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/selfdrive/camerad/cameras/camera_common.h @@ -68,7 +68,6 @@ typedef struct FrameMetadata { // Focus unsigned int lens_pos; - float lens_sag; float lens_err; float lens_true_pos; } FrameMetadata; @@ -123,7 +122,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr kj::Array get_frame_image(const CameraBuf *b); float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt); +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt); void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx); void cameras_open(MultiCameraState *s); diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc index 11383de668..ecb1c8ffe4 100644 --- a/selfdrive/camerad/cameras/camera_qcom.cc +++ b/selfdrive/camerad/cameras/camera_qcom.cc @@ -227,7 +227,7 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i s->stats_bufs[i].allocate(0xb80); } std::fill_n(s->lapres, std::size(s->lapres), 16160); - s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, 3); + s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, s->road_cam.buf.rgb_stride, 3); } static void set_exposure(CameraState *s, float exposure_frac, float gain_frac) { @@ -852,21 +852,7 @@ static void parse_autofocus(CameraState *s, uint8_t *d) { s->focus_err = max_focus*1.0; } -static std::optional get_accel_z(SubMaster *sm) { - sm->update(0); - if(sm->updated("sensorEvents")) { - for (auto event : (*sm)["sensorEvents"].getSensorEvents()) { - if (event.which() == cereal::SensorEventData::ACCELERATION) { - if (auto v = event.getAcceleration().getV(); v.size() >= 3) - return -v[2]; - break; - } - } - } - return std::nullopt; -} - -static void do_autofocus(CameraState *s, SubMaster *sm) { +static void do_autofocus(CameraState *s) { float lens_true_pos = s->lens_true_pos.load(); if (!isnan(s->focus_err)) { // learn lens_true_pos @@ -874,23 +860,10 @@ static void do_autofocus(CameraState *s, SubMaster *sm) { lens_true_pos -= s->focus_err*focus_kp; } - if (auto accel_z = get_accel_z(sm)) { - s->last_sag_acc_z = *accel_z; - } - const float sag = (s->last_sag_acc_z / 9.8) * 128; // stay off the walls lens_true_pos = std::clamp(lens_true_pos, float(LP3_AF_DAC_DOWN), float(LP3_AF_DAC_UP)); - int target = std::clamp(lens_true_pos - sag, float(LP3_AF_DAC_DOWN), float(LP3_AF_DAC_UP)); s->lens_true_pos.store(lens_true_pos); - - /*char debug[4096]; - char *pdebug = debug; - pdebug += sprintf(pdebug, "focus "); - for (int i = 0; i < NUM_FOCUS; i++) pdebug += sprintf(pdebug, "%2x(%4d) ", s->confidence[i], s->focus[i]); - pdebug += sprintf(pdebug, " err: %7.2f offset: %6.2f sag: %6.2f lens_true_pos: %6.2f cur_lens_pos: %4d->%4d", err * focus_kp, offset, sag, s->lens_true_pos, s->cur_lens_pos, target); - LOGD(debug);*/ - - actuator_move(s, target); + actuator_move(s, lens_true_pos); } void camera_autoexposure(CameraState *s, float grey_frac) { @@ -1045,13 +1018,12 @@ static void ops_thread(MultiCameraState *s) { CameraExpInfo road_cam_op; CameraExpInfo driver_cam_op; - set_thread_name("camera_settings"); - SubMaster sm({"sensorEvents"}); + util::set_thread_name("camera_settings"); while(!do_exit) { road_cam_op = road_cam_exp.load(); if (road_cam_op.op_id != last_road_cam_op_id) { do_autoexposure(&s->road_cam, road_cam_op.grey_frac); - do_autofocus(&s->road_cam, &sm); + do_autofocus(&s->road_cam); last_road_cam_op_id = road_cam_op.op_id; } @@ -1086,10 +1058,6 @@ static void setup_self_recover(CameraState *c, const uint16_t *lapres, size_t la c->self_recover.store(self_recover); } -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1121,7 +1089,7 @@ void cameras_run(MultiCameraState *s) { std::vector threads; threads.push_back(std::thread(ops_thread, s)); threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); CameraState* cameras[2] = {&s->road_cam, &s->driver_cam}; @@ -1169,7 +1137,6 @@ void cameras_run(MultiCameraState *s) { .frame_length = (uint32_t)c->frame_length, .integ_lines = (uint32_t)c->cur_integ_lines, .lens_pos = c->cur_lens_pos, - .lens_sag = c->last_sag_acc_z, .lens_err = c->focus_err, .lens_true_pos = c->lens_true_pos, .gain = c->cur_gain_frac, diff --git a/selfdrive/camerad/cameras/camera_qcom.h b/selfdrive/camerad/cameras/camera_qcom.h index 207857e05c..87bbe4b7e7 100644 --- a/selfdrive/camerad/cameras/camera_qcom.h +++ b/selfdrive/camerad/cameras/camera_qcom.h @@ -76,7 +76,6 @@ typedef struct CameraState { // rear camera only,used for focusing unique_fd actuator_fd; std::atomic focus_err; - std::atomic last_sag_acc_z; std::atomic lens_true_pos; std::atomic self_recover; // af recovery counter, neg is patience, pos is active uint16_t cur_step_pos; diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc index 3ea065ae73..030bbe47e1 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.cc +++ b/selfdrive/camerad/cameras/camera_qcom2.cc @@ -987,11 +987,6 @@ void camera_autoexposure(CameraState *s, float grey_frac) { set_camera_exposure(s, grey_frac); } - -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1016,7 +1011,7 @@ void cameras_run(MultiCameraState *s) { LOG("-- Starting threads"); std::vector threads; threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); threads.push_back(start_process_thread(s, &s->wide_road_cam, process_road_camera)); // start devices diff --git a/selfdrive/camerad/cameras/camera_replay.cc b/selfdrive/camerad/cameras/camera_replay.cc index 8ff1b6aacc..b5b2e6ad29 100644 --- a/selfdrive/camerad/cameras/camera_replay.cc +++ b/selfdrive/camerad/cameras/camera_replay.cc @@ -67,12 +67,12 @@ void run_camera(CameraState *s) { } void road_camera_thread(CameraState *s) { - set_thread_name("replay_road_camera_thread"); + util::set_thread_name("replay_road_camera_thread"); run_camera(s); } // void driver_camera_thread(CameraState *s) { -// set_thread_name("replay_driver_camera_thread"); +// util::set_thread_name("replay_driver_camera_thread"); // run_camera(s); // } diff --git a/selfdrive/camerad/cameras/camera_webcam.cc b/selfdrive/camerad/cameras/camera_webcam.cc index 47da56042c..956f2dc88f 100644 --- a/selfdrive/camerad/cameras/camera_webcam.cc +++ b/selfdrive/camerad/cameras/camera_webcam.cc @@ -97,7 +97,7 @@ void run_camera(CameraState *s, cv::VideoCapture &video_cap, float *ts) { } static void road_camera_thread(CameraState *s) { - set_thread_name("webcam_road_camera_thread"); + util::set_thread_name("webcam_road_camera_thread"); cv::VideoCapture cap_road(ROAD_CAMERA_ID, cv::CAP_V4L2); // road cap_road.set(cv::CAP_PROP_FRAME_WIDTH, 853); @@ -186,7 +186,7 @@ void cameras_run(MultiCameraState *s) { threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); std::thread t_rear = std::thread(road_camera_thread, &s->road_cam); - set_thread_name("webcam_thread"); + util::set_thread_name("webcam_thread"); driver_camera_thread(&s->driver_cam); t_rear.join(); diff --git a/selfdrive/camerad/imgproc/utils.cc b/selfdrive/camerad/imgproc/utils.cc index ad6452c6a6..a88b8f4bb1 100644 --- a/selfdrive/camerad/imgproc/utils.cc +++ b/selfdrive/camerad/imgproc/utils.cc @@ -55,8 +55,8 @@ static cl_program build_conv_program(cl_device_id device_id, cl_context context, 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 filter_size) - : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), +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); @@ -81,9 +81,9 @@ uint16_t LapConv::Update(cl_command_queue q, const uint8_t *rgb_buf, const int r 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 * FULL_STRIDE_X * 3 + x_offset * width * 3; + 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 * FULL_STRIDE_X * 3], width * 3); + 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)); diff --git a/selfdrive/camerad/imgproc/utils.h b/selfdrive/camerad/imgproc/utils.h index 72baa5d53a..b735975b3e 100644 --- a/selfdrive/camerad/imgproc/utils.h +++ b/selfdrive/camerad/imgproc/utils.h @@ -16,16 +16,11 @@ #define LM_THRESH 120 #define LM_PREC_THRESH 0.9 // 90 perc is blur - -// only apply to QCOM -#define FULL_STRIDE_X 1280 -#define FULL_STRIDE_Y 896 - #define CONV_LOCAL_WORKSIZE 16 class LapConv { public: - LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int filter_size); + 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); @@ -34,6 +29,7 @@ private: cl_program prg; cl_kernel krnl; const int width, height; + const int rgb_stride; std::vector roi_buf; std::vector result_buf; }; diff --git a/selfdrive/camerad/main.cc b/selfdrive/camerad/main.cc index 1db7130444..f06bd341df 100644 --- a/selfdrive/camerad/main.cc +++ b/selfdrive/camerad/main.cc @@ -46,9 +46,9 @@ void party(cl_device_id device_id, cl_context context) { int main(int argc, char *argv[]) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(53); + ret = util::set_realtime_priority(53); assert(ret == 0); - ret = set_core_affinity({Hardware::EON() ? 2 : 6}); + ret = util::set_core_affinity({Hardware::EON() ? 2 : 6}); assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores } diff --git a/selfdrive/camerad/test/test_camerad.py b/selfdrive/camerad/test/test_camerad.py index 11e070f328..ff37ad1f31 100755 --- a/selfdrive/camerad/test/test_camerad.py +++ b/selfdrive/camerad/test/test_camerad.py @@ -54,7 +54,7 @@ class TestCamerad(unittest.TestCase): self.assertTrue(abs(dfid - 1) <= SKIP_FRAME_TOLERANCE, "%s frame id diff is %d" % (camera, dfid)) dts = ct - last_ts[camera] - self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, "%s frame t(ms) diff is %f" % (camera, dts)) + self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, f"{camera} frame t(ms) diff is {dts:f}") last_frame_id[camera] = sm[camera].frameId last_ts[camera] = ct diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 786b00fa2b..0704bc8950 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,7 +1,7 @@ import os from common.params import Params from common.basedir import BASEDIR -from selfdrive.version import get_comma_remote, get_tested_branch +from selfdrive.version import is_comma_remote, is_tested_branch from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, VIN_UNKNOWN from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car @@ -14,7 +14,7 @@ EventName = car.CarEvent.EventName def get_startup_event(car_recognized, controller_available, fw_seen): - if get_comma_remote() and get_tested_branch(): + if is_comma_remote() and is_tested_branch(): event = EventName.startup else: event = EventName.startupMaster @@ -39,7 +39,7 @@ def get_one_can(logcan): def load_interfaces(brand_names): ret = {} for brand_name in brand_names: - path = ('selfdrive.car.%s' % brand_name) + path = f'selfdrive.car.{brand_name}' CarInterface = __import__(path + '.interface', fromlist=['CarInterface']).CarInterface if os.path.exists(BASEDIR + '/' + path.replace('.', '/') + '/carstate.py'): @@ -65,10 +65,10 @@ def _get_interface_names(): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: try: brand_name = car_folder.split('/')[-1] - model_names = __import__('selfdrive.car.%s.values' % brand_name, fromlist=['CAR']).CAR + model_names = __import__(f'selfdrive.car.{brand_name}.values', fromlist=['CAR']).CAR model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")] brand_names[brand_name] = model_names - except (ImportError, IOError): + except (ImportError, OSError): pass return brand_names @@ -131,7 +131,7 @@ def fingerprint(logcan, sendcan): for b in candidate_cars: # Ignore extended messages and VIN query response. - if can.src == b and can.address < 0x800 and can.address not in [0x7df, 0x7e0, 0x7e8]: + if can.src == b and can.address < 0x800 and can.address not in (0x7df, 0x7e0, 0x7e8): candidate_cars[b] = eliminate_incompatible_cars(can, candidate_cars[b]) # if we only have one car choice and the time since we got our first diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 46681b13e2..d491ccd4b9 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,3 +1,4 @@ +from cereal import car from selfdrive.car import apply_toyota_steer_torque_limits from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, \ create_wheel_buttons @@ -20,9 +21,8 @@ class CarController(): # this seems needed to avoid steering faults and to force the sync with the EPS counter frame = CS.lkas_counter if self.prev_frame == frame: - return [] + return car.CarControl.Actuators.new_message(), [] - # *** compute control surfaces *** # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, @@ -67,4 +67,7 @@ class CarController(): self.ccframe += 1 self.prev_frame = frame - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index ab3125b011..66cd178f88 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -52,7 +52,7 @@ class CarState(CarStateBase): ret.cruiseState.available = ret.cruiseState.enabled # FIXME: for now same as enabled ret.cruiseState.speed = cp.vl["DASHBOARD"]["ACC_SPEED_CONFIG_KPH"] * CV.KPH_TO_MS # CRUISE_STATE is a three bit msg, 0 is off, 1 and 2 are Non-ACC mode, 3 and 4 are ACC mode, find if there are other states too - ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in [1, 2] + ret.cruiseState.nonAdaptive = cp.vl["DASHBOARD"]["CRUISE_STATE"] in (1, 2) ret.steeringTorque = cp.vl["EPS_STATUS"]["TORQUE_DRIVER"] ret.steeringTorqueEps = cp.vl["EPS_STATUS"]["TORQUE_MOTOR"] diff --git a/selfdrive/car/chrysler/chryslercan.py b/selfdrive/car/chrysler/chryslercan.py index c72f155298..4d5226570c 100644 --- a/selfdrive/car/chrysler/chryslercan.py +++ b/selfdrive/car/chrysler/chryslercan.py @@ -8,7 +8,7 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert def create_lkas_hud(packer, gear, lkas_active, hud_alert, hud_count, lkas_car_model): # LKAS_HUD 0x2a6 (678) Controls what lane-keeping icon is displayed. - if hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw]: + if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw): msg = b'\x00\x00\x00\x03\x00\x00\x00\x00' return make_can_msg(0x2a6, msg, 0) diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 1822d76a1a..a893b222f8 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -78,8 +78,6 @@ class CarInterface(CarInterfaceBase): def apply(self, c): if (self.CS.frame == -1): - return [] # if we haven't seen a frame 220, then do not update. + return car.CarControl.Actuators.new_message(), [] # if we haven't seen a frame 220, then do not update. - can_sends = self.CC.update(c.enabled, self.CS, c.actuators, c.cruiseControl.cancel, c.hudControl.visualAlert) - - return can_sends + return self.CC.update(c.enabled, self.CS, c.actuators, c.cruiseControl.cancel, c.hudControl.visualAlert) diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index b09e14a429..9b280f3b45 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -11,7 +11,7 @@ def get_attr_from_cars(attr, result=dict, combine_brands=True): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: try: car_name = car_folder.split('/')[-1] - values = __import__('selfdrive.car.%s.values' % car_name, fromlist=[attr]) + values = __import__(f'selfdrive.car.{car_name}.values', fromlist=[attr]) if hasattr(values, attr): attr_values = getattr(values, attr) else: @@ -28,7 +28,7 @@ def get_attr_from_cars(attr, result=dict, combine_brands=True): elif isinstance(attr_values, list): result += attr_values - except (ImportError, IOError): + except (ImportError, OSError): pass return result diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 06c5f25797..389d3d8d8b 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -22,7 +22,7 @@ class CarController(): def update(self, enabled, CS, frame, actuators, visual_alert, pcm_cancel): can_sends = [] - steer_alert = visual_alert in [VisualAlert.steerRequired, VisualAlert.ldw] + steer_alert = visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw) apply_steer = actuators.steer @@ -32,7 +32,7 @@ class CarController(): if (frame % 3) == 0: - curvature = self.vehicle_model.calc_curvature(actuators.steeringAngleDeg*math.pi/180., CS.out.vEgo) + curvature = self.vehicle_model.calc_curvature(math.radians(actuators.steeringAngleDeg), CS.out.vEgo, 0.0) # The use of the toggle below is handy for trying out the various LKAS modes if TOGGLE_DEBUG: @@ -83,4 +83,4 @@ class CarController(): self.main_on_last = CS.out.cruiseState.available self.steer_alert_last = steer_alert - return can_sends + return actuators, can_sends diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index a621ab16ab..d71e65352f 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -25,7 +25,7 @@ class CarState(CarStateBase): ret.steeringPressed = not cp.vl["Lane_Keep_Assist_Status"]["LaHandsOff_B_Actl"] ret.steerError = cp.vl["Lane_Keep_Assist_Status"]["LaActDeny_B_Actl"] == 1 ret.cruiseState.speed = cp.vl["Cruise_Status"]["Set_Speed"] * CV.MPH_TO_MS - ret.cruiseState.enabled = not (cp.vl["Cruise_Status"]["Cruise_State"] in [0, 3]) + ret.cruiseState.enabled = not (cp.vl["Cruise_Status"]["Cruise_State"] in (0, 3)) ret.cruiseState.available = cp.vl["Cruise_Status"]["Cruise_State"] != 0 ret.gas = cp.vl["EngineData_14"]["ApedPosScal_Pc_Actl"] / 100. ret.gasPressed = ret.gas > 1e-6 diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index e98dec584b..5a8b0b2dec 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -6,7 +6,7 @@ def create_steer_command(packer, angle_cmd, enabled, lkas_state, angle_steers, c """Creates a CAN message for the Ford Steer Command.""" #if enabled and lkas available: - if enabled and lkas_state in [2, 3]: # and (frame % 500) >= 3: + if enabled and lkas_state in (2, 3): # and (frame % 500) >= 3: action = lkas_action else: action = 0xf diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 8c0b1e1faa..12cf6367e3 100755 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -51,7 +51,7 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret) - if self.CS.lkas_state not in [2, 3] and ret.vEgo > 13. * CV.MPH_TO_MS and ret.cruiseState.enabled: + if self.CS.lkas_state not in (2, 3) and ret.vEgo > 13. * CV.MPH_TO_MS and ret.cruiseState.enabled: events.add(car.CarEvent.EventName.steerTempUnavailable) ret.events = events.to_msg() @@ -63,8 +63,8 @@ class CarInterface(CarInterfaceBase): # to be called @ 100hz def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.hudControl.visualAlert, c.cruiseControl.cancel) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 5d982d6a8d..59fd4fe8a5 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -84,8 +84,21 @@ NISSAN_VERSION_RESPONSE_STANDARD = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIF NISSAN_RX_OFFSET = 0x20 +SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) +SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) + + # brand, request, response, response offset REQUESTS = [ + # Subaru + ( + "subaru", + [TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], + [TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + DEFAULT_RX_OFFSET, + ), # Hyundai ( "hyundai", @@ -221,7 +234,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): if match_count >= 2: if log: cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {match_count} matching ECUs") - return set([candidate]) + return {candidate} else: return set() @@ -240,11 +253,11 @@ def match_fw_to_car_exact(fw_versions_dict): addr = ecu[1:] found_version = fw_versions_dict.get(addr, None) ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.esp, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] - if ecu_type == Ecu.esp and candidate in [TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS] and found_version is None: + if ecu_type == Ecu.esp and candidate in (TOYOTA.RAV4, TOYOTA.COROLLA, TOYOTA.HIGHLANDER, TOYOTA.SIENNA, TOYOTA.LEXUS_IS) and found_version is None: continue # On some Toyota models, the engine can show on two different addresses - if ecu_type == Ecu.engine and candidate in [TOYOTA.CAMRY, TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS] and found_version is None: + if ecu_type == Ecu.engine and candidate in (TOYOTA.CAMRY, TOYOTA.COROLLA_TSS2, TOYOTA.CHR, TOYOTA.LEXUS_IS) and found_version is None: continue # Ignore non essential ecus @@ -362,7 +375,7 @@ if __name__ == "__main__": print("Getting vin...") addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) print(f"VIN: {vin}") - print("Getting VIN took %.3f s" % (time.time() - t)) + print(f"Getting VIN took {time.time() - t:.3f} s") print() t = time.time() @@ -379,4 +392,4 @@ if __name__ == "__main__": print() print("Possible matches:", candidates) - print("Getting fw took %.3f s" % (time.time() - t)) + print(f"Getting fw took {time.time() - t:.3f} s") diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 8f4d0f27ca..56c7c2a3ca 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -14,6 +14,9 @@ class CarController(): def __init__(self, dbc_name, CP, VM): self.start_time = 0. self.apply_steer_last = 0 + self.apply_gas = 0 + self.apply_brake = 0 + self.lka_steering_cmd_counter_last = -1 self.lka_icon_status_last = (False, False) self.steer_rate_limited = False @@ -53,22 +56,22 @@ class CarController(): can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, lkas_enabled)) - if not enabled: - # Stock ECU sends max regen when not enabled. - apply_gas = P.MAX_ACC_REGEN - apply_brake = 0 - else: - apply_gas = int(round(interp(actuators.accel, P.GAS_LOOKUP_BP, P.GAS_LOOKUP_V))) - apply_brake = int(round(interp(actuators.accel, P.BRAKE_LOOKUP_BP, P.BRAKE_LOOKUP_V))) - # Gas/regen and brakes - all at 25Hz if (frame % 4) == 0: + if not enabled: + # Stock ECU sends max regen when not enabled. + self.apply_gas = P.MAX_ACC_REGEN + self.apply_brake = 0 + else: + self.apply_gas = int(round(interp(actuators.accel, P.GAS_LOOKUP_BP, P.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, P.BRAKE_LOOKUP_BP, P.BRAKE_LOOKUP_V))) + idx = (frame // 4) % 4 at_full_stop = enabled and CS.out.standstill near_stop = enabled and (CS.out.vEgo < P.NEAR_STOP_BRAKE_PHASE) - can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, apply_brake, idx, near_stop, at_full_stop)) - can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, apply_gas, idx, enabled, at_full_stop)) + can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) + can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, enabled, at_full_stop)) # Send dashboard UI commands (ACC status), 25hz if (frame % 4) == 0: @@ -102,8 +105,13 @@ class CarController(): lka_critical = lka_active and abs(actuators.steer) > 0.9 lka_icon_status = (lka_active, lka_critical) if frame % P.CAMERA_KEEPALIVE_STEP == 0 or lka_icon_status != self.lka_icon_status_last: - steer_alert = hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw] + steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert)) self.lka_icon_status_last = lka_icon_status - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / P.STEER_MAX + new_actuators.gas = self.apply_gas + new_actuators.brake = self.apply_brake + + return new_actuators, can_sends diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index e4d6448348..5180d2e511 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -36,7 +36,7 @@ class CarState(CarStateBase): if ret.brake < 10/0xd0: ret.brake = 0. - ret.gas = pt_cp.vl["AcceleratorPedal"]["AcceleratorPedal"] / 254. + ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254. ret.gasPressed = ret.gas > 1e-5 ret.steeringAngleDeg = pt_cp.vl["PSCMSteeringAngle"]["SteeringWheelAngle"] @@ -90,7 +90,7 @@ class CarState(CarStateBase): ("LeftSeatBelt", "BCMDoorBeltStatus", 0), ("RightSeatBelt", "BCMDoorBeltStatus", 0), ("TurnSignals", "BCMTurnSignals", 0), - ("AcceleratorPedal", "AcceleratorPedal", 0), + ("AcceleratorPedal2", "AcceleratorPedal2", 0), ("CruiseState", "AcceleratorPedal2", 0), ("ACCButtons", "ASCMSteeringButton", CruiseButtons.UNPRESS), ("SteeringWheelAngle", "PSCMSteeringAngle", 0), @@ -117,7 +117,6 @@ class CarState(CarStateBase): ("EPBStatus", 20), ("EBCMWheelSpdFront", 20), ("EBCMWheelSpdRear", 20), - ("AcceleratorPedal", 33), ("AcceleratorPedal2", 33), ("ASCMSteeringButton", 33), ("ECMEngineStatus", 100), diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index b06e5373aa..45fb55098e 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -44,6 +44,11 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.pcmCruise = False # stock cruise control is kept off + # These cars have been put into dashcam only due to both a lack of users and test coverage. + # These cars likely still work fine. Once a user confirms each car works and a test route is + # added to selfdrive/test/test_routes, we can remove it from this list. + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU} + # Presence of a camera on the object bus is ok. # Have to go to read_only if ASCM is online (ACC-enabled cars), # or camera is on powertrain bus (LKA cars without ACC). @@ -198,7 +203,7 @@ class CarInterface(CarInterfaceBase): # handle button presses for b in ret.buttonEvents: # do enable on both accel and decel buttons - if b.type in [ButtonType.accelCruise, ButtonType.decelCruise] and not b.pressed: + if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed: events.add(EventName.buttonEnable) # do disable on button down if b.type == ButtonType.cancel and b.pressed: @@ -212,7 +217,8 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - hud_v_cruise = c.hudControl.setSpeed + hud_control = c.hudControl + hud_v_cruise = hud_control.setSpeed if hud_v_cruise > 70: hud_v_cruise = 0 @@ -220,10 +226,10 @@ class CarInterface(CarInterfaceBase): # In GM, PCM faults out if ACC command overlaps user gas. enabled = c.enabled and not self.CS.out.gasPressed - can_sends = self.CC.update(enabled, self.CS, self.frame, - c.actuators, - hud_v_cruise, c.hudControl.lanesVisible, - c.hudControl.leadVisible, c.hudControl.visualAlert) + ret = self.CC.update(enabled, self.CS, self.frame, + c.actuators, + hud_v_cruise, hud_control.lanesVisible, + hud_control.leadVisible, hud_control.visualAlert) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 4cb1e0781f..d1ad1c1635 100755 --- a/selfdrive/car/gm/radar_interface.py +++ b/selfdrive/car/gm/radar_interface.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import print_function import math from cereal import car from opendbc.can.parser import CANParser diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index e1488eda2a..33d01f61b6 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -83,7 +83,7 @@ def process_hud_alert(hud_alert): # priority is: FCW, steer required, all others if hud_alert == VisualAlert.fcw: fcw_display = VISUAL_HUD[hud_alert.raw] - elif hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw]: + elif hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw): steer_required = VISUAL_HUD[hud_alert.raw] else: acc_alert = VISUAL_HUD[hud_alert.raw] @@ -105,6 +105,11 @@ class CarController(): self.last_pump_ts = 0. self.packer = CANPacker(dbc_name) + self.accel = 0 + self.speed = 0 + self.gas = 0 + self.brake = 0 + self.params = CarControllerParams(CP) def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, @@ -163,7 +168,6 @@ class CarController(): lkas_active, CS.CP.carFingerprint, idx, CS.CP.openpilotLongitudinalControl)) stopping = actuators.longControlState == LongCtrlState.stopping - starting = actuators.longControlState == LongCtrlState.starting # wind brake from air resistance decel at high speed wind_brake = interp(CS.out.vEgo, [0.0, 2.3, 35.0], [0.001, 0.002, 0.15]) @@ -211,10 +215,9 @@ class CarController(): ts = frame * DT_CTRL if CS.CP.carFingerprint in HONDA_BOSCH: - accel = clip(accel, P.BOSCH_ACCEL_MIN, P.BOSCH_ACCEL_MAX) - bosch_gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP, P.BOSCH_GAS_LOOKUP_V) - can_sends.extend(hondacan.create_acc_commands(self.packer, enabled, active, accel, bosch_gas, idx, stopping, starting, CS.CP.carFingerprint)) - + self.accel = clip(accel, P.BOSCH_ACCEL_MIN, P.BOSCH_ACCEL_MAX) + self.gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP, P.BOSCH_GAS_LOOKUP_V) + can_sends.extend(hondacan.create_acc_commands(self.packer, enabled, active, accel, self.gas, idx, stopping, CS.CP.carFingerprint)) else: apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0) apply_brake = int(clip(apply_brake * P.NIDEC_BRAKE_MAX, 0, P.NIDEC_BRAKE_MAX - 1)) @@ -224,6 +227,7 @@ class CarController(): can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw_display, idx, CS.CP.carFingerprint, CS.stock_brake)) self.apply_brake_last = apply_brake + self.brake = apply_brake / P.NIDEC_BRAKE_MAX if CS.CP.enableGasInterceptor: # way too aggressive at low speed without this @@ -233,17 +237,28 @@ class CarController(): # Sending non-zero gas when OP is not enabled will cause the PCM not to respond to throttle as expected # when you do enable. if active: - apply_gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.) + self.gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.) else: - apply_gas = 0.0 - can_sends.append(create_gas_interceptor_command(self.packer, apply_gas, idx)) - - hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car, - hud_lanes, fcw_display, acc_alert, steer_required) + self.gas = 0.0 + can_sends.append(create_gas_interceptor_command(self.packer, self.gas, idx)) # Send dashboard UI commands. if (frame % 10) == 0: idx = (frame//10) % 4 + hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car, + hud_lanes, fcw_display, acc_alert, steer_required) can_sends.extend(hondacan.create_ui_commands(self.packer, CS.CP, pcm_speed, hud, CS.is_metric, idx, CS.stock_hud)) - return can_sends + if (CS.CP.openpilotLongitudinalControl) and (CS.CP.carFingerprint not in HONDA_BOSCH): + self.speed = pcm_speed + + if not CS.CP.enableGasInterceptor: + self.gas = pcm_accel / 0xc6 + + new_actuators = actuators.copy() + new_actuators.speed = self.speed + new_actuators.accel = self.accel + new_actuators.gas = self.gas + new_actuators.brake = self.brake + + return new_actuators, can_sends diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 65a9d8fd44..029413ad72 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -107,7 +107,7 @@ def get_can_signals(CP, gearbox_msg, main_on_sig_msg): else: checks += [("CRUISE_PARAMS", 50)] - if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): signals += [("DRIVERS_DOOR_OPEN", "SCM_FEEDBACK", 1)] elif CP.carFingerprint == CAR.ODYSSEY_CHN: signals += [("DRIVERS_DOOR_OPEN", "SCM_BUTTONS", 1)] @@ -185,7 +185,7 @@ class CarState(CarStateBase): # ******************* parse out can ******************* # TODO: find wheels moving bit in dbc - if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): + if self.CP.carFingerprint in (CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): ret.standstill = cp.vl["ENGINE_DATA"]["XMISSION_SPEED"] < 0.1 ret.doorOpen = bool(cp.vl["SCM_FEEDBACK"]["DRIVERS_DOOR_OPEN"]) elif self.CP.carFingerprint == CAR.ODYSSEY_CHN: @@ -201,11 +201,11 @@ class CarState(CarStateBase): ret.seatbeltUnlatched = bool(cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LAMP"] or not cp.vl["SEATBELT_STATUS"]["SEATBELT_DRIVER_LATCHED"]) steer_status = self.steer_status_values[cp.vl["STEER_STATUS"]["STEER_STATUS"]] - ret.steerError = steer_status not in ["NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT"] + ret.steerError = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_1", "NO_TORQUE_ALERT_2", "LOW_SPEED_LOCKOUT", "TMP_FAULT") # NO_TORQUE_ALERT_2 can be caused by bump OR steering nudge from driver - self.steer_not_allowed = steer_status not in ["NORMAL", "NO_TORQUE_ALERT_2"] + self.steer_not_allowed = steer_status not in ("NORMAL", "NO_TORQUE_ALERT_2") # LOW_SPEED_LOCKOUT is not worth a warning - ret.steerWarning = steer_status not in ["NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2"] + ret.steerWarning = steer_status not in ("NORMAL", "LOW_SPEED_LOCKOUT", "NO_TORQUE_ALERT_2") if not self.CP.openpilotLongitudinalControl: self.brake_error = 0 @@ -236,7 +236,7 @@ class CarState(CarStateBase): 250, cp.vl["SCM_FEEDBACK"]["LEFT_BLINKER"], cp.vl["SCM_FEEDBACK"]["RIGHT_BLINKER"]) ret.brakeHoldActive = cp.vl["VSA_STATUS"]["BRAKE_HOLD_ACTIVE"] == 1 - if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, + if self.CP.carFingerprint in (CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN, CAR.CRV_5G, CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E): self.park_brake = cp.vl["EPB_STATUS"]["EPB_STATE"] != 0 else: diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 7fcafe67f8..db7104cd4f 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -43,7 +43,7 @@ def create_brake_command(packer, apply_brake, pump_on, pcm_override, pcm_cancel_ return packer.make_can_msg("BRAKE_COMMAND", bus, values, idx) -def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, starting, car_fingerprint): +def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, car_fingerprint): commands = [] bus = get_pt_bus(car_fingerprint) min_gas_accel = CarControllerParams.BOSCH_GAS_LOOKUP_BP[0] @@ -53,7 +53,7 @@ def create_acc_commands(packer, enabled, active, accel, gas, idx, stopping, star accel_command = accel if active else 0 braking = 1 if active and accel < min_gas_accel else 0 standstill = 1 if active and stopping else 0 - standstill_release = 1 if active and starting else 0 + standstill_release = 1 if active and not stopping else 0 acc_control_values = { # setting CONTROL_ON causes car to set POWERTRAIN_DATA->ACC_STATUS = 1 diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 09804c3ebd..62dbb24c34 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -114,7 +114,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 1. ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.8], [0.24]] - elif candidate in (CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021): + elif candidate in (CAR.ACCORD, CAR.ACCORDH): stop_and_go = True ret.mass = 3279. * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.83 @@ -227,7 +227,7 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 ret.steerRatio = 11.95 # as spec ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] tire_stiffness_factor = 0.677 elif candidate == CAR.ODYSSEY: @@ -301,7 +301,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning else: - raise ValueError("unsupported car %s" % candidate) + raise ValueError(f"unsupported car {candidate}") # These cars use alternate user brake msg (0x1BE) if candidate in HONDA_BOSCH_ALT_BRAKE_SIGNAL: @@ -350,7 +350,6 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) ret.canValid = self.cp.can_valid and self.cp_cam.can_valid and (self.cp_body is None or self.cp_body.can_valid) - ret.yawRate = self.VM.yaw_rate(ret.steeringAngleDeg * CV.DEG_TO_RAD, ret.vEgo) buttonEvents = [] @@ -417,7 +416,7 @@ class CarInterface(CarInterfaceBase): for b in ret.buttonEvents: # do enable on both accel and decel buttons - if b.type in [ButtonType.accelCruise, ButtonType.decelCruise] and not b.pressed: + if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed: if not self.CP.pcmCruise: events.add(EventName.buttonEnable) @@ -433,18 +432,19 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): - if c.hudControl.speedVisible: - hud_v_cruise = c.hudControl.setSpeed * CV.MS_TO_KPH + hud_control = c.hudControl + if hud_control.speedVisible: + hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH else: hud_v_cruise = 255 - can_sends = self.CC.update(c.enabled, c.active, self.CS, self.frame, - c.actuators, - c.cruiseControl.cancel, - hud_v_cruise, - c.hudControl.lanesVisible, - hud_show_car=c.hudControl.leadVisible, - hud_alert=c.hudControl.visualAlert) + ret = self.CC.update(c.enabled, c.active, self.CS, self.frame, + c.actuators, + c.cruiseControl.cancel, + hud_v_cruise, + hud_control.lanesVisible, + hud_show_car=hud_control.leadVisible, + hud_alert=hud_control.visualAlert) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index dad3c9a938..cbe2369b14 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -66,8 +66,6 @@ VISUAL_HUD = { class CAR: ACCORD = "HONDA ACCORD 2018" ACCORDH = "HONDA ACCORD HYBRID 2018" - ACCORD_2021 = "HONDA ACCORD 2021" - ACCORDH_2021 = "HONDA ACCORD HYBRID 2021" CIVIC = "HONDA CIVIC 2016" CIVIC_BOSCH = "HONDA CIVIC (BOSCH) 2019" CIVIC_BOSCH_DIESEL = "HONDA CIVIC SEDAN 1.6 DIESEL 2019" @@ -105,7 +103,9 @@ FW_VERSIONS = { b'37805-6A0-A750\x00\x00', b'37805-6A0-A840\x00\x00', b'37805-6A0-A850\x00\x00', + b'37805-6A0-AF30\x00\x00', b'37805-6A0-AG30\x00\x00', + b'37805-6B2-C520\x00\x00', b'37805-6A0-C540\x00\x00', b'37805-6A1-H650\x00\x00', b'37805-6B2-A550\x00\x00', @@ -131,6 +131,7 @@ FW_VERSIONS = { b'28101-6A7-A410\x00\x00', b'28101-6A7-A510\x00\x00', b'28101-6A7-A610\x00\x00', + b'28101-6A7-A710\x00\x00', b'28101-6A9-H140\x00\x00', b'28101-6A9-H420\x00\x00', b'28102-6B8-A560\x00\x00', @@ -155,10 +156,12 @@ FW_VERSIONS = { b'57114-TVA-B040\x00\x00', b'57114-TVA-B050\x00\x00', b'57114-TVA-B060\x00\x00', + b'57114-TVA-B530\x00\x00', b'57114-TVA-C040\x00\x00', b'57114-TVA-C050\x00\x00', b'57114-TVA-C060\x00\x00', b'57114-TVA-C530\x00\x00', + b'57114-TVA-E520\x00\x00', b'57114-TVE-H250\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ @@ -166,6 +169,7 @@ FW_VERSIONS = { b'39990-TVA-A140\x00\x00', b'39990-TVA-A150\x00\x00', b'39990-TVA-A160\x00\x00', + b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', b'39990-TVA,A150\x00\x00', @@ -173,6 +177,7 @@ FW_VERSIONS = { ], (Ecu.unknown, 0x18da3af1, None): [ b'39390-TVA-A020\x00\x00', + b'39390-TVA-A120\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TBX-H230\x00\x00', @@ -191,6 +196,7 @@ FW_VERSIONS = { b'78109-TVA-A120\x00\x00', b'78109-TVA-A210\x00\x00', b'78109-TVA-A220\x00\x00', + b'78109-TVA-A230\x00\x00', b'78109-TVA-A310\x00\x00', b'78109-TVA-C010\x00\x00', b'78109-TVA-L010\x00\x00', @@ -203,6 +209,7 @@ FW_VERSIONS = { b'78109-TVC-A130\x00\x00', b'78109-TVC-A210\x00\x00', b'78109-TVC-A220\x00\x00', + b'78109-TVC-A230\x00\x00', b'78109-TVC-C010\x00\x00', b'78109-TVC-C110\x00\x00', b'78109-TVC-L010\x00\x00', @@ -214,12 +221,15 @@ FW_VERSIONS = { ], (Ecu.hud, 0x18da61f1, None): [ b'78209-TVA-A010\x00\x00', + b'78209-TVA-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TBX-H140\x00\x00', b'36802-TVA-A150\x00\x00', b'36802-TVA-A160\x00\x00', b'36802-TVA-A170\x00\x00', + b'36802-TVA-A330\x00\x00', + b'36802-TVC-A330\x00\x00', b'36802-TVE-H070\x00\x00', b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', @@ -227,6 +237,7 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TBX-H130\x00\x00', b'36161-TVA-A060\x00\x00', + b'36161-TVA-A330\x00\x00', b'36161-TVC-A330\x00\x00', b'36161-TVE-H050\x00\x00', b'36161-TWA-A070\x00\x00', @@ -246,6 +257,7 @@ FW_VERSIONS = { (Ecu.vsa, 0x18da28f1, None): [ b'57114-TWA-A040\x00\x00', b'57114-TWA-A050\x00\x00', + b'57114-TWA-A530\x00\x00', b'57114-TWA-B520\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ @@ -258,6 +270,7 @@ FW_VERSIONS = { b'78109-TWA-A030\x00\x00', b'78109-TWA-A110\x00\x00', b'78109-TWA-A120\x00\x00', + b'78109-TWA-A130\x00\x00', b'78109-TWA-A210\x00\x00', b'78109-TWA-A220\x00\x00', b'78109-TWA-A230\x00\x00', @@ -273,95 +286,18 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TWA-A070\x00\x00', + b'36161-TWA-A330\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', + b'36802-TWA-A330\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ b'39990-TVA-A160\x00\x00', b'39990-TVA-A150\x00\x00', - ], - }, - CAR.ACCORD_2021: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-6A0-AF30\x00\x00', - b'37805-6B2-C520\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-6A7-A710\x00\x00', - b'28102-6B8-A700\x00\x00', - ], - (Ecu.electricBrakeBooster, 0x18da2bf1, None): [ - b'46114-TVA-A320\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TVA-A020\x00\x00', - b'38897-TVA-A240\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TVA-B530\x00\x00', - b'57114-TVA-E520\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TVA-L420\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TVA-A230\x00\x00', - b'78109-TVC-A230\x00\x00', - ], - (Ecu.hud, 0x18da61f1, None): [ - b'78209-TVA-A110\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TVC-A910\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TVA-A330\x00\x00', - b'36161-TVC-A330\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TVA-A330\x00\x00', - b'36802-TVC-A330\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ b'39990-TVA-A340\x00\x00', ], - (Ecu.unknown, 0x18da3af1, None): [ - b'39390-TVA-A120\x00\x00', - ], - }, - CAR.ACCORDH_2021: { - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TWD-J020\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TWA-A530\x00\x00', - b'57114-TWA-B520\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TWA-L420\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TWA-A030\x00\x00', - b'78109-TWA-A130\x00\x00', - b'78109-TWA-A230\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TWA-A910\x00\x00', - ], - (Ecu.fwdCamera, 0x18dab5f1, None): [ - b'36161-TWA-A330\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36802-TWA-A330\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TVA-A340\x00\x00', - ], - (Ecu.unknown, 0x18da3af1, None): [ - b'39390-TVA-A120\x00\x00', - ], }, CAR.CIVIC: { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ @@ -1419,9 +1355,7 @@ FW_VERSIONS = { DBC = { CAR.ACCORD: dbc_dict('honda_accord_2018_can_generated', None), - CAR.ACCORD_2021: dbc_dict('honda_accord_2018_can_generated', None), CAR.ACCORDH: dbc_dict('honda_accord_2018_can_generated', None), - CAR.ACCORDH_2021: dbc_dict('honda_accord_2018_can_generated', None), CAR.ACURA_ILX: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.ACURA_RDX: dbc_dict('acura_rdx_2018_can_generated', 'acura_ilx_2016_nidec'), CAR.ACURA_RDX_3G: dbc_dict('acura_rdx_2020_can_generated', None), @@ -1451,9 +1385,9 @@ STEER_THRESHOLD = { CAR.CRV_EU: 400, } -HONDA_NIDEC_ALT_PCM_ACCEL = set([CAR.ODYSSEY]) -HONDA_NIDEC_ALT_SCM_MESSAGES = set([CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, - CAR.PILOT, CAR.PILOT_2019, CAR.PASSPORT, CAR.RIDGELINE]) -HONDA_BOSCH = set([CAR.ACCORD, CAR.ACCORD_2021, CAR.ACCORDH, CAR.ACCORDH_2021, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, - CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E]) -HONDA_BOSCH_ALT_BRAKE_SIGNAL = set([CAR.ACCORD, CAR.ACCORD_2021, CAR.CRV_5G, CAR.ACURA_RDX_3G]) +HONDA_NIDEC_ALT_PCM_ACCEL = {CAR.ODYSSEY} +HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, + CAR.PILOT, CAR.PILOT_2019, CAR.PASSPORT, CAR.RIDGELINE} +HONDA_BOSCH = {CAR.ACCORD, CAR.ACCORDH, CAR.CIVIC_BOSCH, CAR.CIVIC_BOSCH_DIESEL, CAR.CRV_5G, + CAR.CRV_HYBRID, CAR.INSIGHT, CAR.ACURA_RDX_3G, CAR.HONDA_E} +HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G} diff --git a/selfdrive/car/hyundai/carcontroller.py b/selfdrive/car/hyundai/carcontroller.py index a8b70fcf90..6adefd26c6 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -13,7 +13,7 @@ LongCtrlState = car.CarControl.Actuators.LongControlState def process_hud_alert(enabled, fingerprint, visual_alert, left_lane, right_lane, left_lane_depart, right_lane_depart): - sys_warning = (visual_alert in [VisualAlert.steerRequired, VisualAlert.ldw]) + sys_warning = (visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw)) # initialize to no line visible sys_state = 1 @@ -28,9 +28,9 @@ def process_hud_alert(enabled, fingerprint, visual_alert, left_lane, left_lane_warning = 0 right_lane_warning = 0 if left_lane_depart: - left_lane_warning = 1 if fingerprint in [CAR.GENESIS_G90, CAR.GENESIS_G80] else 2 + left_lane_warning = 1 if fingerprint in (CAR.GENESIS_G90, CAR.GENESIS_G80) else 2 if right_lane_depart: - right_lane_warning = 1 if fingerprint in [CAR.GENESIS_G90, CAR.GENESIS_G80] else 2 + right_lane_warning = 1 if fingerprint in (CAR.GENESIS_G90, CAR.GENESIS_G80) else 2 return sys_warning, sys_state, left_lane_warning, right_lane_warning @@ -44,6 +44,7 @@ class CarController(): self.car_fingerprint = CP.carFingerprint self.steer_rate_limited = False self.last_resume_frame = 0 + self.accel = 0 def update(self, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, hud_speed, left_lane, right_lane, left_lane_depart, right_lane_depart): @@ -100,12 +101,13 @@ class CarController(): stopping = (actuators.longControlState == LongCtrlState.stopping) set_speed_in_units = hud_speed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH) can_sends.extend(create_acc_commands(self.packer, enabled, accel, jerk, int(frame / 2), lead_visible, set_speed_in_units, stopping)) + self.accel = accel # 20 Hz LFA MFA message - if frame % 5 == 0 and self.car_fingerprint in [CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, + if frame % 5 == 0 and self.car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020]: + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022): can_sends.append(create_lfahda_mfc(self.packer, enabled)) # 5 Hz ACC options @@ -116,4 +118,8 @@ class CarController(): if frame % 50 == 0 and CS.CP.openpilotLongitudinalControl: can_sends.append(create_frt_radar_opt(self.packer)) - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / self.p.STEER_MAX + new_actuators.accel = self.accel + + return new_actuators, can_sends diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 50031231bc..fd3fc78e88 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -16,10 +16,10 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, values["CF_Lkas_ActToi"] = steer_req values["CF_Lkas_MsgCount"] = frame % 0x10 - if car_fingerprint in [CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, + if car_fingerprint in (CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]: + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 57b313e534..89e1934cdf 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -41,11 +41,10 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] ret.stopAccel = 0.0 - ret.startAccel = 0.0 ret.longitudinalActuatorDelayUpperBound = 1.0 # s - if candidate in [CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022]: + if candidate in (CAR.SANTA_FE, CAR.SANTA_FE_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3982. * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.766 @@ -54,7 +53,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.82 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]] - elif candidate in [CAR.SONATA, CAR.SONATA_HYBRID]: + elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID): ret.lateralTuning.pid.kf = 0.00005 ret.mass = 1513. + STD_CARGO_KG ret.wheelbase = 2.84 @@ -77,7 +76,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.63 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.3], [0.05]] - elif candidate in [CAR.ELANTRA, CAR.ELANTRA_GT_I30]: + elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): ret.lateralTuning.pid.kf = 0.00006 ret.mass = 1275. + STD_CARGO_KG ret.wheelbase = 2.7 @@ -117,7 +116,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] ret.lateralTuning.indi.actuatorEffectivenessV = [2.3] ret.minSteerSpeed = 60 * CV.KPH_TO_MS - elif candidate in [CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV]: + elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV): ret.lateralTuning.pid.kf = 0.00005 ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425.}.get(candidate, 1275.) + STD_CARGO_KG ret.wheelbase = 2.7 @@ -125,7 +124,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.385 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - elif candidate in [CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022]: + elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): ret.lateralTuning.pid.kf = 0.00006 ret.mass = 1490. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx ret.wheelbase = 2.7 @@ -133,7 +132,7 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.385 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - if candidate not in [CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022]: + if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): ret.minSteerSpeed = 32 * CV.MPH_TO_MS elif candidate == CAR.VELOSTER: ret.lateralTuning.pid.kf = 0.00005 @@ -152,7 +151,7 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - elif candidate in [CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021]: + elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021): ret.lateralTuning.pid.kf = 0.00006 ret.mass = 1737. + STD_CARGO_KG ret.wheelbase = 2.7 @@ -176,7 +175,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.indi.timeConstantV = [1.4] ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] ret.lateralTuning.indi.actuatorEffectivenessV = [1.8] - elif candidate in [CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_H]: + elif candidate in (CAR.KIA_OPTIMA, CAR.KIA_OPTIMA_H): ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 @@ -327,7 +326,7 @@ class CarInterface(CarInterfaceBase): for b in ret.buttonEvents: # do enable on both accel and decel buttons - if b.type in [ButtonType.accelCruise, ButtonType.decelCruise] and not b.pressed: + if b.type in (ButtonType.accelCruise, ButtonType.decelCruise) and not b.pressed: events.add(EventName.buttonEnable) # do disable on button down if b.type == ButtonType.cancel and b.pressed: @@ -347,8 +346,9 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, c.hudControl.setSpeed, c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, hud_control.setSpeed, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 6022069a2d..a7269e9283 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -74,7 +74,7 @@ class RadarInterface(RadarInterfaceBase): self.pts[addr].trackId = self.track_id self.track_id += 1 - valid = msg['STATE'] in [3, 4] + valid = msg['STATE'] in (3, 4) if valid: azimuth = math.radians(msg['AZIMUTH']) self.pts[addr].measured = True diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index df2e10dbe7..1b55bde3dd 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -10,13 +10,14 @@ class CarControllerParams: ACCEL_MAX = 2.0 # m/s def __init__(self, CP): - if CP.carFingerprint in [CAR.SONATA, CAR.PALISADE, CAR.SANTA_FE, CAR.VELOSTER, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, - CAR.IONIQ_EV_2020, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.ELANTRA_2021, - CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.KONA_EV, CAR.KONA, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]: - self.STEER_MAX = 384 - else: + # To determine the limit for your car, find the maximum value that the stock LKAS will request. + # If the max stock LKAS request is <384, add your car to this list. + if CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, + CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_HEV, + CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA, CAR.KIA_SORENTO, CAR.KIA_STINGER): self.STEER_MAX = 255 + else: + self.STEER_MAX = 384 self.STEER_DELTA_UP = 3 self.STEER_DELTA_DOWN = 7 self.STEER_DRIVER_ALLOWANCE = 50 @@ -41,6 +42,7 @@ class CAR: SANTA_FE = "HYUNDAI SANTA FE 2019" SANTA_FE_2022 = "HYUNDAI SANTA FE 2022" SANTA_FE_HEV_2022 = "HYUNDAI SANTA FE HYBRID 2022" + SANTA_FE_PHEV_2022 = "HYUNDAI SANTA FE PlUG-IN HYBRID 2022" SONATA = "HYUNDAI SONATA 2020" SONATA_LF = "HYUNDAI SONATA 2019" PALISADE = "HYUNDAI PALISADE 2020" @@ -498,6 +500,23 @@ FW_VERSIONS = { b'\xf1\x87391312MTC1', ], }, + CAR.SANTA_FE_PHEV_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391312MTF0', + ], + }, CAR.KIA_STINGER: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', @@ -777,25 +796,22 @@ FW_VERSIONS = { b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', - b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', b'\xf1\x8799110Q4500\xf1\000DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', - ], - (Ecu.esp, 0x7D1, None): [ - b'\xf1\x00OS IEB \r 212 \x11\x13 58520-K4000', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', - b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\000DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', - b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', ], }, CAR.KIA_NIRO_HEV: { @@ -981,25 +997,25 @@ FW_VERSIONS = { } CHECKSUM = { - "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022], + "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022], "6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS], } FEATURES = { # which message has the gear - "use_cluster_gears": set([CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA]), - "use_tcu_gears": set([CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER]), - "use_elect_gears": set([CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021,CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]), + "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, + "use_tcu_gears": {CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER}, + "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021,CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": set([CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]), + "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022}, } -HYBRID_CAR = set([CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]) # these cars use a different gas signal -EV_CAR = set([CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV]) +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022} # these cars use a different gas signal +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV} # these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = set([CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_NIRO_EV, CAR.KIA_OPTIMA, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022]) +LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} # If 0x500 is present on bus 1 it probably has a Mando radar outputting radar points. # If no points are outputted by default it might be possible to turn it on using selfdrive/debug/hyundai_enable_radar_points.py @@ -1020,7 +1036,7 @@ DBC = { CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None), CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None), - CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.KIA_NIRO_HEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), CAR.KIA_OPTIMA: dbc_dict('hyundai_kia_generic', None), @@ -1034,6 +1050,7 @@ DBC = { CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None), + CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 487fcab8c7..089dc2045d 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,6 +1,7 @@ import os import time -from typing import Dict +from abc import abstractmethod, ABC +from typing import Dict, Tuple, List from cereal import car from common.kalman.simple_kalman import KF1D @@ -14,9 +15,7 @@ from selfdrive.controls.lib.vehicle_model import VehicleModel GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName -# WARNING: this value was determined based on the model's training distribution, -# model predictions above this speed can be unpredictable -MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS # 135 + 4 = 86 mph +MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 @@ -24,7 +23,7 @@ ACCEL_MIN = -3.5 # generic car and radar interfaces -class CarInterfaceBase(): +class CarInterfaceBase(ABC): def __init__(self, CP, CarController, CarState): self.CP = CP self.VM = VehicleModel(CP) @@ -50,8 +49,9 @@ class CarInterfaceBase(): return ACCEL_MIN, ACCEL_MAX @staticmethod + @abstractmethod def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): - raise NotImplementedError + pass @staticmethod def init(CP, logcan, sendcan): @@ -84,13 +84,10 @@ class CarInterfaceBase(): ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this ret.steerRatioRear = 0. # no rear steering, at least on the listed cars aboveA ret.openpilotLongitudinalControl = False - ret.minSpeedCan = 0.3 - ret.startAccel = -0.8 ret.stopAccel = -2.0 - ret.startingAccelRate = 3.2 # brake_travel/s while releasing on restart ret.stoppingDecelRate = 0.8 # brake_travel/s while trying to stop ret.vEgoStopping = 0.5 - ret.vEgoStarting = 0.5 + ret.vEgoStarting = 0.5 # needs to be >= vEgoStopping to avoid state transition oscillation ret.stoppingControl = True ret.longitudinalTuning.deadzoneBP = [0.] ret.longitudinalTuning.deadzoneV = [0.] @@ -102,13 +99,13 @@ class CarInterfaceBase(): ret.longitudinalActuatorDelayUpperBound = 0.15 return ret - # returns a car.CarState, pass in car.CarControl - def update(self, c, can_strings): - raise NotImplementedError + @abstractmethod + def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState: + pass - # return sendcan, pass in a car.CarControl - def apply(self, c): - raise NotImplementedError + @abstractmethod + def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: + pass def create_common_events(self, cs_out, extra_gears=None, gas_resume_speed=-1, pcm_enable=True): events = Events() @@ -171,7 +168,7 @@ class CarInterfaceBase(): return events -class RadarInterfaceBase(): +class RadarInterfaceBase(ABC): def __init__(self, CP): self.pts = {} self.delay = 0 @@ -185,7 +182,7 @@ class RadarInterfaceBase(): return ret -class CarStateBase: +class CarStateBase(ABC): def __init__(self, CP): self.CP = CP self.car_fingerprint = CP.carFingerprint diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 06c5eb0948..c65ff72ed5 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -58,4 +58,8 @@ class CarController(): # send steering command can_sends.append(mazdacan.create_steering_control(self.packer, CS.CP.carFingerprint, frame, apply_steer, CS.cam_lkas)) - return can_sends + + new_actuators = c.actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 8364bf000c..a4c0ce705d 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -22,7 +22,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)] ret.radarOffCan = True - ret.dashcamOnly = candidate not in [CAR.CX9_2021] + ret.dashcamOnly = candidate not in (CAR.CX9_2021,) ret.steerActuatorDelay = 0.1 ret.steerRateCost = 1.0 @@ -36,7 +36,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.19], [0.019]] ret.lateralTuning.pid.kf = 0.00006 - elif candidate in [CAR.CX9, CAR.CX9_2021]: + elif candidate in (CAR.CX9, CAR.CX9_2021): ret.mass = 4217 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 3.1 ret.steerRatio = 17.6 @@ -95,6 +95,6 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c, self.CS, self.frame) + ret = self.CC.update(c, self.CS, self.frame) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 58e1dd7033..f18c30176c 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -50,9 +50,11 @@ FW_VERSIONS = { b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2K-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX38-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX42-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -65,6 +67,7 @@ FW_VERSIONS = { b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.esp, 0x760, None): [ b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -78,7 +81,9 @@ FW_VERSIONS = { b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -87,10 +92,12 @@ FW_VERSIONS = { b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB1-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB1-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB1-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -145,10 +152,12 @@ FW_VERSIONS = { b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KR11-3210X-K-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], (Ecu.engine, 0x7e0, None): [ b'P5JD-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PY2P-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYJW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKC-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -163,11 +172,13 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'B61L-67XK2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'P52G-21PS1-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKA-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -236,7 +247,7 @@ DBC = { } # Gen 1 hardware: same CAN messages and same camera -GEN1 = set([CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6]) +GEN1 = {CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6} # Cars with a steering lockout -STEER_LOCKOUT_CAR = set([CAR.CX5, CAR.CX9, CAR.MAZDA3, CAR.MAZDA6]) +STEER_LOCKOUT_CAR = {CAR.CX5, CAR.CX9, CAR.MAZDA3, CAR.MAZDA6} diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 999e735c73..bc1f6dcf6b 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -88,4 +88,5 @@ class CarInterface(CarInterfaceBase): def apply(self, c): # in mock no carcontrols - return [] + actuators = car.CarControl.Actuators.new_message() + return actuators, [] diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 41ba9f6599..40dd5da42a 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -31,7 +31,7 @@ class CarController(): lkas_hud_info_msg = CS.lkas_hud_info_msg apply_angle = actuators.steeringAngleDeg - steer_hud_alert = 1 if hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw] else 0 + steer_hud_alert = 1 if hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0 if enabled: # # windup slower @@ -64,14 +64,14 @@ class CarController(): # send acc cancel cmd if drive is disabled but pcm is still on, or if the system can't be activated cruise_cancel = 1 - if self.CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA] and cruise_cancel: + if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and cruise_cancel: can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg, frame)) # TODO: Find better way to cancel! # For some reason spamming the cancel button is unreliable on the Leaf # We now cancel by making propilot think the seatbelt is unlatched, # this generates a beep and a warning message every time you disengage - if self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC] and frame % 2 == 0: + if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC) and frame % 2 == 0: can_sends.append(nissancan.create_cancel_msg(self.packer, CS.cancel_msg, cruise_cancel)) can_sends.append(nissancan.create_steering_control( @@ -87,4 +87,7 @@ class CarController(): self.packer, lkas_hud_info_msg, steer_hud_alert )) - return can_sends + new_actuators = actuators.copy() + new_actuators.steeringAngleDeg = apply_angle + + return new_actuators, can_sends diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index 05191feedf..4c605395d4 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -23,16 +23,16 @@ class CarState(CarStateBase): def update(self, cp, cp_adas, cp_cam): ret = car.CarState.new_message() - if self.CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA]: + if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): ret.gas = cp.vl["GAS_PEDAL"]["GAS_PEDAL"] - elif self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): ret.gas = cp.vl["CRUISE_THROTTLE"]["GAS_PEDAL"] ret.gasPressed = bool(ret.gas > 3) - if self.CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA]: + if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): ret.brakePressed = bool(cp.vl["DOORS_LIGHTS"]["USER_BRAKE_PRESSED"]) - elif self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): ret.brakePressed = bool(cp.vl["CRUISE_THROTTLE"]["USER_BRAKE_PRESSED"]) ret.wheelSpeeds = self.get_wheel_speeds( @@ -51,10 +51,10 @@ class CarState(CarStateBase): else: ret.cruiseState.enabled = bool(cp_adas.vl["CRUISE_STATE"]["CRUISE_ENABLED"]) - if self.CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL]: + if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL): ret.seatbeltUnlatched = cp.vl["HUD"]["SEATBELT_DRIVER_LATCHED"] == 0 ret.cruiseState.available = bool(cp_cam.vl["PRO_PILOT"]["CRUISE_ON"]) - elif self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + elif self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): if self.CP.carFingerprint == CAR.LEAF: ret.seatbeltUnlatched = cp.vl["SEATBELT"]["SEATBELT_DRIVER_LATCHED"] == 0 elif self.CP.carFingerprint == CAR.LEAF_IC: @@ -70,7 +70,7 @@ class CarState(CarStateBase): speed = cp_adas.vl["PROPILOT_HUD"]["SET_SPEED"] if speed != 255: - if self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): conversion = CV.MPH_TO_MS if cp.vl["HUD_SETTINGS"]["SPEED_MPH"] else CV.KPH_TO_MS else: conversion = CV.MPH_TO_MS if cp.vl["HUD"]["SPEED_MPH"] else CV.KPH_TO_MS @@ -108,7 +108,7 @@ class CarState(CarStateBase): self.cruise_throttle_msg = copy.copy(cp.vl["CRUISE_THROTTLE"]) - if self.CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + if self.CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): self.cancel_msg = copy.copy(cp.vl["CANCEL_MSG"]) if self.CP.carFingerprint != CAR.ALTIMA: @@ -153,7 +153,7 @@ class CarState(CarStateBase): ("LIGHTS", 10), ] - if CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA]: + if CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA): signals += [ ("USER_BRAKE_PRESSED", "DOORS_LIGHTS", 1), @@ -183,7 +183,7 @@ class CarState(CarStateBase): ("HUD", 25), ] - elif CP.carFingerprint in [CAR.LEAF, CAR.LEAF_IC]: + elif CP.carFingerprint in (CAR.LEAF, CAR.LEAF_IC): signals += [ ("USER_BRAKE_PRESSED", "CRUISE_THROTTLE", 1), ("GAS_PEDAL", "CRUISE_THROTTLE", 0), @@ -344,7 +344,7 @@ class CarState(CarStateBase): signals = [] checks = [] - if CP.carFingerprint in [CAR.ROGUE, CAR.XTRAIL]: + if CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL): signals += [ ("CRUISE_ON", "PRO_PILOT", 0), ] diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 18ae885f83..4350fb5447 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -21,12 +21,12 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 - if candidate in [CAR.ROGUE, CAR.XTRAIL]: + if candidate in (CAR.ROGUE, CAR.XTRAIL): ret.mass = 1610 + STD_CARGO_KG ret.wheelbase = 2.705 ret.centerToFront = ret.wheelbase * 0.44 ret.steerRatio = 17 - elif candidate in [CAR.LEAF, CAR.LEAF_IC]: + elif candidate in (CAR.LEAF, CAR.LEAF_IC): ret.mass = 1610 + STD_CARGO_KG ret.wheelbase = 2.705 ret.centerToFront = ret.wheelbase * 0.44 @@ -78,9 +78,10 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, c.hudControl.rightLaneVisible, - c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index 6163d7329c..72b07f9192 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -8,11 +8,11 @@ class CarController(): def __init__(self, dbc_name, CP, VM): self.apply_steer_last = 0 self.es_distance_cnt = -1 - self.es_accel_cnt = -1 self.es_lkas_cnt = -1 self.cruise_button_prev = 0 self.steer_rate_limited = False + self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) def update(self, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): @@ -20,23 +20,23 @@ class CarController(): can_sends = [] # *** steering *** - if (frame % CarControllerParams.STEER_STEP) == 0: + if (frame % self.p.STEER_STEP) == 0: - apply_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) + apply_steer = int(round(actuators.steer * self.p.STEER_MAX)) # limits due to driver torque new_steer = int(round(apply_steer)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, CarControllerParams) + apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p) self.steer_rate_limited = new_steer != apply_steer if not enabled: apply_steer = 0 if CS.CP.carFingerprint in PREGLOBAL_CARS: - can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, CarControllerParams.STEER_STEP)) + can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) else: - can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, CarControllerParams.STEER_STEP)) + can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) self.apply_steer_last = apply_steer @@ -44,7 +44,7 @@ class CarController(): # *** alerts and pcm cancel *** if CS.CP.carFingerprint in PREGLOBAL_CARS: - if self.es_accel_cnt != CS.es_accel_msg["Counter"]: + if self.es_distance_cnt != CS.es_distance_msg["Counter"]: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep # disengage ACC when OP is disengaged if pcm_cancel_cmd: @@ -60,8 +60,8 @@ class CarController(): cruise_button = 0 self.cruise_button_prev = cruise_button - can_sends.append(subarucan.create_es_throttle_control(self.packer, cruise_button, CS.es_accel_msg)) - self.es_accel_cnt = CS.es_accel_msg["Counter"] + can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg)) + self.es_distance_cnt = CS.es_distance_msg["Counter"] else: if self.es_distance_cnt != CS.es_distance_msg["Counter"]: @@ -72,4 +72,7 @@ class CarController(): can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart)) self.es_lkas_cnt = CS.es_lkas_msg["Counter"] - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 1f56f09ff7..b6eb54287f 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -65,14 +65,13 @@ class CarState(CarStateBase): ret.steerError = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 if self.car_fingerprint in PREGLOBAL_CARS: - self.cruise_button = cp_cam.vl["ES_CruiseThrottle"]["Cruise_Button"] + self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] - self.es_accel_msg = copy.copy(cp_cam.vl["ES_CruiseThrottle"]) else: ret.steerWarning = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 - self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"]) self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) + self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"]) return ret @@ -153,7 +152,7 @@ class CarState(CarStateBase): ("CruiseControl", 50), ] - if CP.carFingerprint in [CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018]: + if CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): checks += [ ("Dashlights", 10), ("CruiseControl", 50), @@ -168,28 +167,28 @@ class CarState(CarStateBase): ("Cruise_Set_Speed", "ES_DashStatus", 0), ("Not_Ready_Startup", "ES_DashStatus", 0), - ("Throttle_Cruise", "ES_CruiseThrottle", 0), - ("Signal1", "ES_CruiseThrottle", 0), - ("Cruise_Activated", "ES_CruiseThrottle", 0), - ("Signal2", "ES_CruiseThrottle", 0), - ("Brake_On", "ES_CruiseThrottle", 0), - ("Distance_Swap", "ES_CruiseThrottle", 0), - ("Standstill", "ES_CruiseThrottle", 0), - ("Signal3", "ES_CruiseThrottle", 0), - ("Close_Distance", "ES_CruiseThrottle", 0), - ("Signal4", "ES_CruiseThrottle", 0), - ("Standstill_2", "ES_CruiseThrottle", 0), - ("Cruise_Fault", "ES_CruiseThrottle", 0), - ("Signal5", "ES_CruiseThrottle", 0), - ("Counter", "ES_CruiseThrottle", 0), - ("Signal6", "ES_CruiseThrottle", 0), - ("Cruise_Button", "ES_CruiseThrottle", 0), - ("Signal7", "ES_CruiseThrottle", 0), + ("Cruise_Throttle", "ES_Distance", 0), + ("Signal1", "ES_Distance", 0), + ("Car_Follow", "ES_Distance", 0), + ("Signal2", "ES_Distance", 0), + ("Brake_On", "ES_Distance", 0), + ("Distance_Swap", "ES_Distance", 0), + ("Standstill", "ES_Distance", 0), + ("Signal3", "ES_Distance", 0), + ("Close_Distance", "ES_Distance", 0), + ("Signal4", "ES_Distance", 0), + ("Standstill_2", "ES_Distance", 0), + ("Cruise_Fault", "ES_Distance", 0), + ("Signal5", "ES_Distance", 0), + ("Counter", "ES_Distance", 0), + ("Signal6", "ES_Distance", 0), + ("Cruise_Button", "ES_Distance", 0), + ("Signal7", "ES_Distance", 0), ] checks = [ ("ES_DashStatus", 20), - ("ES_CruiseThrottle", 20), + ("ES_Distance", 20), ] else: signals = [ diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 86bba542cc..6c8659b4c0 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -45,6 +45,16 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] + if candidate == CAR.IMPREZA_2020: + ret.mass = 1480. + STD_CARGO_KG + ret.wheelbase = 2.67 + ret.centerToFront = ret.wheelbase * 0.5 + ret.steerRatio = 17 # learned, 14 stock + ret.steerActuatorDelay = 0.1 + ret.lateralTuning.pid.kf = 0.00005 + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] + if candidate == CAR.FORESTER: ret.mass = 1568. + STD_CARGO_KG ret.wheelbase = 2.67 @@ -55,7 +65,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]] - if candidate in [CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018]: + if candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): ret.safetyConfigs[0].safetyParam = 1 # Outback 2018-2019 and Forester have reversed driver torque signal ret.mass = 1568 + STD_CARGO_KG ret.wheelbase = 2.67 @@ -112,8 +122,9 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, c.hudControl.rightLaneVisible, c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 6485de7901..86ec5e8bd4 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -80,11 +80,11 @@ def create_preglobal_steering_control(packer, apply_steer, frame, steer_step): return packer.make_can_msg("ES_LKAS", 0, values) -def create_es_throttle_control(packer, cruise_button, es_accel_msg): +def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): - values = copy.copy(es_accel_msg) + values = copy.copy(es_distance_msg) values["Cruise_Button"] = cruise_button - values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_CruiseThrottle") + values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_Distance") - return packer.make_can_msg("ES_CruiseThrottle", 0, values) + return packer.make_can_msg("ES_Distance", 0, values) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 39f8e5e598..5ec95b0076 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -5,17 +5,22 @@ from cereal import car Ecu = car.CarParams.Ecu class CarControllerParams: - STEER_MAX = 2047 # max_steer 4095 - STEER_STEP = 2 # how often we update the steer cmd - STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max - STEER_DELTA_DOWN = 70 # torque decrease per refresh - STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting - STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily - STEER_DRIVER_FACTOR = 1 # from dbc + def __init__(self, CP): + if CP.carFingerprint == CAR.IMPREZA_2020: + self.STEER_MAX = 1439 + else: + self.STEER_MAX = 2047 + self.STEER_STEP = 2 # how often we update the steer cmd + self.STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max + self.STEER_DELTA_DOWN = 70 # torque decrease per refresh + self.STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting + self.STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily + self.STEER_DRIVER_FACTOR = 1 # from dbc class CAR: ASCENT = "SUBARU ASCENT LIMITED 2019" IMPREZA = "SUBARU IMPREZA LIMITED 2019" + IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020" FORESTER = "SUBARU FORESTER 2019" FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018" LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018" @@ -23,54 +28,340 @@ class CAR: OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019" FINGERPRINTS = { - CAR.ASCENT: [{ - # SUBARU ASCENT LIMITED 2019 - 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1657: 8, 1658: 8, 1677: 8, 1722: 8, 1743: 8, 1759: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8 - }], CAR.IMPREZA: [{ 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 827: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 }], + CAR.IMPREZA_2020: [{ + 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1617: 8, 1632: 8, 1650: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8, 1968: 8, 1976: 8, 2015: 8, 2016: 8, 2024: 8 + }, + { + 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 544: 8, 545: 8, 546: 8, 554: 8, 557: 8, 576: 8, 577: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 + }], CAR.FORESTER: [{ - # Forester 2019-2020 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 961: 8, 984: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1651: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1698: 8, 1722: 8, 1743: 8, 1759: 8, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 }], - CAR.OUTBACK_PREGLOBAL: [{ - # OUTBACK PREMIUM 2.5i 2015 - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 346: 8, 352: 8, 353: 8, 354: 8, 356: 8, 358: 8, 359: 8, 392: 8, 640: 8, 642: 8, 644: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 977: 8, 1632: 8, 1745: 8, 1786: 5, 1882: 8, 2015: 8, 2016: 8, 2024: 8, 604: 8, 885: 8, 1788: 8, 316: 8, 1614: 8, 1640: 8, 1657: 8, 1658: 8, 1672: 8, 1743: 8, 1785: 5, 1787: 5 +} + +FW_VERSIONS = { + CAR.ASCENT: { + (Ecu.esp, 0x7b0, None): [ + b'\xa5 \x19\x02\x00', + b'\xa5 !\002\000', + b'\xf1\x82\xa5 \x19\x02\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x85\xc0\xd0\x00', + b'\005\xc0\xd0\000', + b'\x95\xc0\xd0\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00d\xb9\x1f@ \x10', + b'\000\000e~\037@ \'', + b'\x00\x00e@\x1f@ $', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xbb,\xa0t\a', + b'\xf1\x82\xbb,\xa0t\x87', + b'\xf1\x82\xbb,\xa0t\a', + b'\xf1\x82\xd9,\xa0@\a', + b'\xf1\x82\xd1,\xa0q\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x00\xfe\xf7\x00\x00', + b'\001\xfe\xf9\000\000', + b'\x01\xfe\xf7\x00\x00', + ], }, - # OUTBACK PREMIUM 3.6i 2015 - { - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 392: 8, 604: 8, 640: 8, 642: 8, 644: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 977: 8, 1632: 8, 1745: 8, 1779: 8, 1786: 5 + CAR.IMPREZA: { + (Ecu.esp, 0x7b0, None): [ + b'\x7a\x94\x3f\x90\x00', + b'\xa2 \x185\x00', + b'\xa2 \x193\x00', + b'z\x94.\x90\x00', + b'z\x94\b\x90\x01', + b'\xa2 \x19`\x00', + b'z\x94\f\x90\001', + b'z\x9c\x19\x80\x01', + b'z\x94\x08\x90\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x7a\xc0\x0c\x00', + b'z\xc0\b\x00', + b'\x8a\xc0\x00\x00', + b'z\xc0\x04\x00', + b'z\xc0\x00\x00', + b'\x8a\xc0\x10\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', + b'\x00\x00d\xdc\x1f@ \x0e', + b'\x00\x00e\x1c\x1f@ \x14', + b'\x00\x00d)\x1f@ \a', + b'\x00\x00e+\x1f@ \x14', + b'\000\000e+\000\000\000\000', + b'\000\000dd\037@ \016', + b'\000\000e\002\037@ \024', + b'\x00\x00d)\x00\x00\x00\x00', + b'\x00\x00c\xf4\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xaa\x61\x66\x73\x07', + b'\xbeacr\a', + b'\xc5!`r\a', + b'\xaa!ds\a', + b'\xaa!`u\a', + b'\xaa!dq\a', + b'\xaa!dt\a', + b'\xf1\x00\xa2\x10\t' + b'\xc5!dr\a', + b'\xc5!ar\a', + b'\xbe!as\a', + b'\xc5!ds\a', + b'\xc5!`s\a', + b'\xaa!au\a', + b'\xbe!at\a', + b'\xaa\x00Bu\x07', + b'\xc5!dr\x07', + b'\xaa!aw\x07', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xe3\xe5\x46\x31\x00', + b'\xe4\xe5\x061\x00', + b'\xe5\xf5\x04\x00\x00', + b'\xe3\xf5G\x00\x00', + b'\xe3\xf5\a\x00\x00', + b'\xe3\xf5C\x00\x00', + b'\xf1\x00\xa4\x10@' + b'\xe5\xf5B\x00\x00', + b'\xe5\xf5$\000\000', + b'\xe4\xf5\a\000\000', + b'\xe3\xf5F\000\000', + b'\xe4\xf5\002\000\000', + b'\xe3\xd0\x081\x00', + b'\xe3\xf5\x06\x00\x00', + ], }, - # OUTBACK LIMITED 2.5i 2018 - { - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 316: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 352: 8, 353: 8, 354: 8, 356: 8, 358: 8, 359: 8, 392: 8, 554: 8, 604: 8, 640: 8, 642: 8, 644: 8, 805: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 977: 8, 1614: 8, 1632: 8, 1657: 8, 1658: 8, 1672: 8, 1722: 8, 1736: 8, 1743: 8, 1745: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8 - }], - CAR.OUTBACK_PREGLOBAL_2018: [{ - # OUTBACK LIMITED 3.6R 2019 - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 316: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 352: 8, 353: 8, 354: 8, 356: 8, 358: 8, 359: 8, 392: 8, 554: 8, 604: 8, 640: 8, 642: 8, 644: 8, 805: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 886: 2, 977: 8, 1614: 8, 1632: 8, 1657: 8, 1658: 8, 1672: 8, 1736: 8, 1743: 8, 1745: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8, 1862: 8, 1870: 8, 1920: 8, 1927: 8, 1928: 8, 1935: 8, 1968: 8, 1976: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], - CAR.FORESTER_PREGLOBAL: [{ - # FORESTER PREMIUM 2.5i 2017 - 2: 8, 112: 8, 117: 8, 128: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 340: 7, 342: 8, 352: 8, 353: 8, 354: 8, 355: 8, 356: 8, 554: 8, 604: 8, 640: 8, 641: 8, 642: 8, 805: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 886: 1, 888: 8, 977: 8, 1398: 8, 1632: 8, 1743: 8, 1744: 8, 1745: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8, 1882: 8, 1895: 8, 1903: 8, 1986: 8, 1994: 8, 2015: 8, 2016: 8, 2024: 8, 644:8, 890:8, 1736:8 - }], - CAR.LEGACY_PREGLOBAL: [{ - # LEGACY 2.5i 2017 - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 392: 8, 604: 8, 640: 8, 642: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 977: 8, 1632: 8, 1640: 8, 1736: 8, 1745: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8, 352: 8, 353: 8, 354: 8, 356: 8, 358: 8, 359: 8, 644: 8 + CAR.IMPREZA_2020: { + (Ecu.esp, 0x7b0, None): [ + b'\xa2 \0314\000', + b'\xa2 \0313\000', + b'\xa2 !i\000', + b'\xa2 !`\000', + ], + (Ecu.eps, 0x746, None): [ + b'\x9a\xc0\000\000', + b'\n\xc0\004\000', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\000\000eb\037@ \"', + b'\000\000e\x8f\037@ )', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xca!ap\a', + b'\xca!`p\a', + b'\xca!`0\a', + b'\xcc\"f0\a', + b'\xcc!fp\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xe6\xf5\004\000\000', + b'\xe6\xf5$\000\000', + b'\xe7\xf6B0\000', + b'\xe7\xf5D0\000', + ], }, - # LEGACY 2018 - { - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 316: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 392: 8, 604: 8, 640: 8, 642: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 977: 8, 1614: 8, 1632: 8, 1640: 8, 1657: 8, 1658: 8, 1672: 8, 1722: 8, 1743: 8, 1745: 8, 1778: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8, 2015: 8, 2016: 8, 2024: 8 + CAR.FORESTER: { + (Ecu.esp, 0x7b0, None): [ + b'\xa3 \030\024\000', + b'\xa3 \024\000', + b'\xa3 \031\024\000', + b'\xa3 \024\001', + ], + (Ecu.eps, 0x746, None): [ + b'\x8d\xc0\004\000', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\000\000e!\037@ \021', + b'\000\000e\x97\037@ 0', + b'\000\000e`\037@ ', + b'\xf1\x00\xac\x02\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb6\"`A\a', + b'\xcf"`0\a', + b'\xcb\"`@\a', + b'\xcb\"`p\a', + b'\xf1\x00\xa2\x10\n', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\032\xf6B0\000', + b'\032\xf6F`\000', + b'\032\xf6b`\000', + b'\032\xf6B`\000' + b'\xf1\x00\xa4\x10@', + ], + }, + CAR.FORESTER_PREGLOBAL: { + (Ecu.esp, 0x7b0, None): [ + b'\x7d\x97\x14\x40', + b'\xf1\x00\xbb\x0c\x04', + ], + (Ecu.eps, 0x746, None): [ + b'}\xc0\x10\x00', + b'm\xc0\x10\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00\x64\x35\x1f\x40\x20\x09', + b'\x00\x00c\xe9\x1f@ \x03', + b'\x00\x00d\xd3\x1f@ \t' + ], + (Ecu.engine, 0x7e0, None): [ + b'\xba"@p\a', + b'\xa7)\xa0q\a', + b'\xf1\x82\xa7)\xa0q\a', + b'\xba"@@\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xdc\xf2\x60\x60\x00', + b'\xdc\xf2@`\x00', + b'\xda\xfd\xe0\x80\x00', + b'\xdc\xf2`\x81\000', + b'\xdc\xf2`\x80\x00', + ], + }, + CAR.LEGACY_PREGLOBAL: { + (Ecu.esp, 0x7b0, None): [ + b'k\x97D\x00', + b'[\xba\xc4\x03', + b'{\x97D\x00', + b'[\x97D\000', + ], + (Ecu.eps, 0x746, None): [ + b'[\xb0\x00\x01', + b'K\xb0\x00\x01', + b'k\xb0\x00\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00c\xb7\x1f@\x10\x16', + b'\x00\x00c\x94\x1f@\x10\x08', + b'\x00\x00c\xec\x1f@ \x04', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xab*@r\a', + b'\xa0+@p\x07', + b'\xb4"@0\x07', + b'\xa0"@q\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbe\xf2\x00p\x00', + b'\xbf\xfb\xc0\x80\x00', + b'\xbd\xf2\x00`\x00', + b'\xbf\xf2\000\x80\000', + ], + }, + CAR.OUTBACK_PREGLOBAL: { + (Ecu.esp, 0x7b0, None): [ + b'{\x9a\xac\x00', + b'k\x97\xac\x00', + b'\x5b\xf7\xbc\x03', + b'[\xf7\xac\x03', + b'{\x97\xac\x00', + b'k\x9a\xac\000', + b'[\xba\xac\x03', + b'[\xf7\xac\000', + ], + (Ecu.eps, 0x746, None): [ + b'k\xb0\x00\x00', + b'[\xb0\x00\x00', + b'\x4b\xb0\x00\x02', + b'K\xb0\x00\x00', + b'{\xb0\x00\x01', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00c\xec\x1f@ \x04', + b'\x00\x00c\xd1\x1f@\x10\x17', + b'\xf1\x00\xf0\xe0\x0e', + b'\x00\x00c\x94\x00\x00\x00\x00', + b'\x00\x00c\x94\x1f@\x10\b', + b'\x00\x00c\xb7\x1f@\x10\x16', + b'\000\000c\x90\037@\020\016', + b'\x00\x00c\xec\x37@\x04', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb4+@p\a', + b'\xab\"@@\a', + b'\xa0\x62\x41\x71\x07', + b'\xa0*@q\a', + b'\xab*@@\a', + b'\xb4"@0\a', + b'\xb4"@p\a', + b'\xab"@s\a', + b'\xab+@@\a', + b'\xb4"@r\a', + b'\xa0+@@\x07' + b'\xa0\"@\x80\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbd\xfb\xe0\x80\x00', + b'\xbe\xf2@\x80\x00', + b'\xbf\xe2\x40\x80\x00', + b'\xbf\xf2@\x80\x00', + b'\xbe\xf2@p\x00', + b'\xbd\xf2@`\x00', + b'\xbd\xf2@\x81\000', + b'\xbe\xfb\xe0p\000', + b'\xbf\xfb\xe0b\x00', + ], + }, + CAR.OUTBACK_PREGLOBAL_2018: { + (Ecu.esp, 0x7b0, None): [ + b'\x8b\x97\xac\x00', + b'\x8b\x9a\xac\x00', + b'\x9b\x97\xac\x00', + b'\x8b\x97\xbc\x00', + b'\x8b\x99\xac\x00', + b'\x9b\x9a\xac\000', + b'\x9b\x97\xbe\x10', + ], + (Ecu.eps, 0x746, None): [ + b'{\xb0\x00\x00', + b'{\xb0\x00\x01', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00df\x1f@ \n', + b'\x00\x00d\xfe\x1f@ \x15', + b'\x00\x00d\x95\x00\x00\x00\x00', + b'\x00\x00d\x95\x1f@ \x0f', + b'\x00\x00d\xfe\x00\x00\x00\x00', + b'\x00\x00e\x19\x1f@ \x15', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xb5"@p\a', + b'\xb5+@@\a', + b'\xb5"@P\a', + b'\xc4"@0\a', + b'\xb5b@1\x07', + b'\xb5q\xe0@\a', + b'\xc4+@0\a', + b'\xc4b@p\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xbc\xf2@\x81\x00', + b'\xbc\xfb\xe0\x80\x00', + b'\xbc\xf2@\x80\x00', + b'\xbb\xf2@`\x00', + b'\xbc\xe2@\x80\x00', + b'\xbc\xfb\xe0`\x00', + b'\xbc\xaf\xe0`\x00', + b'\xbb\xfb\xe0`\000', + ], }, - # LEGACY 2018 - { - 2: 8, 208: 8, 209: 4, 210: 8, 211: 7, 212: 8, 316: 8, 320: 8, 321: 8, 324: 8, 328: 8, 329: 8, 336: 2, 338: 8, 342: 8, 352: 8, 353: 8, 354: 8, 356: 8, 358: 8, 359: 8, 392: 8, 554: 8, 604: 8, 640: 8, 642: 8, 805: 8, 864: 8, 865: 8, 866: 8, 872: 8, 880: 8, 881: 8, 882: 8, 884: 8, 885: 8, 977: 8, 1614: 8, 1632: 8, 1640: 8, 1657: 8, 1658: 8, 1672: 8, 1722: 8, 1743: 8, 1745: 8, 1785: 5, 1786: 5, 1787: 5, 1788: 8, 2015: 8, 2016: 8, 2024: 8 - }], } STEER_THRESHOLD = { CAR.ASCENT: 80, CAR.IMPREZA: 80, + CAR.IMPREZA_2020: 80, CAR.FORESTER: 80, CAR.FORESTER_PREGLOBAL: 75, CAR.LEGACY_PREGLOBAL: 75, @@ -81,6 +372,7 @@ STEER_THRESHOLD = { DBC = { CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None), CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None), + CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None), CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index 7e6a2f2e9a..0b58632f0a 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -37,7 +37,7 @@ class CarController(): can_sends.append(self.tesla_can.create_steering_control(apply_angle, lkas_enabled, frame)) # Longitudinal control (40Hz) - if self.CP.openpilotLongitudinalControl and ((frame % 5) in [0, 2]): + if self.CP.openpilotLongitudinalControl and ((frame % 5) in (0, 2)): target_accel = actuators.accel target_speed = max(CS.out.vEgo + (target_accel * CarControllerParams.ACCEL_TO_SPEED_MULTIPLIER), 0) max_accel = 0 if target_accel < 0 else target_accel @@ -62,4 +62,7 @@ class CarController(): # TODO: HUD control - return can_sends + new_actuators = actuators.copy() + new_actuators.steeringAngleDeg = apply_angle + + return actuators, can_sends diff --git a/selfdrive/car/tesla/carstate.py b/selfdrive/car/tesla/carstate.py index 0a45b6f2bb..39869baeae 100644 --- a/selfdrive/car/tesla/carstate.py +++ b/selfdrive/car/tesla/carstate.py @@ -50,7 +50,7 @@ class CarState(CarStateBase): cruise_state = self.can_define.dv["DI_state"]["DI_cruiseState"].get(int(cp.vl["DI_state"]["DI_cruiseState"]), None) speed_units = self.can_define.dv["DI_state"]["DI_speedUnits"].get(int(cp.vl["DI_state"]["DI_speedUnits"]), None) - acc_enabled = (cruise_state in ["ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL"]) + acc_enabled = (cruise_state in ("ENABLED", "STANDSTILL", "OVERRIDE", "PRE_FAULT", "PRE_CANCEL")) ret.cruiseState.enabled = acc_enabled if speed_units == "KPH": diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 45dc0a7239..e8d6ab6854 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -25,7 +25,6 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kiBP = [0] ret.longitudinalTuning.kiV = [0] ret.stopAccel = 0.0 - ret.startAccel = 0.0 ret.longitudinalActuatorDelayUpperBound = 0.5 # s ret.radarTimeStep = (1.0 / 8) # 8Hz @@ -44,7 +43,7 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerRateCost = 0.5 - if candidate in [CAR.AP2_MODELS, CAR.AP1_MODELS]: + if candidate in (CAR.AP2_MODELS, CAR.AP1_MODELS): ret.mass = 2100. + STD_CARGO_KG ret.wheelbase = 2.959 ret.centerToFront = ret.wheelbase * 0.5 @@ -71,6 +70,6 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.cruiseControl.cancel) + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.cruiseControl.cancel) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 93dc05c838..930619ee67 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -59,7 +59,7 @@ class TestCarInterfaces(unittest.TestCase): car_interface.apply(CC) # Test radar interface - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % car_params.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface radar_interface = RadarInterface(car_params) assert radar_interface diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 1997e6c111..73ea2f350f 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -19,19 +19,19 @@ class CarController(): self.steer_rate_limited = False self.packer = CANPacker(dbc_name) + self.gas = 0 + self.accel = 0 def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_alert, left_line, right_line, lead, left_lane_depart, right_lane_depart): - # *** compute control surfaces *** - # gas and brake if CS.CP.enableGasInterceptor and enabled: MAX_INTERCEPTOR_GAS = 0.5 # RAV4 has very sensitive gas pedal - if CS.CP.carFingerprint in [CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH]: + if CS.CP.carFingerprint in (CAR.RAV4, CAR.RAV4H, CAR.HIGHLANDER, CAR.HIGHLANDERH): PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.15, 0.3, 0.0]) - elif CS.CP.carFingerprint in [CAR.COROLLA]: + elif CS.CP.carFingerprint in (CAR.COROLLA,): PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.3, 0.4, 0.0]) else: PEDAL_SCALE = interp(CS.out.vEgo, [0.0, MIN_ACC_SPEED, MIN_ACC_SPEED + PEDAL_TRANSITION], [0.4, 0.5, 0.0]) @@ -49,7 +49,7 @@ class CarController(): self.steer_rate_limited = new_steer != apply_steer # Cut steering while we're in a known fault state (2s) - if not enabled or CS.steer_state in [9, 25]: + if not enabled or CS.steer_state in (9, 25): apply_steer = 0 apply_steer_req = 0 else: @@ -89,26 +89,28 @@ class CarController(): # we can spam can to cancel the system even if we are using lat only control if (frame % 3 == 0 and CS.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: - lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present do ACC can be engaged + lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged # Lexus IS uses a different cancellation message - if pcm_cancel_cmd and CS.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: + if pcm_cancel_cmd and CS.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): can_sends.append(create_acc_cancel_command(self.packer)) elif CS.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) + self.accel = pcm_accel_cmd else: can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type)) - if frame % 2 == 0 and CS.CP.enableGasInterceptor: + if frame % 2 == 0 and CS.CP.enableGasInterceptor and CS.CP.openpilotLongitudinalControl: # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd. # This prevents unexpected pedal range rescaling can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2)) + self.gas = interceptor_gas_cmd # ui mesg is at 100Hz but we send asap if: # - there is something to display # - there is something to stop displaying fcw_alert = hud_alert == VisualAlert.fcw - steer_alert = hud_alert in [VisualAlert.steerRequired, VisualAlert.ldw] + steer_alert = hud_alert in (VisualAlert.steerRequired, VisualAlert.ldw) send_ui = False if ((fcw_alert or steer_alert) and not self.alert_active) or \ @@ -120,15 +122,19 @@ class CarController(): send_ui = True if (frame % 100 == 0 or send_ui): - can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart)) + can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart, enabled)) if frame % 100 == 0 and CS.CP.enableDsu: can_sends.append(create_fcw_command(self.packer, fcw_alert)) - #*** static msgs *** - + # *** static msgs *** for (addr, cars, bus, fr_step, vl) in STATIC_DSU_MSGS: if frame % fr_step == 0 and CS.CP.enableDsu and CS.CP.carFingerprint in cars: can_sends.append(make_can_msg(addr, vl, bus)) - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.accel = self.accel + new_actuators.gas = self.gas + + return new_actuators, can_sends diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 7ce5907b9b..f7d353e691 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -79,9 +79,9 @@ class CarState(CarStateBase): ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in [1, 5] + ret.steerWarning = cp.vl["EPS_STATUS"]["LKA_STATE"] not in (1, 5) - if self.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: + if self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS else: @@ -95,7 +95,7 @@ class CarState(CarStateBase): # these cars are identified by an ACC_TYPE value of 2. # TODO: it is possible to avoid the lockout and gain stop and go if you # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1 - if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in [CAR.LEXUS_IS, CAR.LEXUS_RC]) or \ + if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in (CAR.LEXUS_IS, CAR.LEXUS_RC)) or \ (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 @@ -107,7 +107,7 @@ class CarState(CarStateBase): else: ret.cruiseState.standstill = self.pcm_acc_status == 7 ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"]) - ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in [1, 2, 3, 4, 5, 6] + ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6) ret.genericToggle = bool(cp.vl["LIGHT_STALK"]["AUTO_HIGH_BEAM"]) ret.stockAeb = bool(cp_cam.vl["PRE_COLLISION"]["PRECOLLISION_ACTIVE"] and cp_cam.vl["PRE_COLLISION"]["FORCE"] < -1e-5) @@ -170,7 +170,7 @@ class CarState(CarStateBase): ("STEER_TORQUE_SENSOR", 50), ] - if CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: + if CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): signals.append(("MAIN_ON", "DSU_CRUISE", 0)) signals.append(("SET_SPEED", "DSU_CRUISE", 0)) checks.append(("DSU_CRUISE", 5)) diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index f9d6b586fd..22080985db 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -40,7 +40,7 @@ class CarInterface(CarInterfaceBase): set_lat_tune(ret.lateralTuning, LatTunes.INDI_PRIUS) ret.steerActuatorDelay = 0.3 - elif candidate in [CAR.RAV4, CAR.RAV4H]: + elif candidate in (CAR.RAV4, CAR.RAV4H): stop_and_go = True if (candidate in CAR.RAV4H) else False ret.wheelbase = 2.65 ret.steerRatio = 16.88 # 14.5 is spec end-to-end @@ -57,41 +57,16 @@ class CarInterface(CarInterfaceBase): ret.mass = 2860. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_A) - elif candidate == CAR.LEXUS_RX: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 14.8 - tire_stiffness_factor = 0.5533 - ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_B) - - elif candidate == CAR.LEXUS_RXH: + elif candidate in (CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2): stop_and_go = True ret.wheelbase = 2.79 ret.steerRatio = 16. # 14.8 is spec end-to-end - tire_stiffness_factor = 0.444 # not optimized yet + ret.wheelSpeedFactor = 1.035 + tire_stiffness_factor = 0.5533 ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max set_lat_tune(ret.lateralTuning, LatTunes.PID_C) - elif candidate == CAR.LEXUS_RX_TSS2: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 14.8 - tire_stiffness_factor = 0.5533 # not optimized yet - ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_D) - ret.wheelSpeedFactor = 1.035 - - elif candidate == CAR.LEXUS_RXH_TSS2: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 16.0 # 14.8 is spec end-to-end - tire_stiffness_factor = 0.444 # not optimized yet - ret.mass = 4481.0 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - set_lat_tune(ret.lateralTuning, LatTunes.PID_E) - ret.wheelSpeedFactor = 1.035 - - elif candidate in [CAR.CHR, CAR.CHRH]: + elif candidate in (CAR.CHR, CAR.CHRH): stop_and_go = True ret.wheelbase = 2.63906 ret.steerRatio = 13.6 @@ -99,7 +74,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 3300. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_F) - elif candidate in [CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2]: + elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): stop_and_go = True ret.wheelbase = 2.82448 ret.steerRatio = 13.7 @@ -107,7 +82,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 3400. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_C) - elif candidate in [CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2]: + elif candidate in (CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): stop_and_go = True ret.wheelbase = 2.84988 # 112.2 in = 2.84988 m ret.steerRatio = 16.0 @@ -115,7 +90,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 4700. * CV.LB_TO_KG + STD_CARGO_KG # 4260 + 4-5 people set_lat_tune(ret.lateralTuning, LatTunes.PID_G) - elif candidate in [CAR.HIGHLANDER, CAR.HIGHLANDERH]: + elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH): stop_and_go = True ret.wheelbase = 2.78 ret.steerRatio = 16.0 @@ -123,7 +98,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 4607. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid limited set_lat_tune(ret.lateralTuning, LatTunes.PID_G) - elif candidate in [CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019]: + elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019): stop_and_go = False ret.wheelbase = 2.82 ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download @@ -131,7 +106,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_H) - elif candidate in [CAR.RAV4_TSS2, CAR.RAV4H_TSS2]: + elif candidate in (CAR.RAV4_TSS2, CAR.RAV4H_TSS2): stop_and_go = True ret.wheelbase = 2.68986 ret.steerRatio = 14.3 @@ -146,7 +121,7 @@ class CarInterface(CarInterfaceBase): set_lat_tune(ret.lateralTuning, LatTunes.PID_I) break - elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2]: + elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2): stop_and_go = True ret.wheelbase = 2.67 # Average between 2.70 for sedan and 2.64 for hatchback ret.steerRatio = 13.9 @@ -154,7 +129,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 3060. * CV.LB_TO_KG + STD_CARGO_KG set_lat_tune(ret.lateralTuning, LatTunes.PID_D) - elif candidate in [CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2]: + elif candidate in (CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2): stop_and_go = True ret.wheelbase = 2.8702 ret.steerRatio = 16.0 # not optimized @@ -205,7 +180,7 @@ class CarInterface(CarInterfaceBase): ret.mass = 3108 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max set_lat_tune(ret.lateralTuning, LatTunes.PID_M) - elif candidate in [CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2]: + elif candidate in (CAR.LEXUS_NXH, CAR.LEXUS_NX, CAR.LEXUS_NX_TSS2): stop_and_go = True ret.wheelbase = 2.66 ret.steerRatio = 14.7 @@ -269,11 +244,9 @@ class CarInterface(CarInterfaceBase): if ret.enableGasInterceptor: set_long_tune(ret.longitudinalTuning, LongTunes.PEDAL) - elif candidate in [CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.RAV4_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_NX_TSS2, - CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2]: + elif candidate in TSS2_CAR: set_long_tune(ret.longitudinalTuning, LongTunes.TSS2) ret.stoppingDecelRate = 0.3 # reach stopping target smoothly - ret.startingAccelRate = 6.0 # release brakes fast else: set_long_tune(ret.longitudinalTuning, LongTunes.TSS) @@ -312,12 +285,12 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): - - can_sends = self.CC.update(c.enabled, c.active, self.CS, self.frame, - c.actuators, c.cruiseControl.cancel, - c.hudControl.visualAlert, c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, c.hudControl.leadVisible, - c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, c.active, self.CS, self.frame, + c.actuators, c.cruiseControl.cancel, + hud_control.visualAlert, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leadVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 5a78e3c7b6..0852991611 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -67,11 +67,11 @@ def create_fcw_command(packer, fcw): return packer.make_can_msg("ACC_HUD", 0, values) -def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart): +def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart, enabled): values = { "RIGHT_LINE": 3 if right_lane_depart else 1 if right_line else 2, "LEFT_LINE": 3 if left_lane_depart else 1 if left_line else 2, - "BARRIERS" : 3 if left_lane_depart else 2 if right_lane_depart else 0, + "BARRIERS" : 1 if enabled else 0, "SET_ME_X0C": 0x0c, "SET_ME_X2C": 0x2c, "SET_ME_X38": 0x38, diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 15c8bbfcc6..f26fc72a09 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -80,10 +80,6 @@ def set_lat_tune(tune, name): tune.pid.kpV = [0.2] tune.pid.kiV = [0.05] tune.pid.kf = 0.00003 - elif name == LatTunes.PID_B: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00006 elif name == LatTunes.PID_C: tune.pid.kpV = [0.6] tune.pid.kiV = [0.1] @@ -92,10 +88,6 @@ def set_lat_tune(tune, name): tune.pid.kpV = [0.6] tune.pid.kiV = [0.1] tune.pid.kf = 0.00007818594 - elif name == LatTunes.PID_E: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.15] - tune.pid.kf = 0.00007818594 elif name == LatTunes.PID_F: tune.pid.kpV = [0.723] tune.pid.kiV = [0.0428] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 275822530e..c8f48c9768 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -434,6 +434,7 @@ FW_VERSIONS = { }, CAR.CHRH: { (Ecu.engine, 0x700, None): [ + b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896631013200\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F405000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x0289663F418000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -525,6 +526,7 @@ FW_VERSIONS = { b'\x01896630ZG5100\x00\x00\x00\x00', b'\x01896630ZG5200\x00\x00\x00\x00', b'\x01896630ZG5300\x00\x00\x00\x00', + b'\x01896630ZP1000\x00\x00\x00\x00', b'\x01896630ZP2000\x00\x00\x00\x00', b'\x01896630ZQ5000\x00\x00\x00\x00', b'\x018966312L8000\x00\x00\x00\x00', @@ -535,6 +537,7 @@ FW_VERSIONS = { b'\x018966312P9200\x00\x00\x00\x00', b'\x018966312P9300\x00\x00\x00\x00', b'\x018966312Q2300\x00\x00\x00\x00', + b'\x018966312Q8000\x00\x00\x00\x00', b'\x018966312R0000\x00\x00\x00\x00', b'\x018966312R0100\x00\x00\x00\x00', b'\x018966312R1000\x00\x00\x00\x00', @@ -582,6 +585,7 @@ FW_VERSIONS = { b'\x01F152612B60\x00\x00\x00\x00\x00\x00', b'\x01F152612B61\x00\x00\x00\x00\x00\x00', b'\x01F152612B71\x00\x00\x00\x00\x00\x00', + b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00', b'\x01F152612C00\x00\x00\x00\x00\x00\x00', b'F152602191\x00\x00\x00\x00\x00\x00', @@ -767,6 +771,7 @@ FW_VERSIONS = { b'\x01896630EB2100\x00\x00\x00\x00', b'\x01896630EB2200\x00\x00\x00\x00', b'\x01896630EC4000\x00\x00\x00\x00', + b'\x01896630ED9000\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -796,6 +801,7 @@ FW_VERSIONS = { b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', @@ -1551,6 +1557,7 @@ FW_VERSIONS = { }, CAR.PRIUS_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', @@ -1563,6 +1570,7 @@ FW_VERSIONS = { b'F152647510\x00\x00\x00\x00\x00\x00', b'F152647520\x00\x00\x00\x00\x00\x00', b'F152647521\x00\x00\x00\x00\x00\x00', + b'F152647531\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B47070\x00\x00\x00\x00\x00\x00', @@ -1605,7 +1613,7 @@ DBC = { CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CHRH: dbc_dict('toyota_nodsu_hybrid_pt_generated', 'toyota_adas'), CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), - CAR.CAMRYH: dbc_dict('toyota_camry_hybrid_2018_pt_generated', 'toyota_adas'), + CAR.CAMRYH: dbc_dict('toyota_nodsu_hybrid_pt_generated', 'toyota_adas'), CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CAMRYH_TSS2: dbc_dict('toyota_nodsu_hybrid_pt_generated', 'toyota_tss2_adas'), CAR.HIGHLANDER: dbc_dict('toyota_highlander_2017_pt_generated', 'toyota_adas'), @@ -1635,11 +1643,11 @@ DBC = { # Toyota/Lexus Safety Sense 2.0 and 2.5 -TSS2_CAR = set([CAR.RAV4_TSS2, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, +TSS2_CAR = {CAR.RAV4_TSS2, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, - CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2]) + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2} -NO_DSU_CAR = TSS2_CAR | set([CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH]) +NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | set([CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH]) +NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index d0f3423120..999bb1a1c7 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -131,7 +131,7 @@ class CarController(): # **** HUD Controls ***************************************************** # if frame % P.LDW_STEP == 0: - if visual_alert in [VisualAlert.steerRequired, VisualAlert.ldw]: + if visual_alert in (VisualAlert.steerRequired, VisualAlert.ldw): hud_alert = MQB_LDW_MESSAGES["laneAssistTakeOverSilent"] else: hud_alert = MQB_LDW_MESSAGES["none"] @@ -169,4 +169,7 @@ class CarController(): self.graButtonStatesToSend = None self.graMsgSentCount = 0 - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / P.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 84a739a9bf..ff621e6ca9 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -41,8 +41,8 @@ class CarState(CarStateBase): # Verify EPS readiness to accept steering commands hca_status = self.hca_status_values.get(pt_cp.vl["LH_EPS_03"]["EPS_HCA_Status"]) - ret.steerError = hca_status in ["DISABLED", "FAULT"] - ret.steerWarning = hca_status in ["INITIALIZING", "REJECTED"] + ret.steerError = hca_status in ("DISABLED", "FAULT") + ret.steerWarning = hca_status in ("INITIALIZING", "REJECTED") # Update gas, brakes, and gearshift. ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0 @@ -104,7 +104,7 @@ class CarState(CarStateBase): # ACC okay and enabled, but not currently engaged ret.cruiseState.available = True ret.cruiseState.enabled = False - elif self.tsk_status in [3, 4, 5]: + elif self.tsk_status in (3, 4, 5): # ACC okay and enabled, currently regulating speed (3) or driver accel override (4) or brake only (5) ret.cruiseState.available = True ret.cruiseState.enabled = True diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 2179bf9501..d56b19d3ac 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -41,7 +41,7 @@ class CarInterface(CarInterfaceBase): else: ret.transmissionType = TransmissionType.manual - if any(msg in fingerprint[1] for msg in [0x40, 0x86, 0xB2, 0xFD]): # Airbag_01, LWI_01, ESP_19, ESP_21 + if any(msg in fingerprint[1] for msg in (0x40, 0x86, 0xB2, 0xFD)): # Airbag_01, LWI_01, ESP_19, ESP_21 ret.networkLocation = NetworkLocation.gateway else: ret.networkLocation = NetworkLocation.fwdCamera @@ -171,7 +171,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.84 else: - raise ValueError("unsupported car %s" % candidate) + raise ValueError(f"unsupported car {candidate}") ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.45 @@ -214,7 +214,7 @@ class CarInterface(CarInterfaceBase): # Vehicle health and operation safety checks if self.CS.parkingBrakeSet: events.add(EventName.parkBrake) - if self.CS.tsk_status in [6, 7]: + if self.CS.tsk_status in (6, 7): events.add(EventName.accFaulted) # Low speed steer alert hysteresis logic @@ -228,7 +228,7 @@ class CarInterface(CarInterfaceBase): if self.CS.CP.openpilotLongitudinalControl: for b in buttonEvents: # do enable on falling edge of both accel and decel buttons - if b.type in [ButtonType.setCruise, ButtonType.resumeCruise] and not b.pressed: + if b.type in (ButtonType.setCruise, ButtonType.resumeCruise) and not b.pressed: events.add(EventName.buttonEnable) # do disable on rising edge of cancel if b.type == "cancel" and b.pressed: @@ -245,14 +245,15 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, self.ext_bus, c.actuators, - c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, - c.hudControl.leftLaneDepart, - c.hudControl.rightLaneDepart, - c.hudControl.leadVisible, - c.hudControl.setSpeed, - c.hudControl.speedVisible) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, self.ext_bus, c.actuators, + hud_control.visualAlert, + hud_control.leftLaneVisible, + hud_control.rightLaneVisible, + hud_control.leftLaneDepart, + hud_control.rightLaneDepart, + hud_control.leadVisible, + hud_control.setSpeed, + hud_control.speedVisible) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 287d410f9f..6591babcfb 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -106,19 +106,24 @@ class CAR: FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x873G0906259F \xf1\x890004', b'\xf1\x873G0906259P \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158L \xf1\x893611', + b'\xf1\x870GC300011L \xf1\x891401', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900', b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\0161812141812171105141123052J00', ], (Ecu.eps, 0x712, None): [ + b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x875Q0907572J \xf1\x890654', ], }, CAR.ATLAS_MK1: { @@ -297,6 +302,7 @@ FW_VERSIONS = { b'\xf1\x8704E906024AK\xf1\x899937', b'\xf1\x8704E906024AS\xf1\x899912', b'\xf1\x8704E906024B \xf1\x895594', + b'\xf1\x8704E906024C \xf1\x899970', b'\xf1\x8704E906024L \xf1\x895595', b'\xf1\x8704E906024L \xf1\x899970', b'\xf1\x8704E906027MS\xf1\x896223', @@ -311,6 +317,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\02314171231313500314611011630169333463100', + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1314171231313500314611011630169333463100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314642011650169333463100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314643011650169333463100', b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100', @@ -327,6 +334,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572N \xf1\x890681', + b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', ], }, @@ -746,6 +754,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906026FP\xf1\x891196', b'\xf1\x8704L906026KB\xf1\x894071', + b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906264A \xf1\x890002', ], @@ -753,6 +762,7 @@ FW_VERSIONS = { b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870D9300011T \xf1\x894801', b'\xf1\x870D9300012 \xf1\x894940', + b'\xf1\x870GC300043 \xf1\x892301', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', @@ -764,6 +774,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x873Q0907572B \xf1\x890192', diff --git a/selfdrive/common/modeldata.h b/selfdrive/common/modeldata.h index 7aec15e7be..8d91f7be10 100644 --- a/selfdrive/common/modeldata.h +++ b/selfdrive/common/modeldata.h @@ -1,6 +1,8 @@ #pragma once #include +#include "selfdrive/common/mat.h" +#include "selfdrive/hardware/hw.h" const int TRAJECTORY_SIZE = 33; const int LAT_MPC_N = 16; @@ -8,38 +10,28 @@ const int LON_MPC_N = 32; const float MIN_DRAW_DISTANCE = 10.0; const float MAX_DRAW_DISTANCE = 100.0; -template -const std::array convert_array_to_type(const std::array &src) { - std::array dst = {}; - for (int i=0; i +constexpr std::array build_idxs(float max_val) { + std::array result{}; + for (int i = 0; i < size; ++i) { + result[i] = max_val * ((i / (double)(size - 1)) * (i / (double)(size - 1))); } - return dst; + return result; } -const std::array T_IDXS = { - 0. , 0.00976562, 0.0390625 , 0.08789062, 0.15625 , - 0.24414062, 0.3515625 , 0.47851562, 0.625 , 0.79101562, - 0.9765625 , 1.18164062, 1.40625 , 1.65039062, 1.9140625 , - 2.19726562, 2.5 , 2.82226562, 3.1640625 , 3.52539062, - 3.90625 , 4.30664062, 4.7265625 , 5.16601562, 5.625 , - 6.10351562, 6.6015625 , 7.11914062, 7.65625 , 8.21289062, - 8.7890625 , 9.38476562, 10.}; -const auto T_IDXS_FLOAT = convert_array_to_type(T_IDXS); +constexpr auto T_IDXS = build_idxs(10.0); +constexpr auto T_IDXS_FLOAT = build_idxs(10.0); +constexpr auto X_IDXS = build_idxs(192.0); +constexpr auto X_IDXS_FLOAT = build_idxs(192.0); -const std::array X_IDXS = { - 0. , 0.1875, 0.75 , 1.6875, 3. , 4.6875, - 6.75 , 9.1875, 12. , 15.1875, 18.75 , 22.6875, - 27. , 31.6875, 36.75 , 42.1875, 48. , 54.1875, - 60.75 , 67.6875, 75. , 82.6875, 90.75 , 99.1875, - 108. , 117.1875, 126.75 , 136.6875, 147. , 157.6875, - 168.75 , 180.1875, 192.}; -const auto X_IDXS_FLOAT = convert_array_to_type(X_IDXS); +const int TICI_CAM_WIDTH = 1928; -#ifdef __cplusplus +namespace tici_dm_crop { + const int x_offset = -72; + const int y_offset = -144; + const int width = 954; +}; -#include "selfdrive/common/mat.h" -#include "selfdrive/hardware/hw.h" const mat3 fcam_intrinsic_matrix = Hardware::EON() ? (mat3){{910., 0., 1164.0 / 2, 0., 910., 874.0 / 2, @@ -62,5 +54,3 @@ static inline mat3 get_model_yuv_transform(bool bayer = true) { }}; return bayer ? transform_scale_buffer(transform, db_s) : transform; } - -#endif diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index a405d21609..29dc17be90 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -85,7 +85,7 @@ private: std::unordered_map keys = { {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, {"AthenadPid", PERSISTENT}, - {"BootedOnroad", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, + {"AthenadUploadQueue", PERSISTENT}, {"CalibrationParams", PERSISTENT}, {"CarBatteryCapacity", PERSISTENT}, {"CarParams", CLEAR_ON_MANAGER_START | CLEAR_ON_PANDA_DISCONNECT | CLEAR_ON_IGNITION_ON}, @@ -120,6 +120,7 @@ std::unordered_map keys = { {"IMEI", PERSISTENT}, {"InstallDate", PERSISTENT}, {"IsDriverViewEnabled", CLEAR_ON_MANAGER_START}, + {"IsEngaged", PERSISTENT}, {"IsLdwEnabled", PERSISTENT}, {"IsMetric", PERSISTENT}, {"IsOffroad", CLEAR_ON_MANAGER_START}, @@ -130,6 +131,7 @@ std::unordered_map keys = { {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, + {"LastPeripheralPandaType", PERSISTENT}, {"LastPowerDropDetected", CLEAR_ON_MANAGER_START}, {"LastUpdateException", PERSISTENT}, {"LastUpdateTime", PERSISTENT}, diff --git a/selfdrive/common/util.cc b/selfdrive/common/util.cc index f115b13dc7..534a7f4456 100644 --- a/selfdrive/common/util.cc +++ b/selfdrive/common/util.cc @@ -20,6 +20,8 @@ #include #endif // __linux__ +namespace util { + void set_thread_name(const char* name) { #ifdef __linux__ // pthread_setname_np is dumb (fails instead of truncates) @@ -56,8 +58,6 @@ int set_core_affinity(std::vector cores) { #endif } -namespace util { - std::string read_file(const std::string& fn) { std::ifstream f(fn, std::ios::binary | std::ios::in); if (f.is_open()) { diff --git a/selfdrive/common/util.h b/selfdrive/common/util.h index a47cce68ff..9a6a4d9bdb 100644 --- a/selfdrive/common/util.h +++ b/selfdrive/common/util.h @@ -37,13 +37,12 @@ const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE; const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_FOOT = 3.28084; -void set_thread_name(const char* name); +namespace util { +void set_thread_name(const char* name); int set_realtime_priority(int level); int set_core_affinity(std::vector cores); -namespace util { - // ***** Time helpers ***** struct tm get_time(); bool time_valid(struct tm sys_time); diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h index aa786903c4..f1f7df9baf 100644 --- a/selfdrive/common/version.h +++ b/selfdrive/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.12" +#define COMMA_VERSION "0.8.13" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index d029650aaf..0469d50be2 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -28,6 +28,7 @@ from selfdrive.locationd.calibrationd import Calibration from selfdrive.hardware import HARDWARE, TICI, EON from selfdrive.manager.process_config import managed_processes +SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS LANE_DEPARTURE_THRESHOLD = 0.1 STEER_ANGLE_SATURATION_TIMEOUT = 1.0 / DT_CTRL @@ -37,7 +38,8 @@ REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ NOSENSOR = "NOSENSOR" in os.environ IGNORE_PROCESSES = {"rtshield", "uploader", "deleter", "loggerd", "logmessaged", "tombstoned", - "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad"} | \ + "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad", + "statsd"} | \ {k for k, v in managed_processes.items() if not v.enabled} ACTUATOR_FIELDS = set(car.CarControl.Actuators.schema.fields.keys()) @@ -149,7 +151,7 @@ class Controls: self.v_cruise_kph_last = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 - self.can_error_counter = 0 + self.can_rcv_error_counter = 0 self.last_blinker_frame = 0 self.saturated_count = 0 self.distance_traveled = 0 @@ -158,6 +160,7 @@ class Controls: self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = False self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0} + self.last_actuators = car.CarControl.Actuators.new_message() # TODO: no longer necessary, aside from process replay self.sm['liveParameters'].valid = True @@ -221,7 +224,7 @@ class Controls: # self.events.add(EventName.highCpuUsage) # Alert if fan isn't spinning for 5 seconds - if self.sm['peripheralState'].pandaType in [PandaType.uno, PandaType.dos]: + if self.sm['peripheralState'].pandaType in (PandaType.uno, PandaType.dos): if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: self.events.add(EventName.fanMalfunction) @@ -247,11 +250,11 @@ class Controls: self.events.add(EventName.preLaneChangeLeft) else: self.events.add(EventName.preLaneChangeRight) - elif self.sm['lateralPlan'].laneChangeState in [LaneChangeState.laneChangeStarting, - LaneChangeState.laneChangeFinishing]: + elif self.sm['lateralPlan'].laneChangeState in (LaneChangeState.laneChangeStarting, + LaneChangeState.laneChangeFinishing): self.events.add(EventName.laneChange) - if self.can_rcv_error or not CS.canValid: + if not CS.canValid: self.events.add(EventName.canError) for i, pandaState in enumerate(self.sm['pandaStates']): @@ -271,7 +274,7 @@ class Controls: self.events.add(EventName.radarFault) elif not self.sm.valid["pandaStates"]: self.events.add(EventName.usbError) - elif not self.sm.all_alive_and_valid(): + elif not self.sm.all_alive_and_valid() or self.can_rcv_error: self.events.add(EventName.commIssue) if not self.logged_comm_issue: invalid = [s for s, valid in self.sm.valid.items() if not valid] @@ -292,15 +295,12 @@ class Controls: self.events.add(EventName.posenetInvalid) if not self.sm['liveLocationKalman'].deviceStable: self.events.add(EventName.deviceFalling) - for pandaState in self.sm['pandaStates']: - if log.PandaState.FaultType.relayMalfunction in pandaState.faults: - self.events.add(EventName.relayMalfunction) if not REPLAY: # Check for mismatch between openpilot and car's PCM cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0 - if self.cruise_mismatch_counter > int(1. / DT_CTRL): + if self.cruise_mismatch_counter > int(3. / DT_CTRL): self.events.add(EventName.cruiseMismatch) # Check for FCW @@ -319,7 +319,7 @@ class Controls: except UnicodeDecodeError: pass - for err in ["ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED"]: + for err in ("ERROR_CRC", "ERROR_ECC", "ERROR_STREAM_UNDERFLOW", "APPLY FAILED"): for m in messages: if err not in m: continue @@ -344,7 +344,7 @@ class Controls: self.events.add(EventName.localizerMalfunction) # Check if all manager processes are running - not_running = set(p.name for p in self.sm['managerState'].processes if not p.running) + not_running = {p.name for p in self.sm['managerState'].processes if not p.running} if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES): self.events.add(EventName.processNotRunning) @@ -367,16 +367,17 @@ class Controls: self.sm.update(0) - all_valid = CS.canValid and self.sm.all_alive_and_valid() - if not self.initialized and (all_valid or self.sm.frame * DT_CTRL > 3.5 or SIMULATION): - if not self.read_only: - self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) - self.initialized = True - Params().put_bool("ControlsReady", True) + if not self.initialized: + all_valid = CS.canValid and self.sm.all_alive_and_valid() + if all_valid or self.sm.frame * DT_CTRL > 3.5 or SIMULATION: + if not self.read_only: + self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) + self.initialized = True + Params().put_bool("ControlsReady", True) # Check for CAN timeout if not can_strs: - self.can_error_counter += 1 + self.can_rcv_error_counter += 1 self.can_rcv_error = True else: self.can_rcv_error = False @@ -405,7 +406,7 @@ class Controls: # if stock cruise is completely disabled, then we can use our own set speed logic if not self.CP.pcmCruise: self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.buttonEvents, self.button_timers, self.enabled, self.is_metric) - elif self.CP.pcmCruise and CS.cruiseState.enabled: + elif CS.cruiseState.enabled: self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH # decrement the soft disable timer at every step, as it's reset on @@ -430,7 +431,7 @@ class Controls: if self.state == State.enabled: if self.events.any(ET.SOFT_DISABLE): self.state = State.softDisabling - self.soft_disable_timer = int(3 / DT_CTRL) + self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL) self.current_alert_types.append(ET.SOFT_DISABLE) # SOFT DISABLING @@ -509,7 +510,7 @@ class Controls: lat_plan.psis, lat_plan.curvatures, lat_plan.curvatureRates) - actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(lat_active, CS, self.CP, self.VM, params, + actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(lat_active, CS, self.CP, self.VM, params, self.last_actuators, desired_curvature, desired_curvature_rate) else: lac_log = log.ControlsState.LateralDebugState.new_message() @@ -538,11 +539,12 @@ class Controls: if (lac_log.saturated and not CS.steeringPressed) or \ (self.saturated_count > STEER_ANGLE_SATURATION_TIMEOUT): - if len(lat_plan.dPathPoints): + dpath_points = lat_plan.dPathPoints + if len(dpath_points): # Check if we deviated from the path # TODO use desired vs actual curvature - left_deviation = actuators.steer > 0 and lat_plan.dPathPoints[0] < -0.20 - right_deviation = actuators.steer < 0 and lat_plan.dPathPoints[0] > 0.20 + left_deviation = actuators.steer > 0 and dpath_points[0] < -0.20 + right_deviation = actuators.steer < 0 and dpath_points[0] > 0.20 if left_deviation or right_deviation: self.events.add(EventName.steerSaturated) @@ -577,71 +579,81 @@ class Controls: CC.active = self.active CC.actuators = actuators - if len(self.sm['liveLocationKalman'].orientationNED.value) > 2: - CC.roll = self.sm['liveLocationKalman'].orientationNED.value[0] - CC.pitch = self.sm['liveLocationKalman'].orientationNED.value[1] + orientation_value = self.sm['liveLocationKalman'].orientationNED.value + if len(orientation_value) > 2: + CC.roll = orientation_value[0] + CC.pitch = orientation_value[1] CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True - CC.hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS) - CC.hudControl.speedVisible = self.enabled - CC.hudControl.lanesVisible = self.enabled - CC.hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead + hudControl = CC.hudControl + hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS) + hudControl.speedVisible = self.enabled + hudControl.lanesVisible = self.enabled + hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead - CC.hudControl.rightLaneVisible = True - CC.hudControl.leftLaneVisible = True + hudControl.rightLaneVisible = True + hudControl.leftLaneVisible = True recent_blinker = (self.sm.frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown ldw_allowed = self.is_ldw_enabled and CS.vEgo > LDW_MIN_SPEED and not recent_blinker \ and not self.active and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED - meta = self.sm['modelV2'].meta - if len(meta.desirePrediction) and ldw_allowed: + model_v2 = self.sm['modelV2'] + desire_prediction = model_v2.meta.desirePrediction + if len(desire_prediction) and ldw_allowed: right_lane_visible = self.sm['lateralPlan'].rProb > 0.5 left_lane_visible = self.sm['lateralPlan'].lProb > 0.5 - l_lane_change_prob = meta.desirePrediction[Desire.laneChangeLeft - 1] - r_lane_change_prob = meta.desirePrediction[Desire.laneChangeRight - 1] - l_lane_close = left_lane_visible and (self.sm['modelV2'].laneLines[1].y[0] > -(1.08 + CAMERA_OFFSET)) - r_lane_close = right_lane_visible and (self.sm['modelV2'].laneLines[2].y[0] < (1.08 - CAMERA_OFFSET)) + l_lane_change_prob = desire_prediction[Desire.laneChangeLeft - 1] + r_lane_change_prob = desire_prediction[Desire.laneChangeRight - 1] - CC.hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close) - CC.hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) + lane_lines = model_v2.laneLines + l_lane_close = left_lane_visible and (lane_lines[1].y[0] > -(1.08 + CAMERA_OFFSET)) + r_lane_close = right_lane_visible and (lane_lines[2].y[0] < (1.08 - CAMERA_OFFSET)) - if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart: + hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close) + hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) + + if hudControl.rightLaneDepart or hudControl.leftLaneDepart: self.events.add(EventName.ldw) clear_event = ET.WARNING if ET.WARNING not in self.current_alert_types else None - alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric]) - self.AM.add_many(self.sm.frame, alerts, self.enabled) - self.AM.process_alerts(self.sm.frame, clear_event) - CC.hudControl.visualAlert = self.AM.visual_alert + alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric, self.soft_disable_timer]) + self.AM.add_many(self.sm.frame, alerts) + current_alert = self.AM.process_alerts(self.sm.frame, clear_event) + if current_alert: + hudControl.visualAlert = current_alert.visual_alert if not self.read_only and self.initialized: # send car controls over can - can_sends = self.CI.apply(CC) + self.last_actuators, can_sends = self.CI.apply(CC) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) + CC.actuatorsOutput = self.last_actuators force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) # Curvature & Steering angle params = self.sm['liveParameters'] - steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetAverageDeg) - curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo) + + steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetDeg) + curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, params.roll) # controlsState dat = messaging.new_message('controlsState') dat.valid = CS.canValid controlsState = dat.controlsState - controlsState.alertText1 = self.AM.alert_text_1 - controlsState.alertText2 = self.AM.alert_text_2 - controlsState.alertSize = self.AM.alert_size - controlsState.alertStatus = self.AM.alert_status - controlsState.alertBlinkingRate = self.AM.alert_rate - controlsState.alertType = self.AM.alert_type - controlsState.alertSound = self.AM.audible_alert + if current_alert: + controlsState.alertText1 = current_alert.alert_text_1 + controlsState.alertText2 = current_alert.alert_text_2 + controlsState.alertSize = current_alert.alert_size + controlsState.alertStatus = current_alert.alert_status + controlsState.alertBlinkingRate = current_alert.alert_rate + controlsState.alertType = current_alert.alert_type + controlsState.alertSound = current_alert.audible_alert + controlsState.canMonoTimes = list(CS.canMonoTimes) controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] controlsState.lateralPlanMonoTime = self.sm.logMonoTime['lateralPlan'] @@ -659,18 +671,20 @@ class Controls: controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.can_error_counter + controlsState.canErrorCounter = self.can_rcv_error_counter + lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: controlsState.lateralControlState.debugState = lac_log elif self.CP.steerControlType == car.CarParams.SteerControlType.angle: controlsState.lateralControlState.angleState = lac_log - elif self.CP.lateralTuning.which() == 'pid': + elif lat_tuning == 'pid': controlsState.lateralControlState.pidState = lac_log - elif self.CP.lateralTuning.which() == 'lqr': + elif lat_tuning == 'lqr': controlsState.lateralControlState.lqrState = lac_log - elif self.CP.lateralTuning.which() == 'indi': + elif lat_tuning == 'indi': controlsState.lateralControlState.indiState = lac_log + self.pm.send('controlsState', dat) # carState diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index 22d7f3eac4..bf93b5f47e 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -5,10 +5,8 @@ from collections import defaultdict from dataclasses import dataclass from typing import List, Dict, Optional -from cereal import car, log from common.basedir import BASEDIR from common.params import Params -from common.realtime import DT_CTRL from selfdrive.controls.lib.events import Alert @@ -33,55 +31,34 @@ class AlertEntry: start_frame: int = -1 end_frame: int = -1 + def active(self, frame: int) -> bool: + return frame <= self.end_frame class AlertManager: - def __init__(self): - self.reset() - self.activealerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) - - def reset(self) -> None: - self.alert: Optional[Alert] = None - self.alert_type: str = "" - self.alert_text_1: str = "" - self.alert_text_2: str = "" - self.alert_status = log.ControlsState.AlertStatus.normal - self.alert_size = log.ControlsState.AlertSize.none - self.visual_alert = car.CarControl.HUDControl.VisualAlert.none - self.audible_alert = car.CarControl.HUDControl.AudibleAlert.none - self.alert_rate: float = 0. + self.alerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) - def add_many(self, frame: int, alerts: List[Alert], enabled: bool = True) -> None: + def add_many(self, frame: int, alerts: List[Alert]) -> None: for alert in alerts: - self.activealerts[alert.alert_type].alert = alert - self.activealerts[alert.alert_type].start_frame = frame - self.activealerts[alert.alert_type].end_frame = frame + int(alert.duration / DT_CTRL) - - def process_alerts(self, frame: int, clear_event_type=None) -> None: + key = alert.alert_type + self.alerts[key].alert = alert + if not self.alerts[key].active(frame): + self.alerts[key].start_frame = frame + min_end_frame = self.alerts[key].start_frame + alert.duration + self.alerts[key].end_frame = max(frame + 1, min_end_frame) + + def process_alerts(self, frame: int, clear_event_type=None) -> Optional[Alert]: current_alert = AlertEntry() - for k, v in self.activealerts.items(): - if v.alert is None: + for v in self.alerts.values(): + if not v.alert: continue - if v.alert.event_type == clear_event_type: - self.activealerts[k].end_frame = -1 + if clear_event_type and v.alert.event_type == clear_event_type: + v.end_frame = -1 # sort by priority first and then by start_frame - active = self.activealerts[k].end_frame > frame greater = current_alert.alert is None or (v.alert.priority, v.start_frame) > (current_alert.alert.priority, current_alert.start_frame) - if active and greater: + if v.active(frame) and greater: current_alert = v - # clear current alert - self.reset() - - self.alert = current_alert.alert - if self.alert is not None: - self.alert_type = self.alert.alert_type - self.audible_alert = self.alert.audible_alert - self.visual_alert = self.alert.visual_alert - self.alert_text_1 = self.alert.alert_text_1 - self.alert_text_2 = self.alert.alert_text_2 - self.alert_status = self.alert.alert_status - self.alert_size = self.alert.alert_size - self.alert_rate = self.alert.alert_rate + return current_alert.alert diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index e578bd677f..14be3d5ed8 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -5,10 +5,11 @@ from common.realtime import DT_MDL from selfdrive.config import Conversions as CV from selfdrive.modeld.constants import T_IDXS -# kph -V_CRUISE_MAX = 145 -V_CRUISE_MIN = 8 -V_CRUISE_ENABLE_MIN = 40 +# WARNING: this value was determined based on the model's training distribution, +# model predictions above this speed can be unpredictable +V_CRUISE_MAX = 145 # kph +V_CRUISE_MIN = 8 # kph +V_CRUISE_ENABLE_MIN = 40 # kph LAT_MPC_N = 16 LON_MPC_N = 32 @@ -118,6 +119,6 @@ def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): -max_curvature_rate, max_curvature_rate) safe_desired_curvature = clip(desired_curvature, - current_curvature - max_curvature_rate/DT_MDL, - current_curvature + max_curvature_rate/DT_MDL) + current_curvature - max_curvature_rate * DT_MDL, + current_curvature + max_curvature_rate * DT_MDL) return safe_desired_curvature, safe_desired_curvature_rate diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index d858dbfcbc..cd139c062a 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import Dict, Union, Callable, Any +from typing import Dict, Union, Callable, List, Optional from cereal import log, car import cereal.messaging as messaging @@ -42,33 +42,30 @@ EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} class Events: def __init__(self): - self.events = [] - self.static_events = [] + self.events: List[int] = [] + self.static_events: List[int] = [] self.events_prev = dict.fromkeys(EVENTS.keys(), 0) @property - def names(self): + def names(self) -> List[int]: return self.events - def __len__(self): + def __len__(self) -> int: return len(self.events) - def add(self, event_name, static=False): + def add(self, event_name: int, static: bool=False) -> None: if static: self.static_events.append(event_name) self.events.append(event_name) - def clear(self): + def clear(self) -> None: self.events_prev = {k: (v + 1 if k in self.events else 0) for k, v in self.events_prev.items()} self.events = self.static_events.copy() - def any(self, event_type): - for e in self.events: - if event_type in EVENTS.get(e, {}).keys(): - return True - return False + def any(self, event_type: str) -> bool: + return any(event_type in EVENTS.get(e, {}) for e in self.events) - def create_alerts(self, event_types, callback_args=None): + def create_alerts(self, event_types: List[str], callback_args=None): if callback_args is None: callback_args = [] @@ -123,13 +120,13 @@ class Alert: self.visual_alert = visual_alert self.audible_alert = audible_alert - self.duration = duration + self.duration = int(duration / DT_CTRL) self.alert_rate = alert_rate self.creation_delay = creation_delay self.alert_type = "" - self.event_type = None + self.event_type: Optional[str] = None def __str__(self) -> str: return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}" @@ -139,22 +136,29 @@ class Alert: class NoEntryAlert(Alert): - def __init__(self, alert_text_2, visual_alert=VisualAlert.none): + def __init__(self, alert_text_2: str, visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none): super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal, AlertSize.mid, Priority.LOW, visual_alert, AudibleAlert.refuse, 3.) class SoftDisableAlert(Alert): - def __init__(self, alert_text_2): + def __init__(self, alert_text_2: str): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.userPrompt, AlertSize.full, Priority.MID, VisualAlert.steerRequired, AudibleAlert.warningSoft, 2.), +# less harsh version of SoftDisable, where the condition is user-triggered +class UserSoftDisableAlert(SoftDisableAlert): + def __init__(self, alert_text_2: str): + super().__init__(alert_text_2), + self.alert_text_1 = "openpilot will disengage" + + class ImmediateDisableAlert(Alert): - def __init__(self, alert_text_2): + def __init__(self, alert_text_2: str): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.critical, AlertSize.full, Priority.HIGHEST, VisualAlert.steerRequired, @@ -191,11 +195,31 @@ def get_display_speed(speed_ms: float, metric: bool) -> str: # ********** alert callback functions ********** -def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: + +AlertCallbackType = Callable[[car.CarParams, messaging.SubMaster, bool, int], Alert] + + +def soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return SoftDisableAlert(alert_text_2) + return func + + +def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return UserSoftDisableAlert(alert_text_2) + return func + + +def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") -def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", "", @@ -203,7 +227,7 @@ def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: Priority.MID, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4) -def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( "Calibration in Progress: %d%%" % sm['liveCalibration'].calPerc, f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}", @@ -211,8 +235,8 @@ def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, met Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) -def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: - gps_integrated = sm['peripheralState'].pandaType in [log.PandaState.PandaType.uno, log.PandaState.PandaType.dos] +def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + gps_integrated = sm['peripheralState'].pandaType in (log.PandaState.PandaType.uno, log.PandaState.PandaType.dos) return Alert( "Poor GPS reception", "If sky is visible, contact support" if gps_integrated else "Check GPS antenna placement", @@ -220,25 +244,28 @@ def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Al Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) -def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: text = "Cruise Mode Disabled" if CP.carName == "honda": text = "Main Switch Off" return NoEntryAlert(text) -def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: axes = sm['testJoystick'].axes gb, steer = list(axes)[:2] if len(axes) else (0., 0.) vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%" return NormalPermanentAlert("Joystick Mode", vals) -EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = { + +EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { # ********** events with no alerts ********** EventName.stockFcw: {}, + EventName.lkasDisabled: {}, + # ********** events only containing alerts displayed in all states ********** EventName.joystickDebug: { @@ -247,7 +274,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.controlsInitializing: { - ET.NO_ENTRY: NoEntryAlert("Controls Initializing"), + ET.NO_ENTRY: NoEntryAlert("System Initializing"), }, EventName.startup: { @@ -281,7 +308,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.invalidLkasSetting: { - ET.PERMANENT: NormalPermanentAlert("Stock LKAS is turned on", + ET.PERMANENT: NormalPermanentAlert("Stock LKAS is on", "Turn off stock LKAS to engage"), }, @@ -293,8 +320,8 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # detects the use of a community feature it switches to dashcam mode # until these features are allowed using a toggle in settings. EventName.communityFeatureDisallowed: { - ET.PERMANENT: NormalPermanentAlert("openpilot Not Available", - "Enable Community Features in Settings to Engage"), + ET.PERMANENT: NormalPermanentAlert("openpilot Unavailable", + "Enable Community Features in Settings"), }, # openpilot doesn't recognize the car. This switches openpilot into a @@ -325,9 +352,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.ldw: { ET.PERMANENT: Alert( - "TAKE CONTROL", "Lane Departure Detected", - AlertStatus.userPrompt, AlertSize.mid, + "", + AlertStatus.userPrompt, AlertSize.small, Priority.LOW, VisualAlert.ldw, AudibleAlert.prompt, 3.), }, @@ -335,7 +362,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.gasPressed: { ET.PRE_ENABLE: Alert( - "openpilot will not brake while gas pressed", + "Release Gas Pedal to Engage", "", AlertStatus.normal, AlertSize.small, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.), @@ -351,7 +378,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub EventName.vehicleModelInvalid: { ET.NO_ENTRY: NoEntryAlert("Vehicle Parameter Identification Failed"), - ET.SOFT_DISABLE: SoftDisableAlert("Vehicle Parameter Identification Failed"), + ET.SOFT_DISABLE: soft_disable_alert("Vehicle Parameter Identification Failed"), }, EventName.steerTempUnavailableSilent: { @@ -364,7 +391,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.preDriverDistracted: { ET.WARNING: Alert( - "KEEP EYES ON ROAD: Driver Distracted", + "Pay Attention", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, .1), @@ -372,7 +399,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.promptDriverDistracted: { ET.WARNING: Alert( - "KEEP EYES ON ROAD", + "Pay Attention", "Driver Distracted", AlertStatus.userPrompt, AlertSize.mid, Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), @@ -388,7 +415,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.preDriverUnresponsive: { ET.WARNING: Alert( - "TOUCH STEERING WHEEL: No Face Detected", + "Touch Steering Wheel: No Face Detected", "", AlertStatus.normal, AlertSize.small, Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .1, alert_rate=0.75), @@ -396,7 +423,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.promptDriverUnresponsive: { ET.WARNING: Alert( - "TOUCH STEERING WHEEL", + "Touch Steering Wheel", "Driver Unresponsive", AlertStatus.userPrompt, AlertSize.mid, Priority.MID, VisualAlert.steerRequired, AudibleAlert.promptDistracted, .1), @@ -421,7 +448,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.resumeRequired: { ET.WARNING: Alert( "STOPPED", - "Press Resume to Move", + "Press Resume to Go", AlertStatus.userPrompt, AlertSize.mid, Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, @@ -464,7 +491,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.steerSaturated: { ET.WARNING: Alert( - "TAKE CONTROL", + "Take Control", "Turn Exceeds Steering Limit", AlertStatus.userPrompt, AlertSize.mid, Priority.LOW, VisualAlert.steerRequired, AudibleAlert.promptRepeat, 1.), @@ -517,12 +544,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.parkBrake: { ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), - ET.NO_ENTRY: NoEntryAlert("Park Brake Engaged"), + ET.NO_ENTRY: NoEntryAlert("Parking Brake Engaged"), }, EventName.pedalPressed: { ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), - ET.NO_ENTRY: NoEntryAlert("Pedal Pressed During Attempt", + ET.NO_ENTRY: NoEntryAlert("Pedal Pressed", visual_alert=VisualAlert.brakePressed), }, @@ -533,11 +560,11 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.wrongCruiseMode: { ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), - ET.NO_ENTRY: NoEntryAlert("Enable Adaptive Cruise"), + ET.NO_ENTRY: NoEntryAlert("Adaptive Cruise Disabled"), }, EventName.steerTempUnavailable: { - ET.SOFT_DISABLE: SoftDisableAlert("Steering Temporarily Unavailable"), + ET.SOFT_DISABLE: soft_disable_alert("Steering Temporarily Unavailable"), ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"), }, @@ -574,12 +601,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.overheat: { ET.PERMANENT: NormalPermanentAlert("System Overheated"), - ET.SOFT_DISABLE: SoftDisableAlert("System Overheated"), + ET.SOFT_DISABLE: soft_disable_alert("System Overheated"), ET.NO_ENTRY: NoEntryAlert("System Overheated"), }, EventName.wrongGear: { - ET.SOFT_DISABLE: SoftDisableAlert("Gear not D"), + ET.SOFT_DISABLE: user_soft_disable_alert("Gear not D"), ET.NO_ENTRY: NoEntryAlert("Gear not D"), }, @@ -590,33 +617,33 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # See https://comma.ai/setup for more information EventName.calibrationInvalid: { ET.PERMANENT: NormalPermanentAlert("Calibration Invalid", "Remount Device and Recalibrate"), - ET.SOFT_DISABLE: SoftDisableAlert("Calibration Invalid: Remount Device & Recalibrate"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"), ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"), }, EventName.calibrationIncomplete: { ET.PERMANENT: calibration_incomplete_alert, - ET.SOFT_DISABLE: SoftDisableAlert("Calibration in Progress"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration in Progress"), ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"), }, EventName.doorOpen: { - ET.SOFT_DISABLE: SoftDisableAlert("Door Open"), + ET.SOFT_DISABLE: user_soft_disable_alert("Door Open"), ET.NO_ENTRY: NoEntryAlert("Door Open"), }, EventName.seatbeltNotLatched: { - ET.SOFT_DISABLE: SoftDisableAlert("Seatbelt Unlatched"), + ET.SOFT_DISABLE: user_soft_disable_alert("Seatbelt Unlatched"), ET.NO_ENTRY: NoEntryAlert("Seatbelt Unlatched"), }, EventName.espDisabled: { - ET.SOFT_DISABLE: SoftDisableAlert("ESP Off"), + ET.SOFT_DISABLE: soft_disable_alert("ESP Off"), ET.NO_ENTRY: NoEntryAlert("ESP Off"), }, EventName.lowBattery: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Battery"), + ET.SOFT_DISABLE: soft_disable_alert("Low Battery"), ET.NO_ENTRY: NoEntryAlert("Low Battery"), }, @@ -625,7 +652,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is thrown. This can mean a service crashed, did not broadcast a message for # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { - ET.SOFT_DISABLE: SoftDisableAlert("Communication Issue between Processes"), + ET.SOFT_DISABLE: soft_disable_alert("Communication Issue between Processes"), ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes"), }, @@ -635,7 +662,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.radarFault: { - ET.SOFT_DISABLE: SoftDisableAlert("Radar Error: Restart the Car"), + ET.SOFT_DISABLE: soft_disable_alert("Radar Error: Restart the Car"), ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"), }, @@ -643,7 +670,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is not processing frames fast enough they have to be dropped. This alert is # thrown when over 20% of frames are dropped. EventName.modeldLagging: { - ET.SOFT_DISABLE: SoftDisableAlert("Driving model lagging"), + ET.SOFT_DISABLE: soft_disable_alert("Driving model lagging"), ET.NO_ENTRY: NoEntryAlert("Driving model lagging"), }, @@ -653,25 +680,25 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # usually means the model has trouble understanding the scene. This is used # as a heuristic to warn the driver. EventName.posenetInvalid: { - ET.SOFT_DISABLE: SoftDisableAlert("Model Output Uncertain"), + ET.SOFT_DISABLE: soft_disable_alert("Model Output Uncertain"), ET.NO_ENTRY: NoEntryAlert("Model Output Uncertain"), }, # When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we # alert the driver the device might have fallen from the windshield. EventName.deviceFalling: { - ET.SOFT_DISABLE: SoftDisableAlert("Device Fell Off Mount"), + ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"), ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"), }, EventName.lowMemory: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Memory: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("Low Memory", "Reboot your Device"), ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"), }, EventName.highCpuUsage: { - #ET.SOFT_DISABLE: SoftDisableAlert("System Malfunction: Reboot Your Device"), + #ET.SOFT_DISABLE: soft_disable_alert("System Malfunction: Reboot Your Device"), #ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"), ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, @@ -707,7 +734,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # Sometimes the USB stack on the device can get into a bad state # causing the connection to the panda to be lost EventName.usbError: { - ET.SOFT_DISABLE: SoftDisableAlert("USB Error: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device", ""), ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"), }, @@ -789,7 +816,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), }, - # When the car is driving faster than most cars in the training data the model outputs can be unpredictable + # When the car is driving faster than most cars in the training data, the model outputs can be unpredictable. EventName.speedTooHigh: { ET.WARNING: Alert( "Speed Too High", diff --git a/selfdrive/controls/lib/lane_planner.py b/selfdrive/controls/lib/lane_planner.py index 88a1f6a672..160808cff0 100644 --- a/selfdrive/controls/lib/lane_planner.py +++ b/selfdrive/controls/lib/lane_planner.py @@ -9,12 +9,14 @@ from selfdrive.swaglog import cloudlog TRAJECTORY_SIZE = 33 # camera offset is meters from center car to camera +# model path is in the frame of EON's camera. TICI is 0.1 m away, +# however the average measured path difference is 0.04 m if EON: - CAMERA_OFFSET = 0.06 + CAMERA_OFFSET = -0.06 PATH_OFFSET = 0.0 elif TICI: - CAMERA_OFFSET = -0.04 - PATH_OFFSET = -0.04 + CAMERA_OFFSET = 0.04 + PATH_OFFSET = 0.04 else: CAMERA_OFFSET = 0.0 PATH_OFFSET = 0.0 @@ -44,30 +46,31 @@ class LanePlanner: self.path_offset = -PATH_OFFSET if wide_camera else PATH_OFFSET def parse_model(self, md): - if len(md.laneLines) == 4 and len(md.laneLines[0].t) == TRAJECTORY_SIZE: - self.ll_t = (np.array(md.laneLines[1].t) + np.array(md.laneLines[2].t))/2 + lane_lines = md.laneLines + if len(lane_lines) == 4 and len(lane_lines[0].t) == TRAJECTORY_SIZE: + self.ll_t = (np.array(lane_lines[1].t) + np.array(lane_lines[2].t))/2 # left and right ll x is the same - self.ll_x = md.laneLines[1].x - # only offset left and right lane lines; offsetting path does not make sense - self.lll_y = np.array(md.laneLines[1].y) - self.camera_offset - self.rll_y = np.array(md.laneLines[2].y) - self.camera_offset + self.ll_x = lane_lines[1].x + self.lll_y = np.array(lane_lines[1].y) + self.camera_offset + self.rll_y = np.array(lane_lines[2].y) + self.camera_offset self.lll_prob = md.laneLineProbs[1] self.rll_prob = md.laneLineProbs[2] self.lll_std = md.laneLineStds[1] self.rll_std = md.laneLineStds[2] - if len(md.meta.desireState): - self.l_lane_change_prob = md.meta.desireState[log.LateralPlan.Desire.laneChangeLeft] - self.r_lane_change_prob = md.meta.desireState[log.LateralPlan.Desire.laneChangeRight] + desire_state = md.meta.desireState + if len(desire_state): + self.l_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeLeft] + self.r_lane_change_prob = desire_state[log.LateralPlan.Desire.laneChangeRight] def get_d_path(self, v_ego, path_t, path_xyz): # Reduce reliance on lanelines that are too far apart or # will be in a few seconds - path_xyz[:, 1] -= self.path_offset + path_xyz[:, 1] += self.path_offset l_prob, r_prob = self.lll_prob, self.rll_prob width_pts = self.rll_y - self.lll_y prob_mods = [] - for t_check in [0.0, 1.5, 3.0]: + for t_check in (0.0, 1.5, 3.0): width_at_t = interp(t_check * (v_ego + 7), self.ll_x, width_pts) prob_mods.append(interp(width_at_t, [4.0, 5.0], [1.0, 0.0])) mod = min(prob_mods) diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 8fcb9ae7bf..dc184cf58b 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -10,7 +10,7 @@ class LatControlAngle(): def reset(self): pass - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): angle_log = log.ControlsState.LateralAngleState.new_message() if CS.vEgo < 0.3 or not active: @@ -18,11 +18,11 @@ class LatControlAngle(): angle_steers_des = float(CS.steeringAngleDeg) else: angle_log.active = True - angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) angle_steers_des += params.angleOffsetDeg angle_log.saturated = False angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) - angle_log.steeringAngleDesiredDeg = angle_steers_des + angle_log.steeringAngleDesiredDeg = angle_steers_des return 0, float(angle_steers_des), angle_log diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index 50a8e22b3c..75b27ac8c1 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -82,7 +82,7 @@ class LatControlINDI(): return self.sat_count > self.sat_limit - def update(self, active, CS, CP, VM, params, curvature, curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, curvature, curvature_rate): self.speed = CS.vEgo # Update Kalman filter y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]]) @@ -93,11 +93,11 @@ class LatControlINDI(): indi_log.steeringRateDeg = math.degrees(self.x[1]) indi_log.steeringAccelDeg = math.degrees(self.x[2]) - steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo) + steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo, params.roll) steers_des += math.radians(params.angleOffsetDeg) indi_log.steeringAngleDesiredDeg = math.degrees(steers_des) - rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo) + rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo, 0) indi_log.steeringRateDesiredDeg = math.degrees(rate_des) if CS.vEgo < 0.3 or not active: diff --git a/selfdrive/controls/lib/latcontrol_lqr.py b/selfdrive/controls/lib/latcontrol_lqr.py index 16fffac279..5777cde8e8 100644 --- a/selfdrive/controls/lib/latcontrol_lqr.py +++ b/selfdrive/controls/lib/latcontrol_lqr.py @@ -44,7 +44,7 @@ class LatControlLQR(): return self.sat_count > self.sat_limit - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): lqr_log = log.ControlsState.LateralLQRState.new_message() steers_max = get_steer_max(CP, CS.vEgo) @@ -53,7 +53,7 @@ class LatControlLQR(): # Subtract offset. Zero angle should correspond to zero torque steering_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetAverageDeg - desired_angle = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + desired_angle = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) instant_offset = params.angleOffsetDeg - params.angleOffsetAverageDeg desired_angle += instant_offset # Only add offset that originates from vehicle model errors diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index c7730d011c..bcb8ccba56 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -16,15 +16,15 @@ class LatControlPID(): def reset(self): self.pid.reset() - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) - angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) angle_steers_des = angle_steers_des_no_offset + params.angleOffsetDeg - pid_log.steeringAngleDesiredDeg = angle_steers_des + pid_log.steeringAngleDesiredDeg = angle_steers_des pid_log.angleError = angle_steers_des - CS.steeringAngleDeg if CS.vEgo < 0.3 or not active: output_steer = 0.0 diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index 148e4e123d..f402e6e15e 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -48,12 +48,13 @@ lenv.Clean(generated_files, Dir(gen)) lenv.Command(generated_files, ["lat_mpc.py"], - f"cd {Dir('.').abspath} && python lat_mpc.py") + f"cd {Dir('.').abspath} && python3 lat_mpc.py") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CCFLAGS"].append("-Wno-unused") -lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") +if arch != "Darwin": + lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_lat", build_files, LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 0aa2359ae7..1d7684e827 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -138,7 +138,7 @@ class LateralPlanner: else: self.lane_change_state = LaneChangeState.off - if self.lane_change_state in [LaneChangeState.off, LaneChangeState.preLaneChange]: + if self.lane_change_state in (LaneChangeState.off, LaneChangeState.preLaneChange): self.lane_change_timer = 0.0 else: self.lane_change_timer += DT_MDL @@ -148,13 +148,13 @@ class LateralPlanner: self.desire = DESIRES[self.lane_change_direction][self.lane_change_state] # Send keep pulse once per second during LaneChangeStart.preLaneChange - if self.lane_change_state in [LaneChangeState.off, LaneChangeState.laneChangeStarting]: + if self.lane_change_state in (LaneChangeState.off, LaneChangeState.laneChangeStarting): self.keep_pulse_timer = 0.0 elif self.lane_change_state == LaneChangeState.preLaneChange: self.keep_pulse_timer += DT_MDL if self.keep_pulse_timer > 1.0: self.keep_pulse_timer = 0.0 - elif self.desire in [log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight]: + elif self.desire in (log.LateralPlan.Desire.keepLeft, log.LateralPlan.Desire.keepRight): self.desire = log.LateralPlan.Desire.none # Turn off lanes during lane change @@ -204,20 +204,22 @@ class LateralPlanner: plan_solution_valid = self.solution_invalid_cnt < 2 plan_send = messaging.new_message('lateralPlan') plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'modelV2']) - plan_send.lateralPlan.laneWidth = float(self.LP.lane_width) - plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts] - plan_send.lateralPlan.psis = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]] - plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]] - plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] - plan_send.lateralPlan.lProb = float(self.LP.lll_prob) - plan_send.lateralPlan.rProb = float(self.LP.rll_prob) - plan_send.lateralPlan.dProb = float(self.LP.d_prob) - - plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid) - - plan_send.lateralPlan.desire = self.desire - plan_send.lateralPlan.useLaneLines = self.use_lanelines - plan_send.lateralPlan.laneChangeState = self.lane_change_state - plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction + + lateralPlan = plan_send.lateralPlan + lateralPlan.laneWidth = float(self.LP.lane_width) + lateralPlan.dPathPoints = self.y_pts.tolist() + lateralPlan.psis = self.lat_mpc.x_sol[0:CONTROL_N, 2].tolist() + lateralPlan.curvatures = self.lat_mpc.x_sol[0:CONTROL_N, 3].tolist() + lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] + lateralPlan.lProb = float(self.LP.lll_prob) + lateralPlan.rProb = float(self.LP.rll_prob) + lateralPlan.dProb = float(self.LP.d_prob) + + lateralPlan.mpcSolutionValid = bool(plan_solution_valid) + + lateralPlan.desire = self.desire + lateralPlan.useLaneLines = self.use_lanelines + lateralPlan.laneChangeState = self.lane_change_state + lateralPlan.laneChangeDirection = self.lane_change_direction pm.send('lateralPlan', plan_send) diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index beacc518d0..b14ffdc6c8 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -7,21 +7,17 @@ from selfdrive.modeld.constants import T_IDXS LongCtrlState = car.CarControl.Actuators.LongControlState -STOPPING_TARGET_SPEED_OFFSET = 0.01 - # As per ISO 15622:2018 for all speeds ACCEL_MIN_ISO = -3.5 # m/s^2 ACCEL_MAX_ISO = 2.0 # m/s^2 -def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, v_pid, - output_accel, brake_pressed, cruise_standstill, min_speed_can): +def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, + brake_pressed, cruise_standstill): """Update longitudinal control state machine""" - stopping_target_speed = min_speed_can + STOPPING_TARGET_SPEED_OFFSET stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ (v_ego < CP.vEgoStopping and - ((v_pid < stopping_target_speed and v_target_future < stopping_target_speed) or - brake_pressed)) + (v_target_future < CP.vEgoStopping or brake_pressed)) starting_condition = v_target_future > CP.vEgoStarting and not cruise_standstill @@ -30,8 +26,7 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_fut else: if long_control_state == LongCtrlState.off: - if active: - long_control_state = LongCtrlState.pid + long_control_state = LongCtrlState.pid elif long_control_state == LongCtrlState.pid: if stopping_condition: @@ -39,12 +34,6 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_fut elif long_control_state == LongCtrlState.stopping: if starting_condition: - long_control_state = LongCtrlState.starting - - elif long_control_state == LongCtrlState.starting: - if stopping_condition: - long_control_state = LongCtrlState.stopping - elif output_accel >= CP.startAccel: long_control_state = LongCtrlState.pid return long_control_state @@ -69,16 +58,19 @@ class LongControl(): """Update longitudinal control. This updates the state machine and runs a PID loop""" # Interp control trajectory # TODO estimate car specific lag, use .15s for now - if len(long_plan.speeds) == CONTROL_N: - v_target_lower = interp(CP.longitudinalActuatorDelayLowerBound, T_IDXS[:CONTROL_N], long_plan.speeds) - a_target_lower = 2 * (v_target_lower - long_plan.speeds[0])/CP.longitudinalActuatorDelayLowerBound - long_plan.accels[0] + speeds = long_plan.speeds + if len(speeds) == CONTROL_N: + v_target_lower = interp(CP.longitudinalActuatorDelayLowerBound, T_IDXS[:CONTROL_N], speeds) + a_target_lower = 2 * (v_target_lower - speeds[0])/CP.longitudinalActuatorDelayLowerBound - long_plan.accels[0] - v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], long_plan.speeds) - a_target_upper = 2 * (v_target_upper - long_plan.speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] + v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], speeds) + a_target_upper = 2 * (v_target_upper - speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] a_target = min(a_target_lower, a_target_upper) - v_target_future = long_plan.speeds[-1] + v_target = speeds[0] + v_target_future = speeds[-1] else: + v_target = 0.0 v_target_future = 0.0 a_target = 0.0 @@ -91,8 +83,8 @@ class LongControl(): # Update state machine output_accel = self.last_output_accel self.long_control_state = long_control_state_trans(CP, active, self.long_control_state, CS.vEgo, - v_target_future, self.v_pid, output_accel, - CS.brakePressed, CS.cruiseState.standstill, CP.minSpeedCan) + v_target_future, CS.brakePressed, + CS.cruiseState.standstill) if self.long_control_state == LongCtrlState.off or CS.gasPressed: self.reset(CS.vEgo) @@ -100,7 +92,7 @@ class LongControl(): # tracking objects and driving elif self.long_control_state == LongCtrlState.pid: - self.v_pid = long_plan.speeds[0] + self.v_pid = v_target # Toyota starts braking more when it thinks you want to stop # Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration @@ -119,13 +111,6 @@ class LongControl(): if not CS.standstill or output_accel > CP.stopAccel: output_accel -= CP.stoppingDecelRate * DT_CTRL output_accel = clip(output_accel, accel_limits[0], accel_limits[1]) - - self.reset(CS.vEgo) - - # Intention is to move again, release brake fast before handing control to PID - elif self.long_control_state == LongCtrlState.starting: - if output_accel < CP.startAccel: - output_accel += CP.startingAccelRate * DT_CTRL self.reset(CS.vEgo) self.last_output_accel = output_accel diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 4abe90f8f8..4c43985d1f 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -58,12 +58,13 @@ lenv.Clean(generated_files, Dir(gen)) lenv.Command(generated_files, ["long_mpc.py"], - f"cd {Dir('.').abspath} && python long_mpc.py") + f"cd {Dir('.').abspath} && python3 long_mpc.py") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CCFLAGS"].append("-Wno-unused") -lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") +if arch != "Darwin": + lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_long", build_files, LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index e75e84da13..c07110eefa 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -40,7 +40,7 @@ CRASH_DISTANCE = .5 LIMIT_COST = 1e6 -# Less timestamps doesn't hurt performance and leads to +# Fewer timestamps don't hurt performance and lead to # much better convergence of the MPC with low iterations N = 12 MAX_T = 10.0 @@ -84,9 +84,9 @@ def gen_long_model(): model.xdot = vertcat(x_ego_dot, v_ego_dot, a_ego_dot) # live parameters - x_obstacle = SX.sym('x_obstacle') a_min = SX.sym('a_min') a_max = SX.sym('a_max') + x_obstacle = SX.sym('x_obstacle') prev_a = SX.sym('prev_a') model.p = vertcat(a_min, a_max, x_obstacle, prev_a) @@ -143,8 +143,8 @@ def gen_long_mpc_solver(): # Constraints on speed, acceleration and desired distance to # the obstacle, which is treated as a slack constraint so it - # behaves like an assymetrical cost. - constraints = vertcat((v_ego), + # behaves like an asymmetrical cost. + constraints = vertcat(v_ego, (a_ego - a_min), (a_max - a_ego), ((x_obstacle - x_ego) - (3/4) * (desired_dist_comfort)) / (v_ego + 10.)) @@ -169,7 +169,7 @@ def gen_long_mpc_solver(): ocp.constraints.idxsh = np.arange(CONSTR_DIM) # The HPIPM solver can give decent solutions even when it is stopped early - # Which is critical for our purpose where the compute time is strictly bounded + # Which is critical for our purpose where compute time is strictly bounded # We use HPIPM in the SPEED_ABS mode, which ensures fastest runtime. This # does not cause issues since the problem is well bounded. ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM' @@ -190,21 +190,18 @@ def gen_long_mpc_solver(): return ocp -class LongitudinalMpc(): +class LongitudinalMpc: def __init__(self, e2e=False): self.e2e = e2e self.reset() - self.accel_limit_arr = np.zeros((N+1, 2)) - self.accel_limit_arr[:,0] = -1.2 - self.accel_limit_arr[:,1] = 1.2 self.source = SOURCES[2] def reset(self): self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR) - self.v_solution = [0.0 for i in range(N+1)] - self.a_solution = [0.0 for i in range(N+1)] - self.prev_a = self.a_solution - self.j_solution = [0.0 for i in range(N)] + self.v_solution = np.zeros(N+1) + self.a_solution = np.zeros(N+1) + self.prev_a = np.array(self.a_solution) + self.j_solution = np.zeros(N) self.yref = np.zeros((N+1, COST_DIM)) for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) @@ -224,6 +221,9 @@ class LongitudinalMpc(): def set_weights(self): if self.e2e: self.set_weights_for_xva_policy() + self.params[:,0] = -10. + self.params[:,1] = 10. + self.params[:,2] = 1e5 else: self.set_weights_for_lead_policy() @@ -264,7 +264,8 @@ class LongitudinalMpc(): self.x0[1] = v self.x0[2] = a - def extrapolate_lead(self, x_lead, v_lead, a_lead, a_lead_tau): + @staticmethod + def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau): a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2)/2.) v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0, 1e8) x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj) @@ -347,15 +348,9 @@ class LongitudinalMpc(): for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - self.accel_limit_arr[:,0] = -10. - self.accel_limit_arr[:,1] = 10. - x_obstacle = 1e5*np.ones((N+1)) - self.params = np.concatenate([self.accel_limit_arr, - x_obstacle[:,None], - self.prev_a], axis=1) + self.params[:,3] = np.copy(self.prev_a) self.run() - def run(self): for i in range(N+1): self.solver.set(i, 'p', self.params[i]) @@ -371,14 +366,13 @@ class LongitudinalMpc(): self.a_solution = self.x_sol[:,2] self.j_solution = self.u_sol[:,0] - self.prev_a = interp(T_IDXS + 0.05, T_IDXS, self.a_solution) + self.prev_a = np.interp(T_IDXS + 0.05, T_IDXS, self.a_solution) t = sec_since_boot() if self.solution_status != 0: if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t - cloudlog.warning("Long mpc reset, solution_status: %s" % ( - self.solution_status)) + cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 41bae4c475..9dc6ab9ee5 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -4,6 +4,7 @@ import numpy as np from common.numpy_fast import interp import cereal.messaging as messaging +from common.filter_simple import FirstOrderFilter from common.realtime import DT_MDL from selfdrive.modeld.constants import T_IDXS from selfdrive.config import Conversions as CV @@ -48,9 +49,8 @@ class Planner: self.fcw = False - self.v_desired = init_v self.a_desired = init_a - self.alpha = np.exp(-DT_MDL / 2.0) + self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) @@ -69,14 +69,13 @@ class Planner: prev_accel_constraint = True if long_control_state == LongCtrlState.off or sm['carState'].gasPressed: - self.v_desired = v_ego + self.v_desired_filter.x = v_ego self.a_desired = a_ego # Smoothly changing between accel trajectory is only relevant when OP is driving prev_accel_constraint = False # Prevent divergence, smooth in current v_ego - self.v_desired = self.alpha * self.v_desired + (1 - self.alpha) * v_ego - self.v_desired = max(0.0, self.v_desired) + self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) @@ -88,7 +87,7 @@ class Planner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) - self.mpc.set_cur_state(self.v_desired, self.a_desired) + self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) self.mpc.update(sm['carState'], sm['radarState'], v_cruise, prev_accel_constraint=prev_accel_constraint) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) @@ -102,7 +101,7 @@ class Planner: # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory)) - self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev) / 2.0 + self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (self.a_desired + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') @@ -113,9 +112,9 @@ class Planner: longitudinalPlan.modelMonoTime = sm.logMonoTime['modelV2'] longitudinalPlan.processingDelay = (plan_send.logMonoTime / 1e9) - sm.logMonoTime['modelV2'] - longitudinalPlan.speeds = [float(x) for x in self.v_desired_trajectory] - longitudinalPlan.accels = [float(x) for x in self.a_desired_trajectory] - longitudinalPlan.jerks = [float(x) for x in self.j_desired_trajectory] + longitudinalPlan.speeds = self.v_desired_trajectory.tolist() + longitudinalPlan.accels = self.a_desired_trajectory.tolist() + longitudinalPlan.jerks = self.j_desired_trajectory.tolist() longitudinalPlan.hasLead = sm['radarState'].leadOne.status longitudinalPlan.longitudinalPlanSource = self.mpc.source diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py index 01498fa138..4f87fdf09b 100644 --- a/selfdrive/controls/lib/radar_helpers.py +++ b/selfdrive/controls/lib/radar_helpers.py @@ -146,7 +146,7 @@ class Cluster(): } def __str__(self): - ret = "x: %4.1f y: %4.1f v: %4.1f a: %4.1f" % (self.dRel, self.yRel, self.vRel, self.aLeadK) + ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}" return ret def potential_low_speed_lead(self, v_ego): diff --git a/selfdrive/controls/lib/tests/test_alertmanager.py b/selfdrive/controls/lib/tests/test_alertmanager.py new file mode 100755 index 0000000000..2b606390c7 --- /dev/null +++ b/selfdrive/controls/lib/tests/test_alertmanager.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 +import random +import unittest + +from selfdrive.controls.lib.events import Alert, EVENTS +from selfdrive.controls.lib.alertmanager import AlertManager + + +class TestAlertManager(unittest.TestCase): + + def test_duration(self): + """ + Enforce that an alert lasts for max(alert duration, duration the alert is added) + """ + for duration in range(1, 100): + alert = None + while not isinstance(alert, Alert): + event = random.choice([e for e in EVENTS.values() if len(e)]) + alert = random.choice(list(event.values())) + + alert.duration = duration + + # check two cases: + # - alert is added to AM for <= the alert's duration + # - alert is added to AM for > alert's duration + for greater in (True, False): + if greater: + add_duration = duration + random.randint(1, 10) + else: + add_duration = random.randint(1, duration) + show_duration = max(duration, add_duration) + + AM = AlertManager() + for frame in range(duration+10): + if frame < add_duration: + AM.add_many(frame, [alert, ]) + current_alert = AM.process_alerts(frame) + + shown = current_alert is not None + should_show = frame <= show_duration + self.assertEqual(shown, should_show, msg=f"{frame=} {add_duration=} {duration=}") + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py new file mode 100755 index 0000000000..f2636027e8 --- /dev/null +++ b/selfdrive/controls/lib/tests/test_vehicle_model.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import math +import unittest + +import numpy as np +from control import StateSpace + +from selfdrive.car.honda.interface import CarInterface +from selfdrive.car.honda.values import CAR +from selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices + + +class TestVehicleModel(unittest.TestCase): + def setUp(self): + CP = CarInterface.get_params(CAR.CIVIC) + self.VM = VehicleModel(CP) + + def test_round_trip_yaw_rate(self): + # TODO: fix VM to work at zero speed + for u in np.linspace(1, 30, num=10): + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + yr = self.VM.yaw_rate(sa, u, roll) + new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll) + + self.assertAlmostEqual(sa, new_sa) + + def test_dyn_ss_sol_against_yaw_rate(self): + """Verify that the yaw_rate helper function matches the results + from the state space model.""" + + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for u in np.linspace(1, 30, num=10): + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + + # Compute yaw rate based on state space model + _, yr1 = dyn_ss_sol(sa, u, roll, self.VM) + + # Compute yaw rate using direct computations + yr2 = self.VM.yaw_rate(sa, u, roll) + self.assertAlmostEqual(float(yr1), yr2) + + def test_syn_ss_sol_simulate(self): + """Verifies that dyn_ss_sol mathes a simulation""" + + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for u in np.linspace(1, 30, num=10): + A, B = create_dyn_state_matrices(u, self.VM) + + # Convert to discrete time system + ss = StateSpace(A, B, np.eye(2), np.zeros((2, 2))) + ss = ss.sample(0.01) + + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + inp = np.array([[sa], [roll]]) + + # Simulate for 1 second + x1 = np.zeros((2, 1)) + for _ in range(100): + x1 = ss.A @ x1 + ss.B @ inp + + # Compute steady state solution directly + x2 = dyn_ss_sol(sa, u, roll, self.VM) + + np.testing.assert_almost_equal(x1, x2, decimal=3) + + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/lib/vehicle_model.py b/selfdrive/controls/lib/vehicle_model.py index a0b1dddfd7..3f180d3252 100755 --- a/selfdrive/controls/lib/vehicle_model.py +++ b/selfdrive/controls/lib/vehicle_model.py @@ -5,7 +5,7 @@ Dynamic bicycle model from "The Science of Vehicle Dynamics (2014), M. Guiggiani The state is x = [v, r]^T with v lateral speed [m/s], and r rotational speed [rad/s] -The input u is the steering angle [rad] +The input u is the steering angle [rad], and roll [rad] The system is defined by x_dot = A*x + B*u @@ -19,6 +19,9 @@ from numpy.linalg import solve from cereal import car +ACCELERATION_DUE_TO_GRAVITY = 9.8 + + class VehicleModel: def __init__(self, CP: car.CarParams): """ @@ -43,7 +46,7 @@ class VehicleModel: self.cR = stiffness_factor * self.cR_orig self.sR = steer_ratio - def steady_state_sol(self, sa: float, u: float) -> np.ndarray: + def steady_state_sol(self, sa: float, u: float, roll: float) -> np.ndarray: """Returns the steady state solution. If the speed is too low we can't use the dynamic model (tire slip is undefined), @@ -52,26 +55,28 @@ class VehicleModel: Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: 2x1 matrix with steady state solution (lateral speed, rotational speed) """ if u > 0.1: - return dyn_ss_sol(sa, u, self) + return dyn_ss_sol(sa, u, roll, self) else: return kin_ss_sol(sa, u, self) - def calc_curvature(self, sa: float, u: float) -> float: + def calc_curvature(self, sa: float, u: float, roll: float) -> float: """Returns the curvature. Multiplied by the speed this will give the yaw rate. Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: Curvature factor [1/m] """ - return self.curvature_factor(u) * sa / self.sR + return (self.curvature_factor(u) * sa / self.sR) + self.roll_compensation(roll, u) def curvature_factor(self, u: float) -> float: """Returns the curvature factor. @@ -86,43 +91,63 @@ class VehicleModel: sf = calc_slip_factor(self) return (1. - self.chi) / (1. - sf * u**2) / self.l - def get_steer_from_curvature(self, curv: float, u: float) -> float: + def get_steer_from_curvature(self, curv: float, u: float, roll: float) -> float: """Calculates the required steering wheel angle for a given curvature Args: curv: Desired curvature [1/m] u: Speed [m/s] + roll: Road Roll [rad] Returns: Steering wheel angle [rad] """ - return curv * self.sR * 1.0 / self.curvature_factor(u) + return (curv - self.roll_compensation(roll, u)) * self.sR * 1.0 / self.curvature_factor(u) + + def roll_compensation(self, roll: float, u: float) -> float: + """Calculates the roll-compensation to curvature + + Args: + roll: Road Roll [rad] + u: Speed [m/s] - def get_steer_from_yaw_rate(self, yaw_rate: float, u: float) -> float: + Returns: + Roll compensation curvature [rad] + """ + sf = calc_slip_factor(self) + + if abs(sf) < 1e-6: + return 0 + else: + return (ACCELERATION_DUE_TO_GRAVITY * roll) / ((1 / sf) - u**2) + + def get_steer_from_yaw_rate(self, yaw_rate: float, u: float, roll: float) -> float: """Calculates the required steering wheel angle for a given yaw_rate Args: yaw_rate: Desired yaw rate [rad/s] u: Speed [m/s] + roll: Road Roll [rad] Returns: Steering wheel angle [rad] """ curv = yaw_rate / u - return self.get_steer_from_curvature(curv, u) + return self.get_steer_from_curvature(curv, u, roll) - def yaw_rate(self, sa: float, u: float) -> float: + def yaw_rate(self, sa: float, u: float, roll: float) -> float: """Calculate yaw rate Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: Yaw rate [rad/s] """ - return self.calc_curvature(sa, u) * u + return self.calc_curvature(sa, u, roll) * u def kin_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: @@ -152,7 +177,7 @@ def create_dyn_state_matrices(u: float, VM: VehicleModel) -> Tuple[np.ndarray, n VM: Vehicle model Returns: - A tuple with the 2x2 A matrix, and 2x1 B matrix + A tuple with the 2x2 A matrix, and 2x2 B matrix Parameters in the vehicle model: cF: Tire stiffness Front [N/rad] @@ -165,30 +190,38 @@ def create_dyn_state_matrices(u: float, VM: VehicleModel) -> Tuple[np.ndarray, n chi: Steer ratio rear [-] """ A = np.zeros((2, 2)) - B = np.zeros((2, 1)) + B = np.zeros((2, 2)) A[0, 0] = - (VM.cF + VM.cR) / (VM.m * u) A[0, 1] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.m * u) - u A[1, 0] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.j * u) A[1, 1] = - (VM.cF * VM.aF**2 + VM.cR * VM.aR**2) / (VM.j * u) + + # Steering input B[0, 0] = (VM.cF + VM.chi * VM.cR) / VM.m / VM.sR B[1, 0] = (VM.cF * VM.aF - VM.chi * VM.cR * VM.aR) / VM.j / VM.sR + + # Roll input + B[0, 1] = -ACCELERATION_DUE_TO_GRAVITY + return A, B -def dyn_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: +def dyn_ss_sol(sa: float, u: float, roll: float, VM: VehicleModel) -> np.ndarray: """Calculate the steady state solution when x_dot = 0, Ax + Bu = 0 => x = -A^{-1} B u Args: sa: Steering angle [rad] u: Speed [m/s] + roll: Road Roll [rad] VM: Vehicle model Returns: 2x1 matrix with steady state solution """ A, B = create_dyn_state_matrices(u, VM) - return -solve(A, B) * sa + inp = np.array([[sa], [roll]]) + return -solve(A, B) @ inp def calc_slip_factor(VM): diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 88f39ce431..d4b0733692 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -135,7 +135,7 @@ class RadarD(): self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) idens = list(sorted(self.tracks.keys())) - track_pts = list([self.tracks[iden].get_key_for_cluster() for iden in idens]) + track_pts = [self.tracks[iden].get_key_for_cluster() for iden in idens] # If we have multiple points, cluster them if len(track_pts) > 1: @@ -172,9 +172,10 @@ class RadarD(): radarState.carStateMonoTime = sm.logMonoTime['carState'] if enable_lead: - if len(sm['modelV2'].leadsV3) > 1: - radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, sm['modelV2'].leadsV3[0], low_speed_override=True) - radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, sm['modelV2'].leadsV3[1], low_speed_override=False) + leads_v3 = sm['modelV2'].leadsV3 + if len(leads_v3) > 1: + radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True) + radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False) return dat @@ -189,7 +190,7 @@ def radard_thread(sm=None, pm=None, can_sock=None): # import the radar from the fingerprint cloudlog.info("radard is importing %s", CP.carName) - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % CP.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{CP.carName}.radar_interface').RadarInterface # *** setup messaging if can_sock is None: diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index a55cb373a1..2502bed06b 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -8,7 +8,7 @@ from PIL import Image, ImageDraw, ImageFont from cereal import log, car from common.basedir import BASEDIR from common.params import Params -from selfdrive.controls.lib.events import Alert, EVENTS +from selfdrive.controls.lib.events import Alert, EVENTS, ET from selfdrive.controls.lib.alertmanager import set_offroad_alert AlertSize = log.ControlsState.AlertSize @@ -61,7 +61,7 @@ class TestAlerts(unittest.TestCase): for alert in ALERTS: # for full size alerts, both text fields wrap the text, # so it's unlikely that they would go past the max width - if alert.alert_size in [AlertSize.none, AlertSize.full]: + if alert.alert_size in (AlertSize.none, AlertSize.full): continue for i, txt in enumerate([alert.alert_text_1, alert.alert_text_2]): @@ -70,24 +70,32 @@ class TestAlerts(unittest.TestCase): font = fonts[alert.alert_size][i] w, _ = draw.textsize(txt, font) - msg = "type: %s msg: %s" % (alert.alert_type, txt) + msg = f"type: {alert.alert_type} msg: {txt}" self.assertLessEqual(w, max_text_width, msg=msg) def test_alert_sanity_check(self): - for a in ALERTS: - if a.alert_size == AlertSize.none: - self.assertEqual(len(a.alert_text_1), 0) - self.assertEqual(len(a.alert_text_2), 0) - elif a.alert_size == AlertSize.small: - self.assertGreater(len(a.alert_text_1), 0) - self.assertEqual(len(a.alert_text_2), 0) - elif a.alert_size == AlertSize.mid: - self.assertGreater(len(a.alert_text_1), 0) - self.assertGreater(len(a.alert_text_2), 0) - else: - self.assertGreater(len(a.alert_text_1), 0) - - self.assertGreaterEqual(a.duration, 0.) + for event_types in EVENTS.values(): + for event_type, a in event_types.items(): + # TODO: add callback alerts + if not isinstance(a, Alert): + continue + + if a.alert_size == AlertSize.none: + self.assertEqual(len(a.alert_text_1), 0) + self.assertEqual(len(a.alert_text_2), 0) + elif a.alert_size == AlertSize.small: + self.assertGreater(len(a.alert_text_1), 0) + self.assertEqual(len(a.alert_text_2), 0) + elif a.alert_size == AlertSize.mid: + self.assertGreater(len(a.alert_text_1), 0) + self.assertGreater(len(a.alert_text_2), 0) + else: + self.assertGreater(len(a.alert_text_1), 0) + + self.assertGreaterEqual(a.duration, 0.) + + if event_type not in (ET.WARNING, ET.PERMANENT, ET.PRE_ENABLE): + self.assertEqual(a.creation_delay, 0.) def test_offroad_alerts(self): params = Params() diff --git a/selfdrive/debug/can_printer.py b/selfdrive/debug/can_printer.py index cb6ff29006..b4436bd2be 100755 --- a/selfdrive/debug/can_printer.py +++ b/selfdrive/debug/can_printer.py @@ -22,7 +22,7 @@ def can_printer(bus, max_msg, addr): if sec_since_boot() - lp > 0.1: dd = chr(27) + "[2J" - dd += "%5.2f\n" % (sec_since_boot() - start) + dd += f"{sec_since_boot() - start:5.2f}\n" for addr in sorted(msgs.keys()): a = msgs[addr][-1].decode('ascii', 'backslashreplace') x = binascii.hexlify(msgs[addr][-1]).decode('ascii') diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index 300b3ea1fb..424ad67b6d 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -42,6 +42,6 @@ if __name__ == "__main__": for name in socket_names: dts = np.diff(rcv_times[name]) mean = np.mean(dts) - print("%s: Freq %.2f Hz, Min %.2f%%, Max %.2f%%, valid " % (name, 1.0 / mean, np.min(dts) / mean * 100, np.max(dts) / mean * 100), all(valids[name])) + print(f"{name}: Freq {1.0 / mean:.2f} Hz, Min {np.min(dts) / mean * 100:.2f}%, Max {np.max(dts) / mean * 100:.2f}%, valid ", all(valids[name])) prev_print = t diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index aafc696b46..76e809d2c4 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -110,14 +110,14 @@ if __name__ == "__main__": stat['avg'][name] = (stat['avg'][name] * (i - c) + avg * c) / (i) stat['cpu_samples'][name] = [] - msg = 'avg: {1:.2%}, min: {2:.2%}, max: {3:.2%} {0}'.format(os.path.basename(k), stat['avg']['total'], stat['min']['total'], stat['max']['total']) + msg = f"avg: {stat['avg']['total']:.2%}, min: {stat['min']['total']:.2%}, max: {stat['max']['total']:.2%} {os.path.basename(k)}" if args.detailed_times: for stat_type in ['avg', 'min', 'max']: - msg += '\n {}: {}'.format(stat_type, [name + ':' + str(round(stat[stat_type][name]*100, 2)) for name in cpu_time_names]) + msg += f"\n {stat_type}: {[(name + ':' + str(round(stat[stat_type][name] * 100, 2))) for name in cpu_time_names]}" l.append((os.path.basename(k), stat['avg']['total'], msg)) l.sort(key=lambda x: -x[1]) for x in l: print(x[2]) - print('avg sum: {0:.2%} over {1} samples {2} seconds\n'.format( + print('avg sum: {:.2%} over {} samples {} seconds\n'.format( sum(stat['avg']['total'] for k, stat in stats.items()), i, i * SLEEP_INTERVAL )) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index ca238b4293..f28d5373f4 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -52,7 +52,7 @@ def cycle_alerts(duration=200, is_metric=False): events.clear() events.add(alert) - a = events.create_alerts([et, ], [CP, sm, is_metric]) + a = events.create_alerts([et, ], [CP, sm, is_metric, 0]) AM.add_many(frame, a) AM.process_alerts(frame) print(AM.alert) diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py index 047c874fb7..f208920f12 100755 --- a/selfdrive/debug/dump.py +++ b/selfdrive/debug/dump.py @@ -54,13 +54,13 @@ if __name__ == "__main__": elif args.dump_json: print(json.dumps(evt.to_dict())) elif values: - print("logMonotime = {}".format(evt.logMonoTime)) + print(f"logMonotime = {evt.logMonoTime}") for value in values: if hasattr(evt, value[0]): item = evt for key in value: item = getattr(item, key) - print("{} = {}".format(".".join(value), item)) + print(f"{'.'.join(value)} = {item}") print("") else: try: diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 098e39fbe8..326e68f8e7 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -41,5 +41,5 @@ if __name__ == "__main__": sys.exit(1) route = Route(sys.argv[1]) - lr = MultiLogIterator(route.log_paths()[:5], wraparound=False) + lr = MultiLogIterator(route.log_paths()[:5]) get_fingerprint(lr) diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index 6c38957be3..e678db4f17 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -27,5 +27,5 @@ while True: fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items())) - print("number of messages {0}:".format(len(msgs))) - print("fingerprint {0}".format(fingerprint)) + print(f"number of messages {len(msgs)}:") + print(f"fingerprint {fingerprint}") diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 204d2480c3..8132a46552 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -13,42 +13,45 @@ USE AT YOUR OWN RISK! Safety features, like AEB and FCW, might be affected by th import sys import argparse +from typing import NamedTuple from subprocess import check_output, CalledProcessError from panda.python import Panda from panda.python.uds import UdsClient, SESSION_TYPE, DATA_IDENTIFIER_TYPE +class ConfigValues(NamedTuple): + default_config: bytes + tracks_enabled: bytes + # If your radar supports changing data identifier 0x0142 as well make a PR to # this file to add your firmware version. Make sure to post a drive as proof! # NOTE: these firmware versions do not match what openpilot uses # because this script uses a different diagnostic session type SUPPORTED_FW_VERSIONS = { # 2020 SONATA - b"DN8_ SCC FHCUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": { - "default_config": b"\x00\x00\x00\x01\x00\x00", - "tracks_enabled": b"\x00\x00\x00\x01\x00\x01", - }, + b"DN8_ SCC FHCUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2021 SONATA HYBRID - b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": { - "default_config": b"\x00\x00\x00\x01\x00\x00", - "tracks_enabled": b"\x00\x00\x00\x01\x00\x01", - }, + b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2020 PALISADE - b"LX2_ SCC FHCUP 1.00 1.04 99110-S8100\x19\x05\x02\x16V ": { - "default_config": b"\x00\x00\x00\x01\x00\x00", - "tracks_enabled": b"\x00\x00\x00\x01\x00\x01", - }, + b"LX2_ SCC FHCUP 1.00 1.04 99110-S8100\x19\x05\x02\x16V ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + # 2022 PALISADE + b"LX2_ SCC FHCUP 1.00 1.00 99110-S8110!\x04\x05\x17\x01 ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2020 SANTA FE - b"TM__ SCC F-CUP 1.00 1.03 99110-S2000\x19\x050\x13' ": { - "default_config": b"\x00\x00\x00\x01\x00\x00", - "tracks_enabled": b"\x00\x00\x00\x01\x00\x01", - }, - + b"TM__ SCC F-CUP 1.00 1.03 99110-S2000\x19\x050\x13' ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2020 GENESIS G70 - b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': { - "default config": b"\x00\x00\x00\x01\x00\x00", - "tracks_enabled": b"\x00\x00\x00\x01\x00\x01", - }, + b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), } if __name__ == "__main__": @@ -90,13 +93,14 @@ if __name__ == "__main__": print("[GET CONFIGURATION]") config_data_id : DATA_IDENTIFIER_TYPE = 0x0142 # type: ignore current_config = uds_client.read_data_by_identifier(config_data_id) - new_config = SUPPORTED_FW_VERSIONS[fw_version]["default_config" if args.default else "tracks_enabled"] + config_values = SUPPORTED_FW_VERSIONS[fw_version] + new_config = config_values.default_config if args.default else config_values.tracks_enabled print(f"current config: 0x{current_config.hex()}") if current_config != new_config: print("[CHANGE CONFIGURATION]") print(f"new config: 0x{new_config.hex()}") uds_client.write_data_by_identifier(config_data_id, new_config) - if not args.default and current_config != SUPPORTED_FW_VERSIONS[fw_version]["default_config"]: + if not args.default and current_config != SUPPORTED_FW_VERSIONS[fw_version].default_config: print("\ncurrent config does not match expected default! (aborted)") sys.exit(1) diff --git a/selfdrive/debug/internal/power_monitor.py b/selfdrive/debug/internal/power_monitor.py index 3377088f8b..c995ef6ff2 100755 --- a/selfdrive/debug/internal/power_monitor.py +++ b/selfdrive/debug/internal/power_monitor.py @@ -45,7 +45,7 @@ if __name__ == '__main__': capacity_average = average(capacity_average, capacity) bat_temp_average = average(bat_temp_average, bat_temp) - print("%.2f volts %12.2f ma %12.2f mW %8.2f%% battery %8.1f degC" % (voltage, current, power, capacity, bat_temp)) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {capacity:8.2f}% battery {bat_temp:8.1f} degC") time.sleep(0.1) finally: stop_time = datetime.now() @@ -55,8 +55,8 @@ if __name__ == '__main__': power = power_average[0] capacity = capacity_average[0] bat_temp = bat_temp_average[0] - print("%.2f volts %12.2f ma %12.2f mW %8.2f%% battery %8.1f degC" % (voltage, current, power, capacity, bat_temp)) - print(" {:.2f} Seconds {} samples".format((stop_time-start_time).total_seconds(), voltage_average[1])) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {capacity:8.2f}% battery {bat_temp:8.1f} degC") + print(f" {(stop_time - start_time).total_seconds():.2f} Seconds {voltage_average[1]} samples") print("----------------------------------------------------------------") # reenable charging diff --git a/selfdrive/debug/internal/rt/atrace.sh b/selfdrive/debug/internal/rt/atrace.sh deleted file mode 100755 index 826178c329..0000000000 --- a/selfdrive/debug/internal/rt/atrace.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/bash - -echo 96000 > /d/tracing/buffer_size_kb -atrace -t 10 sched workq -b 96000 > /tmp/trace.txt diff --git a/selfdrive/debug/internal/rt/record_trace.sh b/selfdrive/debug/internal/rt/record_trace.sh deleted file mode 100755 index 00593bfca4..0000000000 --- a/selfdrive/debug/internal/rt/record_trace.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/bash - -cd /d/tracing - -# setup tracer -echo "function" > current_tracer - -echo "start tracing" -echo 1 > tracing_on - -# do stuff -sleep 2 -#/data/openpilot/scripts/restart_modem.sh -#sleep 3 -#/data/openpilot/scripts/restart_modem.sh -sleep 5 - -# disable tracing -echo "done tracing" -echo 0 > tracing_on - -# copy -echo "copy traces" -cp trace /tmp/trace.txt -cp per_cpu/cpu3/trace /tmp/trace_cpu3.txt diff --git a/selfdrive/debug/internal/rt/waste3.c b/selfdrive/debug/internal/rt/waste3.c deleted file mode 100644 index c536ca9c6d..0000000000 --- a/selfdrive/debug/internal/rt/waste3.c +++ /dev/null @@ -1,63 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include "../../../common/util.h" -#include "../../../common/timing.h" - -#define CORES 3 -double ttime[CORES]; -double oout[CORES]; - -void waste(int core) { - prctl(PR_SET_NAME, (unsigned long)"waste", 0, 0, 0); - - cpu_set_t my_set; - CPU_ZERO(&my_set); - CPU_SET(core, &my_set); - int ret = sched_setaffinity(0, sizeof(cpu_set_t), &my_set); - printf("set affinity to %d: %d\n", core, ret); - - //struct sched_param sa; - //memset(&sa, 0, sizeof(sa)); - //sa.sched_priority = 51; - //sched_setscheduler(syscall(SYS_gettid), SCHED_FIFO, &sa); - - float32x4_t *tmp = (float32x4_t *)malloc(0x1000008*sizeof(float32x4_t)); - float32x4_t out; - - uint64_t i = 0; - double sec = seconds_since_boot(); - while(1) { - int j; - for (j = 0; j < 0x1000000; j++) { - out = vmlaq_f32(out, tmp[j], tmp[j+1]); - } - if (i == 0x8) { - double nsec = seconds_since_boot(); - ttime[core] = nsec-sec; - oout[core] = out[0] + out[1] + out[2] + out[3]; - i = 0; - sec = nsec; - } - i++; - } -} - -int main() { - pthread_t waster[CORES]; - for (int i = 0 ; i < CORES; i++) { - pthread_create(&waster[i], NULL, waste, (void*)i); - } - while (1) { - for (int i = 0 ; i < CORES; i++) { - printf("%.2f ", ttime[i]); - } - printf("\n"); - sleep(1); - } -} - diff --git a/selfdrive/debug/internal/sensor_test_bootloop.py b/selfdrive/debug/internal/sensor_test_bootloop.py index 9e89add6bb..36eb112e44 100755 --- a/selfdrive/debug/internal/sensor_test_bootloop.py +++ b/selfdrive/debug/internal/sensor_test_bootloop.py @@ -17,7 +17,7 @@ except PermissionError: print("WARNING: failed to make /dev/shm") try: - with open('/tmp/sensor-test-results.json', 'r') as infile: + with open('/tmp/sensor-test-results.json') as infile: data = json.load(infile) except Exception: data = {'sensor-pass': 0, 'sensor-fail': 0} diff --git a/selfdrive/debug/internal/sounds/set_volume.sh b/selfdrive/debug/internal/sounds/set_volume.sh deleted file mode 100755 index b25a421822..0000000000 --- a/selfdrive/debug/internal/sounds/set_volume.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/bash -while true -do - service call audio 3 i32 3 i32 $1 i32 1 - sleep 1 -done diff --git a/selfdrive/debug/internal/sounds/test_sound_stability.py b/selfdrive/debug/internal/sounds/test_sound_stability.py deleted file mode 100755 index ba6c59220b..0000000000 --- a/selfdrive/debug/internal/sounds/test_sound_stability.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -import os -import subprocess -import time -import datetime -import random - -from common.basedir import BASEDIR -import cereal.messaging as messaging - -if __name__ == "__main__": - - sound_dir = os.path.join(BASEDIR, "selfdrive/assets/sounds") - sound_files = [f for f in os.listdir(sound_dir) if f.endswith(".wav")] - play_sound = os.path.join(BASEDIR, "selfdrive/ui/test/play_sound") - - print("disabling charging") - os.system('echo "0" > /sys/class/power_supply/battery/charging_enabled') - - os.environ["LD_LIBRARY_PATH"] = "" - - sm = messaging.SubMaster(["deviceState"]) - - FNULL = open(os.devnull, "w") - start_time = time.time() - while True: - volume = 15 - - n = random.randint(5, 10) - procs = [] - for _ in range(n): - sound = random.choice(sound_files) - p = subprocess.Popen([play_sound, os.path.join(sound_dir, sound), str(volume)], stdout=FNULL, stderr=FNULL) - procs.append(p) - time.sleep(random.uniform(0, 0.75)) - - time.sleep(random.randint(0, 5)) - for p in procs: - p.terminate() - - sm.update(0) - s = time.time() - start_time - hhmmss = str(datetime.timedelta(seconds=s)).split(".")[0] - print("test duration:", hhmmss) - print("\tbattery percent", sm["deviceState"].batteryPercent) diff --git a/selfdrive/debug/internal/sounds/test_sounds.py b/selfdrive/debug/internal/sounds/test_sounds.py deleted file mode 100755 index 3ed3a51589..0000000000 --- a/selfdrive/debug/internal/sounds/test_sounds.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import time - -from common.basedir import BASEDIR - -if __name__ == "__main__": - - sound_dir = os.path.join(BASEDIR, "selfdrive/assets/sounds") - sound_files = [f for f in os.listdir(sound_dir) if f.endswith(".wav")] - - play_sound = os.path.join(BASEDIR, "selfdrive/ui/test/play_sound") - - os.environ["LD_LIBRARY_PATH"] = "" - - while True: - for volume in range(10, 16): - for sound in sound_files: - p = subprocess.Popen([play_sound, os.path.join(sound_dir, sound), str(volume)]) - time.sleep(1) - p.terminate() diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index 5589b0d6dd..e46c0b0a1f 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -71,7 +71,7 @@ if __name__ == "__main__": total_times = total_times_new[:] busy_times = busy_times_new[:] - print("CPU %.2f%% - RAM: %.2f%% - Temp %.2fC" % (100. * mean(cores), last_mem, last_temp)) + print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C") if args.cpu and prev_proclog is not None: procs = {} diff --git a/selfdrive/debug/profiling/palanteer/.gitignore b/selfdrive/debug/profiling/palanteer/.gitignore new file mode 100644 index 0000000000..158e7b0759 --- /dev/null +++ b/selfdrive/debug/profiling/palanteer/.gitignore @@ -0,0 +1,2 @@ +palanteer/ +viewer diff --git a/selfdrive/debug/profiling/palanteer/setup.sh b/selfdrive/debug/profiling/palanteer/setup.sh new file mode 100755 index 0000000000..e912a9367f --- /dev/null +++ b/selfdrive/debug/profiling/palanteer/setup.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d palanteer ]; then + git clone https://github.com/dfeneyrou/palanteer + pip install wheel + sudo apt install libunwind-dev libdw-dev +fi + +cd palanteer +git pull + +mkdir -p build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) + +pip install --force-reinstall python/dist/palanteer*.whl + +cp bin/palanteer $DIR/viewer diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index bcb741f54e..a6e67d2b24 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -17,13 +17,13 @@ if __name__ == "__main__": cfg = [c for c in CONFIGS if c.proc_name == args.process][0] route = Route(args.route) - lr = MultiLogIterator(route.log_paths(), wraparound=False) + lr = MultiLogIterator(route.log_paths()) inputs = list(lr) outputs = replay_process(cfg, inputs) # Remove message generated by the process under test and merge in the new messages - produces = set(o.which() for o in outputs) + produces = {o.which() for o in outputs} inputs = [i for i in inputs if i.which() not in produces] outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/toyota_eps_factor.py index e63122b6a2..0a459bb719 100755 --- a/selfdrive/debug/toyota_eps_factor.py +++ b/selfdrive/debug/toyota_eps_factor.py @@ -59,6 +59,6 @@ def get_eps_factor(lr, plot=False): if __name__ == "__main__": r = Route(sys.argv[1]) - lr = MultiLogIterator(r.log_paths(), wraparound=False) + lr = MultiLogIterator(r.log_paths()) n = get_eps_factor(lr, plot="--plot" in sys.argv) print("EPS torque factor: ", n) diff --git a/selfdrive/hardware/base.py b/selfdrive/hardware/base.py index 06c86f0c26..77ddcbc2d4 100644 --- a/selfdrive/hardware/base.py +++ b/selfdrive/hardware/base.py @@ -1,11 +1,12 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC from collections import namedtuple +from typing import Dict ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic']) -class HardwareBase: +class HardwareBase(ABC): @staticmethod - def get_cmdline(): + def get_cmdline() -> Dict[str, str]: with open('/proc/cmdline') as f: cmdline = f.read() return {kv[0]: kv[1] for kv in [s.split('=') for s in cmdline.split(' ')] if len(kv) == 2} diff --git a/selfdrive/hardware/eon/androidd.py b/selfdrive/hardware/eon/androidd.py index 6de00d8e71..b836eb0129 100755 --- a/selfdrive/hardware/eon/androidd.py +++ b/selfdrive/hardware/eon/androidd.py @@ -52,25 +52,26 @@ def main(): cloudlog.event("android service pid changed", proc=p, cur=cur[p], prev=procs[p]) procs.update(cur) - # check modem state - state = get_modem_state() - if state != modem_state and not modem_killed: - cloudlog.event("modem state changed", state=state) - modem_state = state + if os.path.exists(MODEM_PATH): + # check modem state + state = get_modem_state() + if state != modem_state and not modem_killed: + cloudlog.event("modem state changed", state=state) + modem_state = state - # check modem crashes - cnt = get_modem_crash_count() - if cnt is not None: - if cnt > crash_count: - cloudlog.event("modem crash", count=cnt) - crash_count = cnt + # check modem crashes + cnt = get_modem_crash_count() + if cnt is not None: + if cnt > crash_count: + cloudlog.event("modem crash", count=cnt) + crash_count = cnt - # handle excessive modem crashes - if crash_count > MAX_MODEM_CRASHES and not modem_killed: - cloudlog.event("killing modem") - with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: - f.write("put") - modem_killed = True + # handle excessive modem crashes + if crash_count > MAX_MODEM_CRASHES and not modem_killed: + cloudlog.event("killing modem") + with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: + f.write("put") + modem_killed = True time.sleep(1) diff --git a/selfdrive/hardware/eon/hardware.py b/selfdrive/hardware/eon/hardware.py index fa275c5a9c..4ab9f81fcf 100644 --- a/selfdrive/hardware/eon/hardware.py +++ b/selfdrive/hardware/eon/hardware.py @@ -10,6 +10,12 @@ from typing import List, Union from cereal import log from selfdrive.hardware.base import HardwareBase, ThermalConfig +try: + from common.params import Params +except Exception: + # openpilot is not built yet + Params = None + NetworkType = log.DeviceState.NetworkType NetworkStrength = log.DeviceState.NetworkStrength @@ -70,6 +76,11 @@ class Android(HardwareBase): return f.read().strip() def get_device_type(self): + try: + if int(Params().get("LastPeripheralPandaType")) == log.PandaState.PandaType.uno: + return "two" + except Exception: + pass return "eon" def get_sound_card_online(self): diff --git a/selfdrive/hardware/tici/agnos.json b/selfdrive/hardware/tici/agnos.json index 14f5567c73..f22575a335 100644 --- a/selfdrive/hardware/tici/agnos.json +++ b/selfdrive/hardware/tici/agnos.json @@ -1,10 +1,10 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c.img.xz", - "hash": "ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c", - "hash_raw": "ab4b6f64a90617ddbebe250f977616d70a25864f82c9c6ea9d88ebc5fe80e37c", - "size": 14772224, + "url": "https://commadist.azureedge.net/agnosupdate/boot-5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c.img.xz", + "hash": "5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c", + "hash_raw": "5ad783213b7de18400f5fd3609fe75677fec80780ae31cbdf5a8ee7106675d7c", + "size": 14768128, "sparse": false, "full_check": true, "has_ab": true @@ -41,9 +41,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-a778d523851d88a78ad7440ab602a80e09decdd1877f9f31ea36a7d7f15970dd.img.xz", - "hash": "540ee7184cc6d8c14f94e652a062027dcc7559e47f4b347b6f8abac570521ec6", - "hash_raw": "a778d523851d88a78ad7440ab602a80e09decdd1877f9f31ea36a7d7f15970dd", + "url": "https://commadist.azureedge.net/agnosupdate/system-0fee88a42385d067756e9b25d57a80228835310deb7b5eef7b7bed5c22c45515.img.xz", + "hash": "a043cba1ae08ca6d17704a8a0978b1e27e5bc79abb85b97efd35203ae26ae1ea", + "hash_raw": "0fee88a42385d067756e9b25d57a80228835310deb7b5eef7b7bed5c22c45515", "size": 10737418240, "sparse": true, "full_check": false, diff --git a/selfdrive/hardware/tici/agnos.py b/selfdrive/hardware/tici/agnos.py index d62d4dbc51..a28b13ac4a 100755 --- a/selfdrive/hardware/tici/agnos.py +++ b/selfdrive/hardware/tici/agnos.py @@ -5,6 +5,7 @@ import hashlib import requests import struct import subprocess +import time import os from typing import Generator @@ -224,7 +225,6 @@ def verify_agnos_update(manifest_path: str, target_slot_number: int) -> bool: if __name__ == "__main__": import logging - import time import argparse parser = argparse.ArgumentParser(description="Flash and verify AGNOS update", diff --git a/selfdrive/hardware/tici/hardware.h b/selfdrive/hardware/tici/hardware.h index 3d025775ae..c37dbb0a36 100644 --- a/selfdrive/hardware/tici/hardware.h +++ b/selfdrive/hardware/tici/hardware.h @@ -10,7 +10,7 @@ class HardwareTici : public HardwareNone { public: static constexpr float MAX_VOLUME = 0.9; - static constexpr float MIN_VOLUME = 0.3; + static constexpr float MIN_VOLUME = 0.2; static bool TICI() { return true; } static std::string get_os_version() { return "AGNOS " + util::read_file("/VERSION"); diff --git a/selfdrive/hardware/tici/hardware.py b/selfdrive/hardware/tici/hardware.py index 855eee908e..31bfa736c0 100644 --- a/selfdrive/hardware/tici/hardware.py +++ b/selfdrive/hardware/tici/hardware.py @@ -320,6 +320,9 @@ class Tici(HardwareBase): def initialize_hardware(self): self.amplifier.initialize_configuration() + # Allow thermald to write engagement status to kmsg + os.system("sudo chmod a+w /dev/kmsg") + def get_networks(self): r = {} diff --git a/selfdrive/hardware/tici/power_monitor.py b/selfdrive/hardware/tici/power_monitor.py index 65f58876de..d7f113cf2c 100755 --- a/selfdrive/hardware/tici/power_monitor.py +++ b/selfdrive/hardware/tici/power_monitor.py @@ -40,7 +40,7 @@ if __name__ == '__main__': power_average = average(power_average, power) power_total_average = average(power_total_average, power_total) - print("%12.2f mW %12.2f mW %12.2f mW" % (power, power_total, power_total-power)) + print(f"{power:12.2f} mW {power_total:12.2f} mW {power_total - power:12.2f} mW") time.sleep(0.25) finally: stop_time = time.monotonic() @@ -48,6 +48,6 @@ if __name__ == '__main__': voltage = voltage_average[0] current = current_average[0] power = power_average[0] - print("%.2f volts %12.2f ma %12.2f mW %12.2f mW" % (voltage, current, power, power_total)) - print(" {:.2f} Seconds {} samples".format(stop_time - start_time, voltage_average[1])) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {power_total:12.2f} mW") + print(f" {stop_time - start_time:.2f} Seconds {voltage_average[1]} samples") print("----------------------------------------------------------------") diff --git a/selfdrive/hardware/tici/test_agnos_updater.py b/selfdrive/hardware/tici/test_agnos_updater.py new file mode 100755 index 0000000000..9f188b83dc --- /dev/null +++ b/selfdrive/hardware/tici/test_agnos_updater.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +import json +import os +import unittest +import requests + +AGNOS_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__))) +MANIFEST = os.path.join(AGNOS_DIR, "agnos.json") + + +class TestAgnosUpdater(unittest.TestCase): + + def test_manifest(self): + with open(MANIFEST) as f: + m = json.load(f) + + for img in m: + r = requests.head(img['url']) + r.raise_for_status() + self.assertEqual(r.headers['Content-Type'], "application/x-xz") + if not img['sparse']: + assert img['hash'] == img['hash_raw'] + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 604f4f68d9..f2a534d370 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -7,7 +7,7 @@ and the image input into the neural network is not corrected for roll. ''' import os -import copy +from typing import NoReturn import numpy as np import cereal.messaging as messaging from cereal import log @@ -69,7 +69,7 @@ class Calibrator(): if param_put and calibration_params: try: msg = log.Event.from_bytes(calibration_params) - rpy_init = list(msg.liveCalibration.rpyCalib) + rpy_init = np.array(msg.liveCalibration.rpyCalib) valid_blocks = msg.liveCalibration.validBlocks except Exception: cloudlog.exception("Error reading cached CalibrationParams") @@ -79,13 +79,15 @@ class Calibrator(): def reset(self, rpy_init=RPY_INIT, valid_blocks=0, smooth_from=None): if not np.isfinite(rpy_init).all(): - self.rpy = copy.copy(RPY_INIT) + self.rpy = RPY_INIT.copy() else: - self.rpy = rpy_init + self.rpy = rpy_init.copy() + if not np.isfinite(valid_blocks) or valid_blocks < 0: self.valid_blocks = 0 else: self.valid_blocks = valid_blocks + self.rpys = np.tile(self.rpy, (INPUTS_WANTED, 1)) self.idx = 0 @@ -99,10 +101,16 @@ class Calibrator(): self.old_rpy = smooth_from self.old_rpy_weight = 1.0 + def get_valid_idxs(self): + # exclude current block_idx from validity window + before_current = list(range(self.block_idx)) + after_current = list(range(min(self.valid_blocks, self.block_idx + 1), self.valid_blocks)) + return before_current + after_current + def update_status(self): - if self.valid_blocks > 0: - max_rpy_calib = np.array(np.max(self.rpys[:self.valid_blocks], axis=0)) - min_rpy_calib = np.array(np.min(self.rpys[:self.valid_blocks], axis=0)) + if len(self.get_valid_idxs()) > 0: + max_rpy_calib = np.array(np.max(self.rpys[self.get_valid_idxs()], axis=0)) + min_rpy_calib = np.array(np.min(self.rpys[self.get_valid_idxs()], axis=0)) self.calib_spread = np.abs(max_rpy_calib - min_rpy_calib) else: self.calib_spread = np.zeros(3) @@ -157,8 +165,8 @@ class Calibrator(): self.block_idx += 1 self.valid_blocks = max(self.block_idx, self.valid_blocks) self.block_idx = self.block_idx % INPUTS_WANTED - if self.valid_blocks > 0: - self.rpy = np.mean(self.rpys[:self.valid_blocks], axis=0) + if len(self.get_valid_idxs()) > 0: + self.rpy = np.mean(self.rpys[self.get_valid_idxs()], axis=0) self.update_status() @@ -172,16 +180,16 @@ class Calibrator(): msg.liveCalibration.validBlocks = self.valid_blocks msg.liveCalibration.calStatus = self.cal_status msg.liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) - msg.liveCalibration.extrinsicMatrix = [float(x) for x in extrinsic_matrix.flatten()] - msg.liveCalibration.rpyCalib = [float(x) for x in smooth_rpy] - msg.liveCalibration.rpyCalibSpread = [float(x) for x in self.calib_spread] + msg.liveCalibration.extrinsicMatrix = extrinsic_matrix.flatten().tolist() + msg.liveCalibration.rpyCalib = smooth_rpy.tolist() + msg.liveCalibration.rpyCalibSpread = self.calib_spread.tolist() return msg - def send_data(self, pm): + def send_data(self, pm) -> None: pm.send('liveCalibration', self.get_msg()) -def calibrationd_thread(sm=None, pm=None): +def calibrationd_thread(sm=None, pm=None) -> NoReturn: if sm is None: sm = messaging.SubMaster(['cameraOdometry', 'carState'], poll=['cameraOdometry']) @@ -209,7 +217,7 @@ def calibrationd_thread(sm=None, pm=None): calibrator.send_data(pm) -def main(sm=None, pm=None): +def main(sm=None, pm=None) -> NoReturn: calibrationd_thread(sm, pm) diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index 7926846895..35fbd90e56 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -232,9 +232,10 @@ void Localizer::handle_sensors(double current_time, const capnp::Listdevice_fell |= (floatlist2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0; + //this->device_fell |= (floatlist2vector(v) - Vector3d(10.0, 0.0, 0.0)).norm() > 40.0; auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ACCEL_SANITY_CHECK) { @@ -531,7 +532,7 @@ int Localizer::locationd_thread() { } int main() { - set_realtime_priority(5); + util::set_realtime_priority(5); Localizer localizer; return localizer.locationd_thread(); diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index dac95dcd7b..031c56ceb9 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -5,6 +5,7 @@ from typing import Any, Dict import numpy as np +from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from selfdrive.locationd.models.constants import ObservationKind from selfdrive.swaglog import cloudlog @@ -37,6 +38,7 @@ class States(): VELOCITY = _slice(2) # (x, y) [m/s] YAW_RATE = _slice(1) # [rad/s] STEER_ANGLE = _slice(1) # [rad] + ROAD_ROLL = _slice(1) # [rad] class CarKalman(KalmanFilter): @@ -51,6 +53,7 @@ class CarKalman(KalmanFilter): 10.0, 0.0, 0.0, 0.0, + 0.0 ]) # process noise @@ -63,12 +66,14 @@ class CarKalman(KalmanFilter): .1**2, .01**2, math.radians(0.1)**2, math.radians(0.1)**2, + math.radians(1)**2, ]) P_initial = Q.copy() obs_noise: Dict[int, Any] = { ObservationKind.STEER_ANGLE: np.atleast_2d(math.radians(0.01)**2), ObservationKind.ANGLE_OFFSET_FAST: np.atleast_2d(math.radians(10.0)**2), + ObservationKind.ROAD_ROLL: np.atleast_2d(math.radians(1.0)**2), ObservationKind.STEER_RATIO: np.atleast_2d(5.0**2), ObservationKind.STIFFNESS: np.atleast_2d(5.0**2), ObservationKind.ROAD_FRAME_X_SPEED: np.atleast_2d(0.1**2), @@ -87,7 +92,7 @@ class CarKalman(KalmanFilter): def generate_code(generated_dir): dim_state = CarKalman.initial_x.shape[0] name = CarKalman.name - + # vehicle models comes from The Science of Vehicle Dynamics: Handling, Braking, and Ride of Road and Race Cars # Model used is in 6.15 with formula from 6.198 @@ -106,6 +111,7 @@ class CarKalman(KalmanFilter): cF, cR = x * cF_orig, x * cR_orig angle_offset = state[States.ANGLE_OFFSET, :][0, 0] angle_offset_fast = state[States.ANGLE_OFFSET_FAST, :][0, 0] + theta = state[States.ROAD_ROLL, :][0, 0] sa = state[States.STEER_ANGLE, :][0, 0] sR = state[States.STEER_RATIO, :][0, 0] @@ -122,8 +128,12 @@ class CarKalman(KalmanFilter): B[0, 0] = cF / m / sR B[1, 0] = (cF * aF) / j / sR + C = sp.Matrix(np.zeros((2, 1))) + C[0, 0] = ACCELERATION_DUE_TO_GRAVITY + C[1, 0] = 0 + x = sp.Matrix([v, r]) # lateral velocity, yaw rate - x_dot = A * x + B * (sa - angle_offset - angle_offset_fast) + x_dot = A * x + B * (sa - angle_offset - angle_offset_fast) - C * theta dt = sp.Symbol('dt') state_dot = sp.Matrix(np.zeros((dim_state, 1))) @@ -145,11 +155,12 @@ class CarKalman(KalmanFilter): [sp.Matrix([angle_offset_fast]), ObservationKind.ANGLE_OFFSET_FAST, None], [sp.Matrix([sR]), ObservationKind.STEER_RATIO, None], [sp.Matrix([x]), ObservationKind.STIFFNESS, None], + [sp.Matrix([theta]), ObservationKind.ROAD_ROLL, None], ] gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, global_vars=global_vars) - def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0): # pylint: disable=super-init-not-called + def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0, P_initial=None): # pylint: disable=super-init-not-called dim_state = self.initial_x.shape[0] dim_state_err = self.P_initial.shape[0] x_init = self.initial_x @@ -157,6 +168,8 @@ class CarKalman(KalmanFilter): x_init[States.STIFFNESS] = stiffness_factor x_init[States.ANGLE_OFFSET] = angle_offset + if P_initial is not None: + self.P_initial = P_initial # init filter self.filter = EKF_sym(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog) diff --git a/selfdrive/locationd/models/constants.py b/selfdrive/locationd/models/constants.py index 7450f76be7..f22f503ee0 100644 --- a/selfdrive/locationd/models/constants.py +++ b/selfdrive/locationd/models/constants.py @@ -38,6 +38,7 @@ class ObservationKind: STIFFNESS = 28 # [-] STEER_RATIO = 29 # [-] ROAD_FRAME_X_SPEED = 30 # (x) [m/s] + ROAD_ROLL = 31 # [rad] names = [ 'Unknown', @@ -69,6 +70,8 @@ class ObservationKind: 'Fast Angle Offset', 'Stiffness', 'Steer Ratio', + 'Road Frame x speed', + 'Road Roll', ] @classmethod diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index fa52945932..023479d10e 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -158,7 +158,7 @@ class LiveKalman(): delta_x = sp.MatrixSymbol('delta_x', dim_state_err, 1) err_function_sym = sp.Matrix(np.zeros((dim_state, 1))) - delta_quat = sp.Matrix(np.ones((4))) + delta_quat = sp.Matrix(np.ones(4)) delta_quat[1:, :] = sp.Matrix(0.5 * delta_x[States.ECEF_ORIENTATION_ERR, :]) err_function_sym[States.ECEF_POS, :] = sp.Matrix(nom_x[States.ECEF_POS, :] + delta_x[States.ECEF_POS_ERR, :]) err_function_sym[States.ECEF_ORIENTATION, 0] = quat_matrix_r(nom_x[States.ECEF_ORIENTATION, 0]) * delta_quat diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index adb9098d4d..199cc7e205 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -232,7 +232,7 @@ class LocKalman(): err_function_sym = sp.Matrix(np.zeros((dim_state, 1))) for q_idx, q_err_idx in zip(q_idxs, q_err_idxs): - delta_quat = sp.Matrix(np.ones((4))) + delta_quat = sp.Matrix(np.ones(4)) delta_quat[1:, :] = sp.Matrix(0.5 * delta_x[q_err_idx[0]: q_err_idx[1], :]) err_function_sym[q_idx[0]:q_idx[1], 0] = quat_matrix_r(nom_x[q_idx[0]:q_idx[1], 0]) * delta_quat for p_idx, p_err_idx in zip(p_idxs, p_err_idxs): diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 914e0d0223..03de2d6462 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -16,10 +16,12 @@ from selfdrive.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s +ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits +ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) class ParamsLearner: - def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset): - self.kf = CarKalman(GENERATED_DIR, steer_ratio, stiffness_factor, angle_offset) + def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset, P_initial=None): + self.kf = CarKalman(GENERATED_DIR, steer_ratio, stiffness_factor, angle_offset, P_initial) self.kf.filter.set_global("mass", CP.mass) self.kf.filter.set_global("rotational_inertia", CP.rotationalInertia) @@ -30,9 +32,10 @@ class ParamsLearner: self.active = False - self.speed = 0 + self.speed = 0.0 + self.roll = 0.0 self.steering_pressed = False - self.steering_angle = 0 + self.steering_angle = 0.0 self.valid = True @@ -41,16 +44,34 @@ class ParamsLearner: yaw_rate = msg.angularVelocityCalibrated.value[2] yaw_rate_std = msg.angularVelocityCalibrated.std[2] + localizer_roll = msg.orientationNED.value[0] + roll_valid = msg.orientationNED.valid and ROLL_MIN < localizer_roll < ROLL_MAX + if roll_valid: + roll = localizer_roll + roll_std = np.radians(1.0) + else: + # This is done to bound the road roll estimate when localizer values are invalid + roll = 0.0 + roll_std = np.radians(10.0) + self.roll = clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA) + yaw_rate_valid = msg.angularVelocityCalibrated.valid yaw_rate_valid = yaw_rate_valid and 0 < yaw_rate_std < 10 # rad/s yaw_rate_valid = yaw_rate_valid and abs(yaw_rate) < 1 # rad/s if self.active: - if msg.inputsOK and msg.posenetOK and yaw_rate_valid: + if msg.inputsOK and msg.posenetOK: + + if yaw_rate_valid: + self.kf.predict_and_observe(t, + ObservationKind.ROAD_FRAME_YAW_RATE, + np.array([[-yaw_rate]]), + np.array([np.atleast_2d(yaw_rate_std**2)])) + self.kf.predict_and_observe(t, - ObservationKind.ROAD_FRAME_YAW_RATE, - np.array([[-yaw_rate]]), - np.array([np.atleast_2d(yaw_rate_std**2)])) + ObservationKind.ROAD_ROLL, + np.array([[self.roll]]), + np.array([np.atleast_2d(roll_std**2)])) self.kf.predict_and_observe(t, ObservationKind.ANGLE_OFFSET_FAST, np.array([[0]])) elif which == 'carState': @@ -148,29 +169,31 @@ def main(sm=None, pm=None): msg = messaging.new_message('liveParameters') msg.logMonoTime = sm.logMonoTime['carState'] - msg.liveParameters.posenetValid = True - msg.liveParameters.sensorValid = True - msg.liveParameters.steerRatio = float(x[States.STEER_RATIO]) - msg.liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) - msg.liveParameters.angleOffsetAverageDeg = angle_offset_average - msg.liveParameters.angleOffsetDeg = angle_offset - msg.liveParameters.valid = all(( - abs(msg.liveParameters.angleOffsetAverageDeg) < 10.0, - abs(msg.liveParameters.angleOffsetDeg) < 10.0, - 0.2 <= msg.liveParameters.stiffnessFactor <= 5.0, - min_sr <= msg.liveParameters.steerRatio <= max_sr, + liveParameters = msg.liveParameters + liveParameters.posenetValid = True + liveParameters.sensorValid = True + liveParameters.steerRatio = float(x[States.STEER_RATIO]) + liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) + liveParameters.roll = float(x[States.ROAD_ROLL]) + liveParameters.angleOffsetAverageDeg = angle_offset_average + liveParameters.angleOffsetDeg = angle_offset + liveParameters.valid = all(( + abs(liveParameters.angleOffsetAverageDeg) < 10.0, + abs(liveParameters.angleOffsetDeg) < 10.0, + 0.2 <= liveParameters.stiffnessFactor <= 5.0, + min_sr <= liveParameters.steerRatio <= max_sr, )) - msg.liveParameters.steerRatioStd = float(P[States.STEER_RATIO]) - msg.liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS]) - msg.liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET]) - msg.liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST]) + liveParameters.steerRatioStd = float(P[States.STEER_RATIO]) + liveParameters.stiffnessFactorStd = float(P[States.STIFFNESS]) + liveParameters.angleOffsetAverageStd = float(P[States.ANGLE_OFFSET]) + liveParameters.angleOffsetFastStd = float(P[States.ANGLE_OFFSET_FAST]) if sm.frame % 1200 == 0: # once a minute params = { 'carFingerprint': CP.carFingerprint, - 'steerRatio': msg.liveParameters.steerRatio, - 'stiffnessFactor': msg.liveParameters.stiffnessFactor, - 'angleOffsetAverageDeg': msg.liveParameters.angleOffsetAverageDeg, + 'steerRatio': liveParameters.steerRatio, + 'stiffnessFactor': liveParameters.stiffnessFactor, + 'angleOffsetAverageDeg': liveParameters.angleOffsetAverageDeg, } put_nonblocking("LiveParameters", json.dumps(params)) diff --git a/selfdrive/locationd/test/test_calibrationd.py b/selfdrive/locationd/test/test_calibrationd.py index 030636dcd5..3612f48276 100755 --- a/selfdrive/locationd/test/test_calibrationd.py +++ b/selfdrive/locationd/test/test_calibrationd.py @@ -2,6 +2,8 @@ import random import unittest +import numpy as np + import cereal.messaging as messaging from common.params import Params from selfdrive.locationd.calibrationd import Calibrator @@ -16,7 +18,7 @@ class TestCalibrationd(unittest.TestCase): Params().put("CalibrationParams", msg.to_bytes()) c = Calibrator(param_put=True) - self.assertEqual(list(msg.liveCalibration.rpyCalib), c.rpy) + np.testing.assert_allclose(msg.liveCalibration.rpyCalib, c.rpy) self.assertEqual(msg.liveCalibration.validBlocks, c.valid_blocks) diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 5194ce4441..76f82efc10 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -51,6 +51,7 @@ void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t liveloc = self.localizer_get_msg() self.assertTrue(liveloc is not None) + @unittest.skip("temporarily disabled due to false positives") def test_device_fell(self): msg = messaging.new_message('sensorEvents', 1) msg.sensorEvents[0].sensor = 1 diff --git a/selfdrive/locationd/test/ublox.py b/selfdrive/locationd/test/ublox.py index 04422a81cd..9cffbeac40 100644 --- a/selfdrive/locationd/test/ublox.py +++ b/selfdrive/locationd/test/ublox.py @@ -338,7 +338,7 @@ class UBloxDescriptor: ret += '%s, ' % v[a] ret = ret[:-2] + '], ' elif isinstance(v, str): - ret += '%s="%s", ' % (f, v.rstrip(' \0')) + ret += '{}="{}", '.format(f, v.rstrip(' \0')) else: ret += '%s=%s, ' % (f, v) for r in msg._recs: @@ -774,10 +774,9 @@ class UBlox: def read(self, n): '''read some bytes''' if self.use_sendrecv: - import socket try: return self.dev.recv(n) - except socket.error: + except OSError: return '' return self.dev.read(n) diff --git a/selfdrive/locationd/ubloxd.cc b/selfdrive/locationd/ubloxd.cc index bcf33b3f73..ae07284c8e 100644 --- a/selfdrive/locationd/ubloxd.cc +++ b/selfdrive/locationd/ubloxd.cc @@ -17,14 +17,14 @@ int main() { PubMaster pm({"ubloxGnss", "gpsLocationExternal"}); - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "ubloxRaw"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "ubloxRaw")); assert(subscriber != NULL); subscriber->setTimeout(100); while (!do_exit) { - Message * msg = subscriber->receive(); + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -32,7 +32,7 @@ int main() { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); auto ubloxRaw = event.getUbloxRaw(); @@ -58,11 +58,7 @@ int main() { } bytes_consumed += bytes_consumed_this_time; } - delete msg; } - delete subscriber; - delete context; - return 0; } diff --git a/selfdrive/logcatd/logcatd_android.cc b/selfdrive/logcatd/logcatd_android.cc index 1a982a0feb..4452e2f093 100644 --- a/selfdrive/logcatd/logcatd_android.cc +++ b/selfdrive/logcatd/logcatd_android.cc @@ -14,8 +14,10 @@ int main() { ExitHandler do_exit; PubMaster pm({"androidLog"}); - log_time last_log_time = {}; - logger_list *logger_list = android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0); + struct timespec cur_time; + clock_gettime(CLOCK_REALTIME, &cur_time); + log_time last_log_time(cur_time); + logger_list *logger_list = nullptr; while (!do_exit) { // setup android logging diff --git a/selfdrive/logcatd/logcatd_systemd.cc b/selfdrive/logcatd/logcatd_systemd.cc index 7c7800dfea..dabb11adcb 100644 --- a/selfdrive/logcatd/logcatd_systemd.cc +++ b/selfdrive/logcatd/logcatd_systemd.cc @@ -24,6 +24,10 @@ int main(int argc, char *argv[]) { err = sd_journal_seek_tail(journal); assert(err >= 0); + // workaround for bug https://github.com/systemd/systemd/issues/9934 + // call sd_journal_previous_skip after sd_journal_seek_tail (like journalctl -f does) to makes things work. + sd_journal_previous_skip(journal, 1); + while (!do_exit) { err = sd_journal_next(journal); assert(err >= 0); diff --git a/selfdrive/loggerd/bootlog.cc b/selfdrive/loggerd/bootlog.cc index 5209958377..478eb16852 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/selfdrive/loggerd/bootlog.cc @@ -11,6 +11,8 @@ static kj::Array build_boot_log() { if (Hardware::TICI()) { bootlog_commands.push_back("journalctl"); bootlog_commands.push_back("sudo nvme smart-log --output-format=json /dev/nvme0"); + } else if (Hardware::EON()) { + bootlog_commands.push_back("logcat -d"); } MessageBuilder msg; diff --git a/selfdrive/loggerd/config.py b/selfdrive/loggerd/config.py index b626073ce4..6cd20a68ab 100644 --- a/selfdrive/loggerd/config.py +++ b/selfdrive/loggerd/config.py @@ -13,6 +13,13 @@ else: CAMERA_FPS = 20 SEGMENT_LENGTH = 60 +STATS_DIR_FILE_LIMIT = 10000 +STATS_SOCKET = "ipc:///tmp/stats" +if PC: + STATS_DIR = os.path.join(str(Path.home()), ".comma", "stats") +else: + STATS_DIR = "/data/stats/" +STATS_FLUSH_TIME_S = 60 def get_available_percent(default=None): try: diff --git a/selfdrive/loggerd/deleter.py b/selfdrive/loggerd/deleter.py index 16083ec921..e7006a08cf 100644 --- a/selfdrive/loggerd/deleter.py +++ b/selfdrive/loggerd/deleter.py @@ -27,11 +27,11 @@ def deleter_thread(exit_event): continue try: - cloudlog.info("deleting %s" % delete_path) + cloudlog.info(f"deleting {delete_path}") shutil.rmtree(delete_path) break except OSError: - cloudlog.exception("issue deleting %s" % delete_path) + cloudlog.exception(f"issue deleting {delete_path}") exit_event.wait(.1) else: exit_event.wait(30) diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index 40d0a15efd..37f03ef4e5 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -38,7 +38,7 @@ bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id) { } void encoder_thread(LoggerdState *s, const LogCameraInfo &cam_info) { - set_thread_name(cam_info.filename); + util::set_thread_name(cam_info.filename); int cur_seg = -1; int encode_idx = 0; @@ -185,15 +185,14 @@ void loggerd_thread() { } QlogState; std::unordered_map qlog_states; - LoggerdState s; - s.ctx = Context::create(); - Poller * poller = Poller::create(); + std::unique_ptr ctx(Context::create()); + std::unique_ptr poller(Poller::create()); // subscribe to all socks for (const auto& it : services) { if (!it.should_log) continue; - SubSocket * sock = SubSocket::create(s.ctx, it.name); + SubSocket * sock = SubSocket::create(ctx.get(), it.name); assert(sock != NULL); poller->registerSocket(sock); qlog_states[sock] = { @@ -203,6 +202,7 @@ void loggerd_thread() { }; } + LoggerdState s; // init logger logger_init(&s.logger, "rlog", true); logger_rotate(&s); @@ -266,6 +266,4 @@ void loggerd_thread() { // messaging cleanup for (auto &[sock, qs] : qlog_states) delete sock; - delete poller; - delete s.ctx; } diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h index 0101a91a5e..bdf5ef8f9d 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/selfdrive/loggerd/loggerd.h @@ -108,7 +108,6 @@ const LogCameraInfo qcam_info = { }; struct LoggerdState { - Context *ctx; LoggerState logger = {}; char segment_path[4096]; std::mutex rotate_lock; diff --git a/selfdrive/loggerd/main.cc b/selfdrive/loggerd/main.cc index 04da0f67e8..7069aa7068 100644 --- a/selfdrive/loggerd/main.cc +++ b/selfdrive/loggerd/main.cc @@ -7,10 +7,10 @@ int main(int argc, char** argv) { setpriority(PRIO_PROCESS, 0, -20); } else if (Hardware::TICI()) { int ret; - ret = set_core_affinity({0, 1, 2, 3}); + ret = util::set_core_affinity({0, 1, 2, 3}); assert(ret == 0); // TODO: why does this impact camerad timings? - //ret = set_realtime_priority(1); + //ret = util::set_realtime_priority(1); //assert(ret == 0); } diff --git a/selfdrive/loggerd/raw_logger.cc b/selfdrive/loggerd/raw_logger.cc index c490a0813d..b0f9f7a9c8 100644 --- a/selfdrive/loggerd/raw_logger.cc +++ b/selfdrive/loggerd/raw_logger.cc @@ -127,7 +127,7 @@ int RawLogger::encode_frame(const uint8_t *y_ptr, const uint8_t *u_ptr, const ui frame->data[0] = (uint8_t*)y_ptr; frame->data[1] = (uint8_t*)u_ptr; frame->data[2] = (uint8_t*)v_ptr; - frame->pts = ts; + frame->pts = counter; int ret = counter; diff --git a/selfdrive/loggerd/tests/test_deleter.py b/selfdrive/loggerd/tests/test_deleter.py index 89904e694a..e0a063bd43 100644 --- a/selfdrive/loggerd/tests/test_deleter.py +++ b/selfdrive/loggerd/tests/test_deleter.py @@ -18,7 +18,7 @@ class TestDeleter(UploaderTestCase): def setUp(self): self.f_type = "fcamera.hevc" - super(TestDeleter, self).setUp() + super().setUp() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) deleter.os.statvfs = self.fake_statvfs deleter.ROOT = self.root diff --git a/selfdrive/loggerd/tests/test_uploader.py b/selfdrive/loggerd/tests/test_uploader.py index 8562f5f21d..54c65db73d 100755 --- a/selfdrive/loggerd/tests/test_uploader.py +++ b/selfdrive/loggerd/tests/test_uploader.py @@ -39,7 +39,7 @@ cloudlog.addHandler(log_handler) class TestUploader(UploaderTestCase): def setUp(self): - super(TestUploader, self).setUp() + super().setUp() log_handler.reset() def start_thread(self): diff --git a/selfdrive/loggerd/tools/mark_unuploaded.py b/selfdrive/loggerd/tools/mark_unuploaded.py index e219c3a53a..0d3f765af4 100755 --- a/selfdrive/loggerd/tools/mark_unuploaded.py +++ b/selfdrive/loggerd/tools/mark_unuploaded.py @@ -4,5 +4,5 @@ from common.xattr import removexattr from selfdrive.loggerd.uploader import UPLOAD_ATTR_NAME for fn in sys.argv[1:]: - print("unmarking %s" % fn) + print(f"unmarking {fn}") removexattr(fn, UPLOAD_ATTR_NAME) diff --git a/selfdrive/loggerd/uploader.py b/selfdrive/loggerd/uploader.py index 10bf218b03..c75b34e9cb 100644 --- a/selfdrive/loggerd/uploader.py +++ b/selfdrive/loggerd/uploader.py @@ -140,7 +140,7 @@ class Uploader(): cloudlog.debug("upload_url v1.4 %s %s", url, str(headers)) if fake_upload: - cloudlog.debug("*** WARNING, THIS IS A FAKE UPLOAD TO %s ***" % url) + cloudlog.debug(f"*** WARNING, THIS IS A FAKE UPLOAD TO {url} ***") class FakeResponse(): def __init__(self): diff --git a/selfdrive/logmessaged.py b/selfdrive/logmessaged.py index 9c40976562..1d1fab5162 100755 --- a/selfdrive/logmessaged.py +++ b/selfdrive/logmessaged.py @@ -17,7 +17,8 @@ def main() -> NoReturn: sock.bind("ipc:///tmp/logmessage") # and we publish them - pub_sock = messaging.pub_sock('logMessage') + log_message_sock = messaging.pub_sock('logMessage') + error_log_message_sock = messaging.pub_sock('errorLogMessage') while True: dat = b''.join(sock.recv_multipart()) @@ -29,7 +30,12 @@ def main() -> NoReturn: # then we publish them msg = messaging.new_message() msg.logMessage = record - pub_sock.send(msg.to_bytes()) + log_message_sock.send(msg.to_bytes()) + + if level >= 40: # logging.ERROR + msg = messaging.new_message() + msg.errorLogMessage = record + error_log_message_sock.send(msg.to_bytes()) if __name__ == "__main__": diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 078f18fd29..4b97855f34 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -12,7 +12,7 @@ from common.spinner import Spinner from common.text_window import TextWindow from selfdrive.hardware import TICI from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import get_dirty +from selfdrive.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if TICI else "/tmp/scons_cache") @@ -22,7 +22,7 @@ MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -def build(spinner, dirty=False): +def build(spinner: Spinner, dirty: bool = False) -> None: env = os.environ.copy() env['SCONS_PROGRESS'] = "1" nproc = os.cpu_count() @@ -30,6 +30,7 @@ def build(spinner, dirty=False): for retry in [True, False]: scons = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) + assert scons.stderr is not None compile_output = [] @@ -98,4 +99,4 @@ def build(spinner, dirty=False): if __name__ == "__main__" and not PREBUILT: spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, get_dirty()) + build(spinner, is_dirty()) diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index fdda0deb9a..b07362dd4d 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -5,7 +5,7 @@ import errno import signal -def unblock_stdout(): +def unblock_stdout() -> None: # get a non-blocking stdout child_pid, child_pty = os.forkpty() if child_pid != 0: # parent @@ -29,7 +29,7 @@ def unblock_stdout(): try: sys.stdout.write(dat.decode('utf8')) - except (OSError, IOError, UnicodeDecodeError): + except (OSError, UnicodeDecodeError): pass # os.wait() returns a tuple with the pid and a 16 bit value diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 386876b692..76106e473f 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -5,6 +5,7 @@ import signal import subprocess import sys import traceback +from typing import List, Tuple, Union import cereal.messaging as messaging import selfdrive.crash as crash @@ -18,14 +19,14 @@ from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import get_dirty, get_commit, get_version, get_origin, get_short_branch, \ - terms_version, training_version, get_comma_remote +from selfdrive.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ + terms_version, training_version, is_comma_remote sys.path.append(os.path.join(BASEDIR, "pyextra")) -def manager_init(): +def manager_init() -> None: # update system time from panda set_time(cloudlog) @@ -35,7 +36,7 @@ def manager_init(): params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) - default_params = [ + default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), ("HasAcceptedTerms", "0"), ("OpenpilotEnabledToggle", "1"), @@ -56,7 +57,7 @@ def manager_init(): # is this dashcam? if os.getenv("PASSIVE") is not None: - params.put_bool("Passive", bool(int(os.getenv("PASSIVE")))) + params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) if params.get("Passive") is None: raise Exception("Passive must be set to continue") @@ -86,25 +87,25 @@ def manager_init(): raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog - if not get_dirty(): + if not is_dirty(): os.environ['CLEAN'] = '1' - cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty(), + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty(), device=HARDWARE.get_device_type()) - if get_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): + if is_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): crash.init() crash.bind_user(id=dongle_id) - crash.bind_extra(dirty=get_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), + crash.bind_extra(dirty=is_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), device=HARDWARE.get_device_type()) -def manager_prepare(): +def manager_prepare() -> None: for p in managed_processes.values(): p.prepare() -def manager_cleanup(): +def manager_cleanup() -> None: # send signals to kill all procs for p in managed_processes.values(): p.stop(block=False) @@ -116,7 +117,8 @@ def manager_cleanup(): cloudlog.info("everything is dead") -def manager_thread(): +def manager_thread() -> None: + cloudlog.bind(daemon="manager") cloudlog.info("manager start") cloudlog.info({"environ": os.environ}) @@ -127,8 +129,7 @@ def manager_thread(): ignore += ["manage_athenad", "uploader"] if os.getenv("NOBOARD") is not None: ignore.append("pandad") - if os.getenv("BLOCK") is not None: - ignore += os.getenv("BLOCK").split(",") + ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] ensure_running(managed_processes.values(), started=False, not_run=ignore) @@ -175,7 +176,7 @@ def manager_thread(): break -def main(): +def main() -> None: prepare_only = os.getenv("PREPAREONLY") is not None manager_init() diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index d9a1619411..2dcb42f339 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -4,6 +4,7 @@ import signal import struct import time import subprocess +from typing import Optional, List, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process @@ -22,7 +23,7 @@ WATCHDOG_FN = "/dev/shm/wd_" ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None -def launcher(proc): +def launcher(proc: str, name: str) -> None: try: # import the process mod = importlib.import_module(proc) @@ -33,10 +34,13 @@ def launcher(proc): # create new context since we forked messaging.context = messaging.Context() + # add daemon name to cloudlog ctx + cloudlog.bind(daemon=name) + # exec the process - mod.main() + getattr(mod, 'main')() except KeyboardInterrupt: - cloudlog.warning("child %s got SIGINT" % proc) + cloudlog.warning(f"child {proc} got SIGINT") except Exception: # can't install the crash handler because sys.excepthook doesn't play nice # with threads, so catch it here. @@ -44,13 +48,13 @@ def launcher(proc): raise -def nativelauncher(pargs, cwd): +def nativelauncher(pargs: List[str], cwd: str) -> None: # exec the process os.chdir(cwd) os.execvp(pargs[0], pargs) -def join_process(process, timeout): +def join_process(process: Process, timeout: float) -> None: # Process().join(timeout) will hang due to a python 3 bug: https://bugs.python.org/issue28382 # We have to poll the exitcode instead t = time.monotonic() @@ -62,7 +66,9 @@ class ManagerProcess(ABC): unkillable = False daemon = False sigkill = False - proc = None + persistent = False + driverview = False + proc: Optional[Process] = None enabled = True name = "" @@ -72,24 +78,25 @@ class ManagerProcess(ABC): shutting_down = False @abstractmethod - def prepare(self): + def prepare(self) -> None: pass @abstractmethod - def start(self): + def start(self) -> None: pass - def restart(self): + def restart(self) -> None: self.stop() self.start() - def check_watchdog(self, started): + def check_watchdog(self, started: bool) -> None: if self.watchdog_max_dt is None or self.proc is None: return try: fn = WATCHDOG_FN + str(self.proc.pid) - self.last_watchdog_time = struct.unpack('Q', open(fn, "rb").read())[0] + # TODO: why can't pylint find struct.unpack? + self.last_watchdog_time = struct.unpack('Q', open(fn, "rb").read())[0] # pylint: disable=no-member except Exception: pass @@ -103,9 +110,9 @@ class ManagerProcess(ABC): else: self.watchdog_seen = True - def stop(self, retry=True, block=True): + def stop(self, retry: bool=True, block: bool=True) -> Optional[int]: if self.proc is None: - return + return None if self.proc.exitcode is None: if not self.shutting_down: @@ -115,7 +122,7 @@ class ManagerProcess(ABC): self.shutting_down = True if not block: - return + return None join_process(self.proc, 5) @@ -145,7 +152,7 @@ class ManagerProcess(ABC): return ret - def signal(self, sig): + def signal(self, sig: int) -> None: if self.proc is None: return @@ -153,6 +160,10 @@ class ManagerProcess(ABC): if self.proc.exitcode is not None and self.proc.pid is not None: return + # Can't signal if we don't have a pid + if self.proc.pid is None: + return + cloudlog.info(f"sending signal {sig} to {self.name}") os.kill(self.proc.pid, sig) @@ -179,10 +190,10 @@ class NativeProcess(ManagerProcess): self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt - def prepare(self): + def prepare(self) -> None: pass - def start(self): + def start(self) -> None: # In case we only tried a non blocking stop we need to stop it before restarting if self.shutting_down: self.stop() @@ -191,7 +202,7 @@ class NativeProcess(ManagerProcess): return cwd = os.path.join(BASEDIR, self.cwd) - cloudlog.info("starting process %s" % self.name) + cloudlog.info(f"starting process {self.name}") self.proc = Process(name=self.name, target=nativelauncher, args=(self.cmdline, cwd)) self.proc.start() self.watchdog_seen = False @@ -209,12 +220,12 @@ class PythonProcess(ManagerProcess): self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt - def prepare(self): + def prepare(self) -> None: if self.enabled: - cloudlog.info("preimporting %s" % self.module) + cloudlog.info(f"preimporting {self.module}") importlib.import_module(self.module) - def start(self): + def start(self) -> None: # In case we only tried a non blocking stop we need to stop it before restarting if self.shutting_down: self.stop() @@ -222,8 +233,8 @@ class PythonProcess(ManagerProcess): if self.proc is not None: return - cloudlog.info("starting python %s" % self.module) - self.proc = Process(name=self.name, target=launcher, args=(self.module,)) + cloudlog.info(f"starting python {self.module}") + self.proc = Process(name=self.name, target=launcher, args=(self.module, self.name)) self.proc.start() self.watchdog_seen = False self.shutting_down = False @@ -239,10 +250,10 @@ class DaemonProcess(ManagerProcess): self.enabled = enabled self.persistent = True - def prepare(self): + def prepare(self) -> None: pass - def start(self): + def start(self) -> None: params = Params() pid = params.get(self.param_name, encoding='utf-8') @@ -257,20 +268,20 @@ class DaemonProcess(ManagerProcess): # process is dead pass - cloudlog.info("starting daemon %s" % self.name) + cloudlog.info(f"starting daemon {self.name}") proc = subprocess.Popen(['python', '-m', self.module], # pylint: disable=subprocess-popen-preexec-fn - stdin=open('/dev/null', 'r'), + stdin=open('/dev/null'), stdout=open('/dev/null', 'w'), stderr=open('/dev/null', 'w'), preexec_fn=os.setpgrp) params.put(self.param_name, str(proc.pid)) - def stop(self, retry=True, block=True): + def stop(self, retry=True, block=True) -> None: pass -def ensure_running(procs, started, driverview=False, not_run=None): +def ensure_running(procs: ValuesView[ManagerProcess], started: bool, driverview: bool=False, not_run: Optional[List[str]]=None) -> None: if not_run is None: not_run = [] diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 2e3741350e..b5fc01364e 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -36,6 +36,7 @@ procs = [ PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, persistent=True), PythonProcess("updated", "selfdrive.updated", enabled=not PC, persistent=True), PythonProcess("uploader", "selfdrive.loggerd.uploader", persistent=True), + PythonProcess("statsd", "selfdrive.statsd", persistent=True), # EON only PythonProcess("rtshield", "selfdrive.rtshield", enabled=EON), diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 9013e63617..0facf3fa0b 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -14,64 +14,46 @@ #include "selfdrive/modeld/models/driving.h" ExitHandler do_exit; -// globals -bool live_calib_seen; -mat3 cur_transform; -std::mutex transform_lock; - -void calibration_thread(bool wide_camera) { - set_thread_name("calibration"); - set_realtime_priority(50); - - SubMaster sm({"liveCalibration"}); +mat3 update_calibration(cereal::LiveCalibrationData::Reader live_calib, bool wide_camera) { /* import numpy as np from common.transformations.model import medmodel_frame_from_road_frame medmodel_frame_from_ground = medmodel_frame_from_road_frame[:, (0, 1, 3)] ground_from_medmodel_frame = np.linalg.inv(medmodel_frame_from_ground) */ - Eigen::Matrix ground_from_medmodel_frame; - ground_from_medmodel_frame << + static const auto ground_from_medmodel_frame = (Eigen::Matrix() << 0.00000000e+00, 0.00000000e+00, 1.00000000e+00, -1.09890110e-03, 0.00000000e+00, 2.81318681e-01, - -1.84808520e-20, 9.00738606e-04,-4.28751576e-02; + -1.84808520e-20, 9.00738606e-04, -4.28751576e-02).finished(); - Eigen::Matrix cam_intrinsics = Eigen::Matrix(wide_camera ? ecam_intrinsic_matrix.v : fcam_intrinsic_matrix.v); - const mat3 yuv_transform = get_model_yuv_transform(); + static const auto cam_intrinsics = Eigen::Matrix(wide_camera ? ecam_intrinsic_matrix.v : fcam_intrinsic_matrix.v); + static const mat3 yuv_transform = get_model_yuv_transform(); - while (!do_exit) { - sm.update(100); - if(sm.updated("liveCalibration")) { - auto extrinsic_matrix = sm["liveCalibration"].getLiveCalibration().getExtrinsicMatrix(); - Eigen::Matrix extrinsic_matrix_eigen; - for (int i = 0; i < 4*3; i++) { - extrinsic_matrix_eigen(i / 4, i % 4) = extrinsic_matrix[i]; - } - - auto camera_frame_from_road_frame = cam_intrinsics * extrinsic_matrix_eigen; - Eigen::Matrix camera_frame_from_ground; - camera_frame_from_ground.col(0) = camera_frame_from_road_frame.col(0); - camera_frame_from_ground.col(1) = camera_frame_from_road_frame.col(1); - camera_frame_from_ground.col(2) = camera_frame_from_road_frame.col(3); - - auto warp_matrix = camera_frame_from_ground * ground_from_medmodel_frame; - mat3 transform = {}; - for (int i=0; i<3*3; i++) { - transform.v[i] = warp_matrix(i / 3, i % 3); - } - mat3 model_transform = matmul3(yuv_transform, transform); - std::lock_guard lk(transform_lock); - cur_transform = model_transform; - live_calib_seen = true; - } + auto extrinsic_matrix = live_calib.getExtrinsicMatrix(); + Eigen::Matrix extrinsic_matrix_eigen; + for (int i = 0; i < 4*3; i++) { + extrinsic_matrix_eigen(i / 4, i % 4) = extrinsic_matrix[i]; } + + auto camera_frame_from_road_frame = cam_intrinsics * extrinsic_matrix_eigen; + Eigen::Matrix camera_frame_from_ground; + camera_frame_from_ground.col(0) = camera_frame_from_road_frame.col(0); + camera_frame_from_ground.col(1) = camera_frame_from_road_frame.col(1); + camera_frame_from_ground.col(2) = camera_frame_from_road_frame.col(3); + + auto warp_matrix = camera_frame_from_ground * ground_from_medmodel_frame; + mat3 transform = {}; + for (int i=0; i<3*3; i++) { + transform.v[i] = warp_matrix(i / 3, i % 3); + } + return matmul3(yuv_transform, transform); } -void run_model(ModelState &model, VisionIpcClient &vipc_client) { +void run_model(ModelState &model, VisionIpcClient &vipc_client, bool wide_camera) { // messaging PubMaster pm({"modelV2", "cameraOdometry"}); - SubMaster sm({"lateralPlan", "roadCameraState"}); + SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration"}); // setup filter to track dropped frames FirstOrderFilter frame_dropped_filter(0., 10., 1. / MODEL_FREQ); @@ -80,70 +62,66 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { double last = 0; uint32_t run_count = 0; + mat3 model_transform = {}; + bool live_calib_seen = false; + while (!do_exit) { VisionIpcBufExtra extra = {}; VisionBuf *buf = vipc_client.recv(&extra); if (buf == nullptr) continue; - transform_lock.lock(); - mat3 model_transform = cur_transform; - const bool run_model_this_iter = live_calib_seen; - transform_lock.unlock(); - // TODO: path planner timeout? sm.update(0); int desire = ((int)sm["lateralPlan"].getLateralPlan().getDesire()); frame_id = sm["roadCameraState"].getRoadCameraState().getFrameId(); + if (sm.updated("liveCalibration")) { + model_transform = update_calibration(sm["liveCalibration"].getLiveCalibration(), wide_camera); + live_calib_seen = true; + } + + float vec_desire[DESIRE_LEN] = {0}; + if (desire >= 0 && desire < DESIRE_LEN) { + vec_desire[desire] = 1.0; + } - if (run_model_this_iter) { - run_count++; - - float vec_desire[DESIRE_LEN] = {0}; - if (desire >= 0 && desire < DESIRE_LEN) { - vec_desire[desire] = 1.0; - } - - double mt1 = millis_since_boot(); - ModelDataRaw model_buf = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, - model_transform, vec_desire); - double mt2 = millis_since_boot(); - float model_execution_time = (mt2 - mt1) / 1000.0; - - // tracked dropped frames - uint32_t vipc_dropped_frames = extra.frame_id - last_vipc_frame_id - 1; - float frames_dropped = frame_dropped_filter.update((float)std::min(vipc_dropped_frames, 10U)); - if (run_count < 10) { // let frame drops warm up - frame_dropped_filter.reset(0); - frames_dropped = 0.; - } - - float frame_drop_ratio = frames_dropped / (1 + frames_dropped); - - model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, model_buf, extra.timestamp_eof, model_execution_time, - kj::ArrayPtr(model.output.data(), model.output.size())); - posenet_publish(pm, extra.frame_id, vipc_dropped_frames, model_buf, extra.timestamp_eof); - - //printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); - last = mt1; - last_vipc_frame_id = extra.frame_id; + double mt1 = millis_since_boot(); + ModelOutput *model_output = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, + model_transform, vec_desire); + double mt2 = millis_since_boot(); + float model_execution_time = (mt2 - mt1) / 1000.0; + + // tracked dropped frames + uint32_t vipc_dropped_frames = extra.frame_id - last_vipc_frame_id - 1; + float frames_dropped = frame_dropped_filter.update((float)std::min(vipc_dropped_frames, 10U)); + if (run_count < 10) { // let frame drops warm up + frame_dropped_filter.reset(0); + frames_dropped = 0.; } + run_count++; + + float frame_drop_ratio = frames_dropped / (1 + frames_dropped); + + model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, *model_output, extra.timestamp_eof, model_execution_time, + kj::ArrayPtr(model.output.data(), model.output.size()), live_calib_seen); + posenet_publish(pm, extra.frame_id, vipc_dropped_frames, *model_output, extra.timestamp_eof, live_calib_seen); + + //printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); + last = mt1; + last_vipc_frame_id = extra.frame_id; } } int main(int argc, char **argv) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(54); + ret = util::set_realtime_priority(54); assert(ret == 0); - set_core_affinity({Hardware::EON() ? 2 : 7}); + util::set_core_affinity({Hardware::EON() ? 2 : 7}); assert(ret == 0); } bool wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; - // start calibration thread - std::thread thread = std::thread(calibration_thread, wide_camera); - // cl init cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); cl_context context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); @@ -163,12 +141,10 @@ int main(int argc, char **argv) { if (vipc_client.connected) { const VisionBuf *b = &vipc_client.buffers[0]; LOGW("connected with buffer size: %d (%d x %d)", b->len, b->width, b->height); - run_model(model, vipc_client); + run_model(model, vipc_client, wide_camera); } model_free(&model); - LOG("joining calibration thread"); - thread.join(); CL_CHECK(clReleaseContext(context)); return 0; } diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index d7904489b1..19af75eef4 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -16,10 +16,6 @@ #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" -constexpr int MODEL_WIDTH = 512; -constexpr int MODEL_HEIGHT = 256; -constexpr int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); @@ -27,14 +23,17 @@ float softplus(float input); float sigmoid(float input); class ModelFrame { - public: +public: ModelFrame(cl_device_id device_id, cl_context context); ~ModelFrame(); float* prepare(cl_mem yuv_cl, int width, int height, const mat3& transform, cl_mem *output); + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; const int buf_size = MODEL_FRAME_SIZE * 2; - private: +private: Transform transform; LoadYUVState loadyuv; cl_command_queue q; diff --git a/selfdrive/modeld/models/dmonitoring.cc b/selfdrive/modeld/models/dmonitoring.cc index 9d87e320af..2acdf7d2b7 100644 --- a/selfdrive/modeld/models/dmonitoring.cc +++ b/selfdrive/modeld/models/dmonitoring.cc @@ -3,15 +3,15 @@ #include "libyuv.h" #include "selfdrive/common/mat.h" +#include "selfdrive/common/modeldata.h" #include "selfdrive/common/params.h" #include "selfdrive/common/timing.h" #include "selfdrive/hardware/hw.h" #include "selfdrive/modeld/models/dmonitoring.h" -#define MODEL_WIDTH 320 -#define MODEL_HEIGHT 640 -#define FULL_W 852 // should get these numbers from camerad +constexpr int MODEL_WIDTH = 320; +constexpr int MODEL_HEIGHT = 640; template static inline T *get_buffer(std::vector &buf, const size_t size) { @@ -67,21 +67,15 @@ void crop_yuv(uint8_t *raw, int width, int height, uint8_t *y, uint8_t *u, uint8 DMonitoringResult dmonitoring_eval_frame(DMonitoringModelState* s, void* stream_buf, int width, int height) { Rect crop_rect; - if (Hardware::TICI()) { - const int full_width_tici = 1928; - const int full_height_tici = 1208; - const int adapt_width_tici = 954; - const int x_offset_tici = -72; - const int y_offset_tici = -144; - const int cropped_height = adapt_width_tici / 1.33; - crop_rect = {full_width_tici / 2 - adapt_width_tici / 2 + x_offset_tici, - full_height_tici / 2 - cropped_height / 2 + y_offset_tici, + if (width == TICI_CAM_WIDTH) { + const int cropped_height = tici_dm_crop::width / 1.33; + crop_rect = {width / 2 - tici_dm_crop::width / 2 + tici_dm_crop::x_offset, + height / 2 - cropped_height / 2 + tici_dm_crop::y_offset, cropped_height / 2, cropped_height}; if (!s->is_rhd) { - crop_rect.x += adapt_width_tici - crop_rect.w; + crop_rect.x += tici_dm_crop::width - crop_rect.w; } - } else { const int adapt_width = 372; crop_rect = {0, 0, adapt_width, height}; diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index a2ff9e811f..69aeb91be1 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -16,8 +16,8 @@ constexpr float FCW_THRESHOLD_5MS2_HIGH = 0.15; constexpr float FCW_THRESHOLD_5MS2_LOW = 0.05; constexpr float FCW_THRESHOLD_3MS2 = 0.7; -float prev_brake_5ms2_probs[5] = {0,0,0,0,0}; -float prev_brake_3ms2_probs[3] = {0,0,0}; +std::array prev_brake_5ms2_probs = {0,0,0,0,0}; +std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV @@ -52,7 +52,7 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #endif } -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput* model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in) { #ifdef DESIRE if (desire_in != NULL) { @@ -69,35 +69,18 @@ ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int heigh } #endif - //for (int i = 0; i < NET_OUTPUT_SIZE; i++) { printf("%f ", s->output[i]); } printf("\n"); - // if getInputBuf is not NULL, net_input_buf will be auto net_input_buf = s->frame->prepare(yuv_cl, width, height, transform, static_cast(s->m->getInputBuf())); s->m->execute(net_input_buf, s->frame->buf_size); - // net outputs - ModelDataRaw net_outputs { - .plans = (ModelDataRawPlans*)&s->output[PLAN_IDX], - .lane_lines = (ModelDataRawLaneLines*)&s->output[LL_IDX], - .road_edges = (ModelDataRawRoadEdges*)&s->output[RE_IDX], - .leads = (ModelDataRawLeads*)&s->output[LEAD_IDX], - .meta = &s->output[DESIRE_STATE_IDX], - .pose = (ModelDataRawPose*)&s->output[POSE_IDX], - }; - return net_outputs; + return (ModelOutput*)&s->output; } void model_free(ModelState* s) { delete s->frame; } -void fill_sigmoid(const float *input, float *output, int len, int stride) { - for (int i=0; i lead_t = {0.0, 2.0, 4.0, 6.0, 8.0, 10.0}; const auto &best_prediction = leads.get_best_prediction(t_idx); lead.setProb(sigmoid(leads.prob[t_idx])); @@ -125,56 +108,54 @@ void fill_lead(cereal::ModelDataV2::LeadDataV3::Builder lead, const ModelDataRaw lead.setAStd(to_kj_array_ptr(lead_a_std)); } -void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const float *meta_data) { - float desire_state_softmax[DESIRE_LEN]; - float desire_pred_softmax[4*DESIRE_LEN]; - softmax(&meta_data[0], desire_state_softmax, DESIRE_LEN); - for (int i=0; i<4; i++) { - softmax(&meta_data[DESIRE_LEN + OTHER_META_SIZE + i*DESIRE_LEN], - &desire_pred_softmax[i*DESIRE_LEN], DESIRE_LEN); +void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMeta &meta_data) { + std::array desire_state_softmax; + softmax(meta_data.desire_state_prob.array.data(), desire_state_softmax.data(), DESIRE_LEN); + + std::array desire_pred_softmax; + for (int i=0; i lat_long_t = {2,4,6,8,10}; + std::array gas_disengage_sigmoid, brake_disengage_sigmoid, steer_override_sigmoid, + brake_3ms2_sigmoid, brake_4ms2_sigmoid, brake_5ms2_sigmoid; + for (int i=0; i threshold; } - for (int i=0; i<3; i++) { + for (int i=0; i FCW_THRESHOLD_3MS2; } auto disengage = meta.initDisengagePredictions(); - disengage.setT({2,4,6,8,10}); - disengage.setGasDisengageProbs(gas_disengage_sigmoid); - disengage.setBrakeDisengageProbs(brake_disengage_sigmoid); - disengage.setSteerOverrideProbs(steer_override_sigmoid); - disengage.setBrake3MetersPerSecondSquaredProbs(brake_3ms2_sigmoid); - disengage.setBrake4MetersPerSecondSquaredProbs(brake_4ms2_sigmoid); - disengage.setBrake5MetersPerSecondSquaredProbs(brake_5ms2_sigmoid); - - meta.setEngagedProb(sigmoid(meta_data[DESIRE_LEN])); - meta.setDesirePrediction(desire_pred_softmax); - meta.setDesireState(desire_state_softmax); + disengage.setT(to_kj_array_ptr(lat_long_t)); + disengage.setGasDisengageProbs(to_kj_array_ptr(gas_disengage_sigmoid)); + disengage.setBrakeDisengageProbs(to_kj_array_ptr(brake_disengage_sigmoid)); + disengage.setSteerOverrideProbs(to_kj_array_ptr(steer_override_sigmoid)); + disengage.setBrake3MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_3ms2_sigmoid)); + disengage.setBrake4MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_4ms2_sigmoid)); + disengage.setBrake5MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_5ms2_sigmoid)); + + meta.setEngagedProb(sigmoid(meta_data.engaged_prob)); + meta.setDesirePrediction(to_kj_array_ptr(desire_pred_softmax)); + meta.setDesireState(to_kj_array_ptr(desire_state_softmax)); meta.setHardBrakePredicted(above_fcw_threshold); } @@ -197,7 +178,7 @@ void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array pos_x, pos_y, pos_z; std::array pos_x_std, pos_y_std, pos_z_std; std::array vel_x, vel_y, vel_z; @@ -229,7 +210,7 @@ void fill_plan(cereal::ModelDataV2::Builder &framed, const ModelDataRawPlanPredi } void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawLaneLines &lanes) { + const ModelOutputLaneLines &lanes) { std::array left_far_y, left_far_z; std::array left_near_y, left_near_z; std::array right_near_y, right_near_z; @@ -267,7 +248,7 @@ void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawRoadEdges &edges) { + const ModelOutputRoadEdges &edges) { std::array left_y, left_z; std::array right_y, right_z; for (int j=0; jget_best_prediction(); +void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_outputs) { + const auto &best_plan = net_outputs.plans.get_best_prediction(); std::array plan_t; std::fill_n(plan_t.data(), plan_t.size(), NAN); plan_t[0] = 0.0; @@ -311,8 +292,8 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou } fill_plan(framed, best_plan); - fill_lane_lines(framed, plan_t, *net_outputs.lane_lines); - fill_road_edges(framed, plan_t, *net_outputs.road_edges); + fill_lane_lines(framed, plan_t, net_outputs.lane_lines); + fill_road_edges(framed, plan_t, net_outputs.road_edges); // meta fill_meta(framed.initMeta(), net_outputs.meta); @@ -321,16 +302,16 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou auto leads = framed.initLeadsV3(LEAD_MHP_SELECTION); std::array t_offsets = {0.0, 2.0, 4.0}; for (int i=0; i raw_pred) { + const ModelOutput &net_outputs, uint64_t timestamp_eof, + float model_execution_time, kj::ArrayPtr raw_pred, const bool valid) { const uint32_t frame_age = (frame_id > vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; MessageBuilder msg; - auto framed = msg.initEvent().initModelV2(); + auto framed = msg.initEvent(valid).initModelV2(); framed.setFrameId(vipc_frame_id); framed.setFrameAge(frame_age); framed.setFrameDropPerc(frame_drop * 100); @@ -344,14 +325,14 @@ void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, flo } void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof) { + const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid) { MessageBuilder msg; - auto v_mean = net_outputs.pose->velocity_mean; - auto r_mean = net_outputs.pose->rotation_mean; - auto v_std = net_outputs.pose->velocity_std; - auto r_std = net_outputs.pose->rotation_std; + const auto &v_mean = net_outputs.pose.velocity_mean; + const auto &r_mean = net_outputs.pose.rotation_mean; + const auto &v_std = net_outputs.pose.velocity_std; + const auto &r_std = net_outputs.pose.rotation_std; - auto posenetd = msg.initEvent(vipc_dropped_frames < 1).initCameraOdometry(); + auto posenetd = msg.initEvent(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); posenetd.setRot({r_mean.x, r_mean.y, r_mean.z}); posenetd.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)}); diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index c6d3e00acc..14645dcfaa 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -16,78 +16,54 @@ #include "selfdrive/modeld/runners/run.h" constexpr int DESIRE_LEN = 8; +constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; constexpr int MODEL_FREQ = 20; -constexpr int DESIRE_PRED_SIZE = 32; -constexpr int OTHER_META_SIZE = 48; -constexpr int NUM_META_INTERVALS = 5; +constexpr int DISENGAGE_LEN = 5; +constexpr int BLINKER_LEN = 6; constexpr int META_STRIDE = 7; constexpr int PLAN_MHP_N = 5; -constexpr int PLAN_MHP_VALS = 15*33; -constexpr int PLAN_MHP_SELECTION = 1; -constexpr int PLAN_MHP_GROUP_SIZE = (2*PLAN_MHP_VALS + PLAN_MHP_SELECTION); constexpr int LEAD_MHP_N = 2; constexpr int LEAD_TRAJ_LEN = 6; constexpr int LEAD_PRED_DIM = 4; -constexpr int LEAD_MHP_VALS = LEAD_PRED_DIM*LEAD_TRAJ_LEN; constexpr int LEAD_MHP_SELECTION = 3; -constexpr int LEAD_MHP_GROUP_SIZE = (2*LEAD_MHP_VALS + LEAD_MHP_SELECTION); - -constexpr int POSE_SIZE = 12; - -constexpr int PLAN_IDX = 0; -constexpr int LL_IDX = PLAN_IDX + PLAN_MHP_N*PLAN_MHP_GROUP_SIZE; -constexpr int LL_PROB_IDX = LL_IDX + 4*2*2*33; -constexpr int RE_IDX = LL_PROB_IDX + 8; -constexpr int LEAD_IDX = RE_IDX + 2*2*2*33; -constexpr int LEAD_PROB_IDX = LEAD_IDX + LEAD_MHP_N*(LEAD_MHP_GROUP_SIZE); -constexpr int DESIRE_STATE_IDX = LEAD_PROB_IDX + 3; -constexpr int META_IDX = DESIRE_STATE_IDX + DESIRE_LEN; -constexpr int POSE_IDX = META_IDX + OTHER_META_SIZE + DESIRE_PRED_SIZE; -constexpr int OUTPUT_SIZE = POSE_IDX + POSE_SIZE; -#ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = 512; -#else - constexpr int TEMPORAL_SIZE = 0; -#endif -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; -struct ModelDataRawXYZ { +struct ModelOutputXYZ { float x; float y; float z; }; -static_assert(sizeof(ModelDataRawXYZ) == sizeof(float)*3); +static_assert(sizeof(ModelOutputXYZ) == sizeof(float)*3); -struct ModelDataRawYZ { +struct ModelOutputYZ { float y; float z; }; -static_assert(sizeof(ModelDataRawYZ) == sizeof(float)*2); +static_assert(sizeof(ModelOutputYZ) == sizeof(float)*2); -struct ModelDataRawPlanElement { - ModelDataRawXYZ position; - ModelDataRawXYZ velocity; - ModelDataRawXYZ acceleration; - ModelDataRawXYZ rotation; - ModelDataRawXYZ rotation_rate; +struct ModelOutputPlanElement { + ModelOutputXYZ position; + ModelOutputXYZ velocity; + ModelOutputXYZ acceleration; + ModelOutputXYZ rotation; + ModelOutputXYZ rotation_rate; }; -static_assert(sizeof(ModelDataRawPlanElement) == sizeof(ModelDataRawXYZ)*5); +static_assert(sizeof(ModelOutputPlanElement) == sizeof(ModelOutputXYZ)*5); -struct ModelDataRawPlanPrediction { - std::array mean; - std::array std; +struct ModelOutputPlanPrediction { + std::array mean; + std::array std; float prob; }; -static_assert(sizeof(ModelDataRawPlanPrediction) == (sizeof(ModelDataRawPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); +static_assert(sizeof(ModelOutputPlanPrediction) == (sizeof(ModelOutputPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); -struct ModelDataRawPlans { - std::array prediction; +struct ModelOutputPlans { + std::array prediction; - constexpr const ModelDataRawPlanPrediction &get_best_prediction() const { + constexpr const ModelOutputPlanPrediction &get_best_prediction() const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob > prediction[max_idx].prob) { @@ -97,69 +73,69 @@ struct ModelDataRawPlans { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawPlans) == sizeof(ModelDataRawPlanPrediction)*PLAN_MHP_N); +static_assert(sizeof(ModelOutputPlans) == sizeof(ModelOutputPlanPrediction)*PLAN_MHP_N); -struct ModelDataRawLinesXY { - std::array left_far; - std::array left_near; - std::array right_near; - std::array right_far; +struct ModelOutputLinesXY { + std::array left_far; + std::array left_near; + std::array right_near; + std::array right_far; }; -static_assert(sizeof(ModelDataRawLinesXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*4); +static_assert(sizeof(ModelOutputLinesXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*4); -struct ModelDataRawLineProbVal { +struct ModelOutputLineProbVal { float val_deprecated; float val; }; -static_assert(sizeof(ModelDataRawLineProbVal) == sizeof(float)*2); +static_assert(sizeof(ModelOutputLineProbVal) == sizeof(float)*2); -struct ModelDataRawLinesProb { - ModelDataRawLineProbVal left_far; - ModelDataRawLineProbVal left_near; - ModelDataRawLineProbVal right_near; - ModelDataRawLineProbVal right_far; +struct ModelOutputLinesProb { + ModelOutputLineProbVal left_far; + ModelOutputLineProbVal left_near; + ModelOutputLineProbVal right_near; + ModelOutputLineProbVal right_far; }; -static_assert(sizeof(ModelDataRawLinesProb) == sizeof(ModelDataRawLineProbVal)*4); +static_assert(sizeof(ModelOutputLinesProb) == sizeof(ModelOutputLineProbVal)*4); -struct ModelDataRawLaneLines { - ModelDataRawLinesXY mean; - ModelDataRawLinesXY std; - ModelDataRawLinesProb prob; +struct ModelOutputLaneLines { + ModelOutputLinesXY mean; + ModelOutputLinesXY std; + ModelOutputLinesProb prob; }; -static_assert(sizeof(ModelDataRawLaneLines) == (sizeof(ModelDataRawLinesXY)*2) + sizeof(ModelDataRawLinesProb)); +static_assert(sizeof(ModelOutputLaneLines) == (sizeof(ModelOutputLinesXY)*2) + sizeof(ModelOutputLinesProb)); -struct ModelDataRawEdgessXY { - std::array left; - std::array right; +struct ModelOutputEdgessXY { + std::array left; + std::array right; }; -static_assert(sizeof(ModelDataRawEdgessXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*2); +static_assert(sizeof(ModelOutputEdgessXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*2); -struct ModelDataRawRoadEdges { - ModelDataRawEdgessXY mean; - ModelDataRawEdgessXY std; +struct ModelOutputRoadEdges { + ModelOutputEdgessXY mean; + ModelOutputEdgessXY std; }; -static_assert(sizeof(ModelDataRawRoadEdges) == (sizeof(ModelDataRawEdgessXY)*2)); +static_assert(sizeof(ModelOutputRoadEdges) == (sizeof(ModelOutputEdgessXY)*2)); -struct ModelDataRawLeadElement { +struct ModelOutputLeadElement { float x; float y; float velocity; float acceleration; }; -static_assert(sizeof(ModelDataRawLeadElement) == sizeof(float)*4); +static_assert(sizeof(ModelOutputLeadElement) == sizeof(float)*4); -struct ModelDataRawLeadPrediction { - std::array mean; - std::array std; +struct ModelOutputLeadPrediction { + std::array mean; + std::array std; std::array prob; }; -static_assert(sizeof(ModelDataRawLeadPrediction) == (sizeof(ModelDataRawLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); +static_assert(sizeof(ModelOutputLeadPrediction) == (sizeof(ModelOutputLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); -struct ModelDataRawLeads { - std::array prediction; +struct ModelOutputLeads { + std::array prediction; std::array prob; - constexpr const ModelDataRawLeadPrediction &get_best_prediction(int t_idx) const { + constexpr const ModelOutputLeadPrediction &get_best_prediction(int t_idx) const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob[t_idx] > prediction[max_idx].prob[t_idx]) { @@ -169,26 +145,77 @@ struct ModelDataRawLeads { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawLeads) == (sizeof(ModelDataRawLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); - -struct ModelDataRawPose { - ModelDataRawXYZ velocity_mean; - ModelDataRawXYZ rotation_mean; - ModelDataRawXYZ velocity_std; - ModelDataRawXYZ rotation_std; -}; -static_assert(sizeof(ModelDataRawPose) == sizeof(ModelDataRawXYZ)*4); - -struct ModelDataRaw { - const ModelDataRawPlans *const plans; - const ModelDataRawLaneLines *const lane_lines; - const ModelDataRawRoadEdges *const road_edges; - const ModelDataRawLeads *const leads; - const float *const desire_state; - const float *const meta; - const float *const desire_pred; - const ModelDataRawPose *const pose; -}; +static_assert(sizeof(ModelOutputLeads) == (sizeof(ModelOutputLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); + +struct ModelOutputPose { + ModelOutputXYZ velocity_mean; + ModelOutputXYZ rotation_mean; + ModelOutputXYZ velocity_std; + ModelOutputXYZ rotation_std; +}; +static_assert(sizeof(ModelOutputPose) == sizeof(ModelOutputXYZ)*4); + +struct ModelOutputDisengageProb { + float gas_disengage; + float brake_disengage; + float steer_override; + float brake_3ms2; + float brake_4ms2; + float brake_5ms2; + float gas_pressed; +}; +static_assert(sizeof(ModelOutputDisengageProb) == sizeof(float)*7); + +struct ModelOutputBlinkerProb { + float left; + float right; +}; +static_assert(sizeof(ModelOutputBlinkerProb) == sizeof(float)*2); + +struct ModelOutputDesireProb { + union { + struct { + float none; + float turn_left; + float turn_right; + float lane_change_left; + float lane_change_right; + float keep_left; + float keep_right; + float null; + }; + struct { + std::array array; + }; + }; +}; +static_assert(sizeof(ModelOutputDesireProb) == sizeof(float)*DESIRE_LEN); + +struct ModelOutputMeta { + ModelOutputDesireProb desire_state_prob; + float engaged_prob; + std::array disengage_prob; + std::array blinker_prob; + std::array desire_pred_prob; +}; +static_assert(sizeof(ModelOutputMeta) == sizeof(ModelOutputDesireProb) + sizeof(float) + (sizeof(ModelOutputDisengageProb)*DISENGAGE_LEN) + (sizeof(ModelOutputBlinkerProb)*BLINKER_LEN) + (sizeof(ModelOutputDesireProb)*DESIRE_PRED_LEN)); + +struct ModelOutput { + const ModelOutputPlans plans; + const ModelOutputLaneLines lane_lines; + const ModelOutputRoadEdges road_edges; + const ModelOutputLeads leads; + const ModelOutputMeta meta; + const ModelOutputPose pose; +}; + +constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); +#ifdef TEMPORAL + constexpr int TEMPORAL_SIZE = 512; +#else + constexpr int TEMPORAL_SIZE = 0; +#endif +constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; // TODO: convert remaining arrays to std::array and update model runners struct ModelState { @@ -205,12 +232,11 @@ struct ModelState { }; void model_init(ModelState* s, cl_device_id device_id, cl_context context); -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput *model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in); void model_free(ModelState* s); -void poly_fit(float *in_pts, float *in_stds, float *out); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, float frame_drop, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof, - float model_execution_time, kj::ArrayPtr raw_pred); + const ModelOutput &net_outputs, uint64_t timestamp_eof, + float model_execution_time, kj::ArrayPtr raw_pred, const bool valid); void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof); + const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid); diff --git a/selfdrive/modeld/visiontest.py b/selfdrive/modeld/visiontest.py index 8bf71d26d5..1e30c05449 100644 --- a/selfdrive/modeld/visiontest.py +++ b/selfdrive/modeld/visiontest.py @@ -62,7 +62,7 @@ class VisionTest(): disable_model = 0 temporal_model = 1 else: - raise ValueError("Bad model name: {}".format(model)) + raise ValueError(f"Bad model name: {model}") prevdir = os.getcwd() os.chdir(_visiond_dir) # tmp hack to find kernels diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f11d72266c..1728cc9e79 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -43,7 +43,7 @@ def dmonitoringd_thread(sm=None, pm=None): v_cruise_last = v_cruise if sm.updated['modelV2']: - driver_status.set_policy(sm['modelV2']) + driver_status.set_policy(sm['modelV2'], sm['carState'].vEgo) # Get data from dmonitoringmodeld events = Events() diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index e4018d66f6..efb00ecb77 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -32,15 +32,21 @@ class DRIVER_MONITOR_SETTINGS(): self._BLINK_THRESHOLD = 0.82 if TICI else 0.588 self._BLINK_THRESHOLD_SLACK = 0.9 if TICI else 0.77 self._BLINK_THRESHOLD_STRICT = self._BLINK_THRESHOLD - self._PITCH_WEIGHT = 1.35 # pitch matters a lot more - self._POSESTD_THRESHOLD = 0.38 if TICI else 0.3 - self._METRIC_THRESHOLD = 0.48 - self._METRIC_THRESHOLD_SLACK = 0.66 - self._METRIC_THRESHOLD_STRICT = self._METRIC_THRESHOLD - self._PITCH_NATURAL_OFFSET = 0.02 # people don't seem to look straight when they drive relaxed, rather a bit up - self._YAW_NATURAL_OFFSET = 0.08 # people don't seem to look straight when they drive relaxed, rather a bit to the right (center of car) + self._POSE_PITCH_THRESHOLD = 0.3237 + self._POSE_PITCH_THRESHOLD_SLACK = 0.3657 + self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD + self._POSE_YAW_THRESHOLD = 0.3109 + self._POSE_YAW_THRESHOLD_SLACK = 0.4294 + self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD + self._PITCH_NATURAL_OFFSET = 0.057 # initial value before offset is learned + self._YAW_NATURAL_OFFSET = 0.11 # initial value before offset is learned + self._PITCH_MAX_OFFSET = 0.124 + self._PITCH_MIN_OFFSET = -0.0881 + self._YAW_MAX_OFFSET = 0.289 + self._YAW_MIN_OFFSET = -0.0246 + self._POSESTD_THRESHOLD = 0.38 if TICI else 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz @@ -93,7 +99,8 @@ class DriverPose(): self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) self.low_std = True - self.cfactor = 1. + self.cfactor_pitch = 1. + self.cfactor_yaw = 1. class DriverBlink(): def __init__(self): @@ -164,34 +171,42 @@ class DriverStatus(): pitch_error = pose.pitch - self.settings._PITCH_NATURAL_OFFSET yaw_error = pose.yaw - self.settings._YAW_NATURAL_OFFSET else: - pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean() - yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean() + pitch_error = pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(), + self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) + yaw_error = pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), + self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit yaw_error = abs(yaw_error) - if pitch_error*self.settings._PITCH_WEIGHT > self.settings._METRIC_THRESHOLD*pose.cfactor or \ - yaw_error > self.settings._METRIC_THRESHOLD*pose.cfactor: + if pitch_error > self.settings._POSE_PITCH_THRESHOLD*pose.cfactor_pitch or \ + yaw_error > self.settings._POSE_YAW_THRESHOLD*pose.cfactor_yaw: return DistractedType.BAD_POSE elif (blink.left_blink + blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD*blink.cfactor: return DistractedType.BAD_BLINK else: return DistractedType.NOT_DISTRACTED - def set_policy(self, model_data): - ep = min(model_data.meta.engagedProb, 0.8) / 0.8 - self.pose.cfactor = interp(ep, [0, 0.5, 1], - [self.settings._METRIC_THRESHOLD_STRICT, - self.settings. _METRIC_THRESHOLD, - self.settings._METRIC_THRESHOLD_SLACK]) / self.settings._METRIC_THRESHOLD + def set_policy(self, model_data, car_speed): + ep = min(model_data.meta.engagedProb, 0.8) / 0.8 # engaged prob + bp = model_data.meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + # TODO: retune adaptive blink self.blink.cfactor = interp(ep, [0, 0.5, 1], [self.settings._BLINK_THRESHOLD_STRICT, self.settings._BLINK_THRESHOLD, self.settings._BLINK_THRESHOLD_SLACK]) / self.settings._BLINK_THRESHOLD + k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) + bp_normal = max(min(bp / k1, 0.5),0) + self.pose.cfactor_pitch = interp(bp_normal, [0, 0.5], + [self.settings._POSE_PITCH_THRESHOLD_SLACK, + self.settings._POSE_PITCH_THRESHOLD_STRICT]) / self.settings._POSE_PITCH_THRESHOLD + self.pose.cfactor_yaw = interp(bp_normal, [0, 0.5], + [self.settings._POSE_YAW_THRESHOLD_SLACK, + self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD def get_pose(self, driver_state, cal_rpy, car_speed, op_engaged): - if not all(len(x) > 0 for x in [driver_state.faceOrientation, driver_state.facePosition, - driver_state.faceOrientationStd, driver_state.facePositionStd]): + if not all(len(x) > 0 for x in (driver_state.faceOrientation, driver_state.facePosition, + driver_state.faceOrientationStd, driver_state.facePositionStd)): return self.face_partial = driver_state.partialFace > self.settings._PARTIAL_FACE_THRESHOLD diff --git a/selfdrive/pandad.py b/selfdrive/pandad.py index b725ca9dda..e3ebdad637 100755 --- a/selfdrive/pandad.py +++ b/selfdrive/pandad.py @@ -4,7 +4,7 @@ import os import usb1 import time import subprocess -from typing import List +from typing import NoReturn from functools import cmp_to_key from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU @@ -30,12 +30,7 @@ def flash_panda(panda_serial : str) -> Panda: panda_version = "bootstub" if panda.bootstub else panda.get_version() panda_signature = b"" if panda.bootstub else panda.get_signature() - cloudlog.warning("Panda %s connected, version: %s, signature %s, expected %s" % ( - panda_serial, - panda_version, - panda_signature.hex()[:16], - fw_signature.hex()[:16], - )) + cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}") if panda.bootstub or panda_signature != fw_signature: cloudlog.info("Panda firmware out of date, update required") @@ -72,11 +67,11 @@ def panda_sort_cmp(a : Panda, b : Panda): # sort by hardware type if a_type != b_type: return a_type < b_type - + # last resort: sort by serial number return a.get_usb_serial() < b.get_usb_serial() -def main() -> None: +def main() -> NoReturn: while True: try: # Flash all Pandas in DFU mode diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py new file mode 100755 index 0000000000..33f35032ff --- /dev/null +++ b/selfdrive/statsd.py @@ -0,0 +1,124 @@ +#!/usr/bin/env python3 +import os +import zmq +import time +from pathlib import Path +from datetime import datetime, timezone +from common.params import Params +from cereal.messaging import SubMaster +from selfdrive.swaglog import cloudlog +from selfdrive.hardware import HARDWARE +from common.file_helpers import atomic_write_in_dir +from selfdrive.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty +from selfdrive.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S + + +class METRIC_TYPE: + GAUGE = 'g' + +class StatLog: + def __init__(self): + self.pid = None + + def connect(self): + self.zctx = zmq.Context() + self.sock = self.zctx.socket(zmq.PUSH) + self.sock.setsockopt(zmq.LINGER, 10) + self.sock.connect(STATS_SOCKET) + self.pid = os.getpid() + + def _send(self, metric: str): + if os.getpid() != self.pid: + self.connect() + + try: + self.sock.send_string(metric, zmq.NOBLOCK) + except zmq.error.Again: + # drop :/ + pass + + def gauge(self, name: str, value: float): + self._send(f"{name}:{value}|{METRIC_TYPE.GAUGE}") + + +def main(): + def get_influxdb_line(measurement: str, value: float, timestamp: datetime, tags: dict): + res = f"{measurement}" + for tag_key in tags.keys(): + res += f",{tag_key}={str(tags[tag_key])}" + res += f" value={value} {int(timestamp.timestamp() * 1e9)}\n" + return res + + # open statistics socket + ctx = zmq.Context().instance() + sock = ctx.socket(zmq.PULL) + sock.bind(STATS_SOCKET) + + # initialize stats directory + Path(STATS_DIR).mkdir(parents=True, exist_ok=True) + + # initialize tags + tags = { + 'dongleId': Params().get("DongleId", encoding='utf-8'), + 'started': False, + 'version': get_short_version(), + 'branch': get_short_branch(), + 'dirty': is_dirty(), + 'origin': get_normalized_origin(), + 'deviceType': HARDWARE.get_device_type(), + } + + # subscribe to deviceState for started state + sm = SubMaster(['deviceState']) + + last_flush_time = time.monotonic() + gauges = {} + while True: + started_prev = sm['deviceState'].started + sm.update() + + # Update metrics + while True: + try: + metric = sock.recv_string(zmq.NOBLOCK) + try: + metric_type = metric.split('|')[1] + metric_name = metric.split(':')[0] + metric_value = metric.split('|')[0].split(':')[1] + + if metric_type == METRIC_TYPE.GAUGE: + gauges[metric_name] = metric_value + else: + cloudlog.event("unknown metric type", metric_type=metric_type) + except Exception: + cloudlog.event("malformed metric", metric=metric) + except zmq.error.Again: + break + + # flush when started state changes or after FLUSH_TIME_S + if (time.monotonic() > last_flush_time + STATS_FLUSH_TIME_S) or (sm['deviceState'].started != started_prev): + result = "" + current_time = datetime.utcnow().replace(tzinfo=timezone.utc) + tags['started'] = sm['deviceState'].started + + for gauge_key in gauges.keys(): + result += get_influxdb_line(f"gauge.{gauge_key}", gauges[gauge_key], current_time, tags) + + # clear intermediate data + gauges = {} + last_flush_time = time.monotonic() + + # check that we aren't filling up the drive + if len(os.listdir(STATS_DIR)) < STATS_DIR_FILE_LIMIT: + if len(result) > 0: + stats_path = os.path.join(STATS_DIR, str(int(current_time.timestamp()))) + with atomic_write_in_dir(stats_path) as f: + f.write(result) + else: + cloudlog.error("stats dir full") + + +if __name__ == "__main__": + main() +else: + statlog = StatLog() diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index cefc2166ec..beb544b1b1 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -97,7 +97,7 @@ class Plant(): 'carState': car_state.carState, 'controlsState': control.controlsState} self.planner.update(sm) - self.speed = self.planner.v_desired + self.speed = self.planner.v_desired_filter.x self.acceleration = self.planner.a_desired fcw = self.planner.fcw self.distance_lead = self.distance_lead + v_lead * self.ts diff --git a/selfdrive/test/openpilotci.py b/selfdrive/test/openpilotci.py index 9cc0e37c4b..5bf43fb10a 100755 --- a/selfdrive/test/openpilotci.py +++ b/selfdrive/test/openpilotci.py @@ -9,7 +9,7 @@ TOKEN_PATH = "/data/azure_token" def get_url(route_name, segment_num, log_type="rlog"): ext = "hevc" if log_type in ["fcamera", "dcamera"] else "bz2" - return BASE_URL + "%s/%s/%s.%s" % (route_name.replace("|", "/"), segment_num, log_type, ext) + return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" def upload_file(path, name): from azure.storage.blob import BlockBlobService # pylint: disable=import-error diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index eb058b354c..72f5e51bb2 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -58,7 +58,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non if ignore_msgs is None: ignore_msgs = [] - log1, log2 = [list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)] + log1, log2 = (list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)) if len(log1) != len(log2): cnt1 = Counter(m.which() for m in log1) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 32a5717852..6eb37dfa29 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -2,16 +2,16 @@ import os import sys import time -from typing import Any - +from collections import defaultdict from tqdm import tqdm +from typing import Any import cereal.messaging as messaging from cereal.visionipc.visionipc_pyx import VisionIpcServer, VisionStreamType # pylint: disable=no-name-in-module, import-error from common.spinner import Spinner from common.timeout import Timeout from common.transformations.camera import get_view_frame_from_road_frame, eon_f_frame_size, tici_f_frame_size, \ - eon_d_frame_size, tici_d_frame_size + eon_d_frame_size, tici_d_frame_size from selfdrive.hardware import PC, TICI from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url @@ -25,13 +25,14 @@ if TICI: TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" else: TEST_ROUTE = "303055c0002aefd1|2021-11-22--18-36-32" +SEGMENT = 0 -CACHE_DIR = os.getenv("CACHE_DIR", None) +SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) -packet_from_camera = {"roadCameraState":"modelV2", "driverCameraState":"driverState"} def get_log_fn(ref_commit): - return "%s_%s_%s.bz2" % (TEST_ROUTE, "model_tici" if TICI else "model", ref_commit) + return f"{TEST_ROUTE}_{'model_tici' if TICI else 'model'}_{ref_commit}.bz2" + def replace_calib(msg, calib): msg = msg.as_builder() @@ -39,36 +40,8 @@ def replace_calib(msg, calib): msg.liveCalibration.extrinsicMatrix = get_view_frame_from_road_frame(*calib, 1.22).flatten().tolist() return msg -def process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire): - if msg.which() == "roadCameraState" and last_desire is not None: - dat = messaging.new_message('lateralPlan') - dat.lateralPlan.desire = last_desire - pm.send('lateralPlan', dat) - - f = msg.as_builder() - pm.send(msg.which(), f) - - img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="yuv420p")[0] - if msg.which == "roadCameraState": - vipc_server.send(VisionStreamType.VISION_STREAM_ROAD, img.flatten().tobytes(), f.roadCameraState.frameId, - f.roadCameraState.timestampSof, f.roadCameraState.timestampEof) - else: - vipc_server.send(VisionStreamType.VISION_STREAM_DRIVER, img.flatten().tobytes(), f.driverCameraState.frameId, - f.driverCameraState.timestampSof, f.driverCameraState.timestampEof) - with Timeout(seconds=15): - log_msgs.append(messaging.recv_one(sm.sock[packet_from_camera[msg.which()]])) - - frame_idxs[msg.which()] += 1 - if frame_idxs[msg.which()] >= frs[msg.which()].frame_count: - return None - update_spinner(spinner, frame_idxs['roadCameraState'], frs['roadCameraState'].frame_count, - frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count) - return 0 - -def update_spinner(s, fidx, fcnt, didx, dcnt): - s.update("replaying models: road %d/%d, driver %d/%d" % (fidx, fcnt, didx, dcnt)) - -def model_replay(lr_list, frs): + +def model_replay(lr, frs): spinner = Spinner() spinner.update("starting model replay") @@ -77,93 +50,110 @@ def model_replay(lr_list, frs): vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size if TICI else eon_d_frame_size)) vipc_server.start_listener() - pm = messaging.PubMaster(['roadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) sm = messaging.SubMaster(['modelV2', 'driverState']) + pm = messaging.PubMaster(['roadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) try: managed_processes['modeld'].start() managed_processes['dmonitoringmodeld'].start() - time.sleep(5) + time.sleep(2) sm.update(1000) - last_desire = None log_msgs = [] - frame_idxs = dict.fromkeys(['roadCameraState','driverCameraState'], 0) - - cal = [msg for msg in lr if msg.which() == "liveCalibration"] - for msg in cal[:5]: - pm.send(msg.which(), replace_calib(msg, None)) - - for msg in tqdm(lr_list): - if msg.which() == "liveCalibration": - last_calib = list(msg.liveCalibration.rpyCalib) - pm.send(msg.which(), replace_calib(msg, last_calib)) - elif msg.which() == "lateralPlan": - last_desire = msg.lateralPlan.desire - elif msg.which() in ["roadCameraState", "driverCameraState"]: - ret = process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire) - if ret is None: + last_desire = None + frame_idxs = defaultdict(lambda: 0) + + # init modeld with valid calibration + cal_msgs = [msg for msg in lr if msg.which() == "liveCalibration"] + for _ in range(5): + pm.send(cal_msgs[0].which(), cal_msgs[0].as_builder()) + time.sleep(0.1) + + for msg in tqdm(lr): + if SEND_EXTRA_INPUTS: + if msg.which() == "liveCalibration": + last_calib = list(msg.liveCalibration.rpyCalib) + pm.send(msg.which(), replace_calib(msg, last_calib)) + elif msg.which() == "lateralPlan": + last_desire = msg.lateralPlan.desire + dat = messaging.new_message('lateralPlan') + dat.lateralPlan.desire = last_desire + pm.send('lateralPlan', dat) + + if msg.which() in ["roadCameraState", "driverCameraState"]: + camera_state = getattr(msg, msg.which()) + stream = VisionStreamType.VISION_STREAM_ROAD if msg.which() == "roadCameraState" else VisionStreamType.VISION_STREAM_DRIVER + img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="yuv420p")[0] + + # send camera state and frame + pm.send(msg.which(), msg.as_builder()) + vipc_server.send(stream, img.flatten().tobytes(), camera_state.frameId, + camera_state.timestampSof, camera_state.timestampEof) + + # wait for a response + with Timeout(seconds=15): + packet_from_camera = {"roadCameraState": "modelV2", "driverCameraState": "driverState"} + log_msgs.append(messaging.recv_one(sm.sock[packet_from_camera[msg.which()]])) + + frame_idxs[msg.which()] += 1 + if frame_idxs[msg.which()] >= frs[msg.which()].frame_count: break - except KeyboardInterrupt: - pass + spinner.update("replaying models: road %d/%d, driver %d/%d" % (frame_idxs['roadCameraState'], + frs['roadCameraState'].frame_count, frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count)) + finally: spinner.close() managed_processes['modeld'].stop() managed_processes['dmonitoringmodeld'].stop() + return log_msgs + if __name__ == "__main__": update = "--update" in sys.argv - - if TICI: - os.system('sudo mount -o remount,size=200M /tmp') # c3 hevcs are 75M each - replay_dir = os.path.dirname(os.path.abspath(__file__)) ref_commit_fn = os.path.join(replay_dir, "model_replay_ref_commit") - segnum = 0 - frs = {} - if CACHE_DIR: - lr = LogReader(os.path.join(CACHE_DIR, '%s--%d--rlog.bz2' % (TEST_ROUTE.replace('|', '_'), segnum))) - frs['roadCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--fcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) - frs['driverCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--dcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) - else: - lr = LogReader(get_url(TEST_ROUTE, segnum)) - frs['roadCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="fcamera")) - frs['driverCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="dcamera")) + # load logs + lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT))) + frs = { + 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="fcamera")), + 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="dcamera")), + } - lr_list = list(lr) - log_msgs = model_replay(lr_list, frs) + # run replay + log_msgs = model_replay(lr, frs) + # get diff failed = False if not update: - ref_commit = open(ref_commit_fn).read().strip() + with open(ref_commit_fn) as f: + ref_commit = f.read().strip() log_fn = get_log_fn(ref_commit) cmp_log = LogReader(BASE_URL + log_fn) - ignore = ['logMonoTime', 'valid', - 'modelV2.frameDropPerc', - 'modelV2.modelExecutionTime', - 'driverState.modelExecutionTime', - 'driverState.dspExecutionTime'] + ignore = [ + 'logMonoTime', + 'modelV2.frameDropPerc', + 'modelV2.modelExecutionTime', + 'driverState.modelExecutionTime', + 'driverState.dspExecutionTime' + ] tolerance = None if not PC else 1e-3 results: Any = {TEST_ROUTE: {}} results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) diff1, diff2, failed = format_diff(results, ref_commit) print(diff2) - print('-------------') - print('-------------') - print('-------------') - print('-------------') - print('-------------') + print('-------------\n'*5) print(diff1) with open("model_diff.txt", "w") as f: f.write(diff2) + # upload new refs if update or failed: from selfdrive.test.openpilotci import upload_file diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 93b61856de..c343275b6b 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -ed6d7e8f5e7e2c141b35814bb1c9b965f5e7dde8 +7d3ad941bc4ba4c923af7a1d7b48544bfc0d3e13 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index ccdbc2754f..8a13e4b18e 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -89,7 +89,7 @@ class DumbSocket: class FakeSubMaster(messaging.SubMaster): def __init__(self, services): - super(FakeSubMaster, self).__init__(services, addr=None) + super().__init__(services, addr=None) self.sock = {s: DumbSocket(s) for s in services} self.update_called = threading.Event() self.update_ready = threading.Event() @@ -111,7 +111,7 @@ class FakeSubMaster(messaging.SubMaster): def update_msgs(self, cur_time, msgs): wait_for_event(self.update_called) self.update_called.clear() - super(FakeSubMaster, self).update_msgs(cur_time, msgs) + super().update_msgs(cur_time, msgs) self.update_ready.set() def wait_for_update(self): diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index b08a84bbac..6ebdb327b3 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -7dad09224e82177e1da44b89a253c343be45dd38 \ No newline at end of file +05ebb83207d2c949ee70702e4ec4568f872c6054 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 8fd1b0b421..6c33b46224 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -74,7 +74,7 @@ def test_process(cfg, lr, cmp_log_fn, ignore_fields=None, ignore_msgs=None): break else: segment = cmp_log_fn.split("/")[-1].split("_")[0] - raise Exception("Route never enabled: %s" % segment) + raise Exception(f"Route never enabled: {segment}") try: return compare_logs(cmp_log_msgs, log_msgs, ignore_fields+cfg.ignore, ignore_msgs, cfg.tolerance) @@ -83,30 +83,30 @@ def test_process(cfg, lr, cmp_log_fn, ignore_fields=None, ignore_msgs=None): def format_diff(results, ref_commit): diff1, diff2 = "", "" - diff2 += "***** tested against commit %s *****\n" % ref_commit + diff2 += f"***** tested against commit {ref_commit} *****\n" failed = False for segment, result in list(results.items()): - diff1 += "***** results for segment %s *****\n" % segment - diff2 += "***** differences for segment %s *****\n" % segment + diff1 += f"***** results for segment {segment} *****\n" + diff2 += f"***** differences for segment {segment} *****\n" for proc, diff in list(result.items()): - diff1 += "\t%s\n" % proc - diff2 += "*** process: %s ***\n" % proc + diff1 += f"\t{proc}\n" + diff2 += f"*** process: {proc} ***\n" if isinstance(diff, str): - diff1 += "\t\t%s\n" % diff + diff1 += f"\t\t{diff}\n" failed = True elif len(diff): cnt = {} for d in diff: - diff2 += "\t%s\n" % str(d) + diff2 += f"\t{str(d)}\n" k = str(d[1]) cnt[k] = 1 if k not in cnt else cnt[k] + 1 for k, v in sorted(cnt.items()): - diff1 += "\t\t%s: %s\n" % (k, v) + diff1 += f"\t\t{k}: {v}\n" failed = True return diff1, diff2, failed @@ -139,13 +139,13 @@ if __name__ == "__main__": print("couldn't find reference commit") sys.exit(1) - print("***** testing against commit %s *****" % ref_commit) + print(f"***** testing against commit {ref_commit} *****") # check to make sure all car brands are tested if FULL_TEST: - tested_cars = set(c.lower() for c, _ in segments) + tested_cars = {c.lower() for c, _ in segments} untested = (set(interface_names) - set(excluded_interfaces)) - tested_cars - assert len(untested) == 0, "Cars missing routes: %s" % (str(untested)) + assert len(untested) == 0, f"Cars missing routes: {str(untested)}" results: Any = {} for car_brand, segment in segments: @@ -153,7 +153,7 @@ if __name__ == "__main__": (not cars_whitelisted and car_brand.upper() in args.blacklist_cars): continue - print("***** testing route segment %s *****\n" % segment) + print(f"***** testing route segment {segment} *****\n") results[segment] = {} @@ -165,7 +165,7 @@ if __name__ == "__main__": (not procs_whitelisted and cfg.proc_name in args.blacklist_procs): continue - cmp_log_fn = os.path.join(process_replay_dir, "%s_%s_%s.bz2" % (segment, cfg.proc_name, ref_commit)) + cmp_log_fn = os.path.join(process_replay_dir, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") results[segment][cfg.proc_name] = test_process(cfg, lr, cmp_log_fn, args.ignore_fields, args.ignore_msgs) diff1, diff2, failed = format_diff(results, ref_commit) diff --git a/selfdrive/test/process_replay/update_refs.py b/selfdrive/test/process_replay/update_refs.py index 96feb0d534..0a3d95e714 100755 --- a/selfdrive/test/process_replay/update_refs.py +++ b/selfdrive/test/process_replay/update_refs.py @@ -28,7 +28,7 @@ if __name__ == "__main__": for cfg in CONFIGS: log_msgs = replay_process(cfg, lr) - log_fn = os.path.join(process_replay_dir, "%s_%s_%s.bz2" % (segment, cfg.proc_name, ref_commit)) + log_fn = os.path.join(process_replay_dir, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") save_log(log_fn, log_msgs) if not no_upload: diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 7a01e72122..5f73176fab 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -5,7 +5,6 @@ import cProfile # pylint: disable=import-error import pprofile # pylint: disable=import-error import pyprof2calltree # pylint: disable=import-error -from cereal import car from common.params import Params from tools.lib.logreader import LogReader from selfdrive.test.profiling.lib import SubMaster, PubMaster, SubSocket, ReplayDone @@ -35,9 +34,7 @@ def get_inputs(msgs, process, fingerprint): if msg.which() == 'carParams': m = msg.as_builder() m.carParams.carFingerprint = fingerprint - - CP = car.CarParams.from_dict(m.carParams.to_dict()) - Params().put("CarParams", CP.to_bytes()) + Params().put("CarParams", m.carParams.copy().to_bytes()) break sm = SubMaster(msgs, trigger, sub_socks) diff --git a/selfdrive/test/test_fingerprints.py b/selfdrive/test/test_fingerprints.py index 07e4774827..4ea60a76a8 100755 --- a/selfdrive/test/test_fingerprints.py +++ b/selfdrive/test/test_fingerprints.py @@ -18,8 +18,8 @@ def _get_fingerprints(): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: car_name = car_folder.split('/')[-1] try: - fingerprints[car_name] = __import__('selfdrive.car.%s.values' % car_name, fromlist=['FINGERPRINTS']).FINGERPRINTS - except (ImportError, IOError, AttributeError): + fingerprints[car_name] = __import__(f'selfdrive.car.{car_name}.values', fromlist=['FINGERPRINTS']).FINGERPRINTS + except (ImportError, OSError, AttributeError): pass return fingerprints @@ -80,14 +80,14 @@ if __name__ == "__main__": for idx2, f2 in enumerate(fingerprints_flat): if idx1 < idx2 and not check_fingerprint_consistency(f1, f2): valid = False - print("Those two fingerprints are inconsistent {0} {1}".format(car_names[idx1], car_names[idx2])) + print(f"Those two fingerprints are inconsistent {car_names[idx1]} {car_names[idx2]}") print("") print(', '.join("%d: %d" % v for v in sorted(f1.items()))) print("") print(', '.join("%d: %d" % v for v in sorted(f2.items()))) print("") - print("Found {0} individual fingerprints".format(len(fingerprints_flat))) + print(f"Found {len(fingerprints_flat)} individual fingerprints") if not valid or len(fingerprints_flat) == 0: print("TEST FAILED") sys.exit(1) diff --git a/selfdrive/test/test_models.py b/selfdrive/test/test_models.py index fc1f919b83..5112d30919 100755 --- a/selfdrive/test/test_models.py +++ b/selfdrive/test/test_models.py @@ -134,7 +134,7 @@ class TestCarModel(unittest.TestCase): def test_radar_interface(self): os.environ['NO_RADAR_SLEEP'] = "1" - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % self.CP.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface RI = RadarInterface(self.CP) assert RI @@ -221,7 +221,7 @@ class TestCarModel(unittest.TestCase): # TODO: use the same signal in panda and carState # tolerate a small delay between the button press and PCM entering a cruise state - if self.car_model == HONDA.ACCORD_2021: + if self.car_model == HONDA.ACCORD: if failed_checks['controlsAllowed'] < 500: del failed_checks['controlsAllowed'] diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index bc44aa3ea1..2f85605b83 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -34,7 +34,7 @@ PROCS = { "./_modeld": 4.48, "./boardd": 3.63, "./_dmonitoringmodeld": 2.67, - "selfdrive.thermald.thermald": 2.41, + "selfdrive.thermald.thermald": 7.73, "selfdrive.locationd.calibrationd": 2.0, "./_soundd": 1.0, "selfdrive.monitoring.dmonitoringd": 1.90, @@ -56,12 +56,12 @@ if TICI: "./loggerd": 70.0, "selfdrive.controls.controlsd": 28.0, "./camerad": 31.0, - "./_ui": 30.2, + "./_ui": 33.0, "selfdrive.controls.plannerd": 11.7, "./_dmonitoringmodeld": 10.0, "selfdrive.locationd.paramsd": 5.0, "selfdrive.controls.radard": 3.6, - "selfdrive.thermald.thermald": 1.5, + "selfdrive.thermald.thermald": 4.75, }) diff --git a/selfdrive/test/test_routes.py b/selfdrive/test/test_routes.py index dc6b926dba..f064b89f51 100755 --- a/selfdrive/test/test_routes.py +++ b/selfdrive/test/test_routes.py @@ -53,9 +53,9 @@ routes = [ TestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN), TestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T TestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T - TestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD_2021), # 2.0T + TestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs TestRoute("07585b0da3c88459|2021-05-26--18-52-04", HONDA.ACCORDH), - TestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORDH_2021), + TestRoute("f29e2b57a55e7ad5|2021-03-24--20-52-38", HONDA.ACCORDH), # 2021 with new style HUD msgs TestRoute("1ad763dd22ef1a0e|2020-02-29--18-37-03", HONDA.CRV_5G), TestRoute("0a96f86fcfe35964|2020-02-05--07-25-51", HONDA.ODYSSEY), TestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL), @@ -74,6 +74,7 @@ routes = [ TestRoute("38bfd238edecbcd7|2018-08-29--22-02-15", HYUNDAI.SANTA_FE), TestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), TestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), + TestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022), TestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), TestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA), TestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), @@ -171,6 +172,7 @@ routes = [ TestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.ASCENT), TestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), TestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), + TestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), # Dashcam TestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), # Dashcam diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 3e5a0d2cf0..3a392b4b36 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -25,7 +25,7 @@ def upload_route(path): "azcopy", "copy", f"{path}/*", - "https://{}.blob.core.windows.net/{}/{}?{}".format(_DATA_ACCOUNT_CI, "openpilotci", destpath, DEST_KEY), + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{DEST_KEY}", "--recursive=false", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", @@ -46,8 +46,8 @@ def sync_to_ci_public(route): cmd = [ "azcopy", "copy", - "https://{}.blob.core.windows.net/{}/{}?{}".format(source_account, source_bucket, key_prefix, source_key), - "https://{}.blob.core.windows.net/{}/{}?{}".format(_DATA_ACCOUNT_CI, "openpilotci", dongle_id, DEST_KEY), + f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{dongle_id}?{DEST_KEY}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 9c07363917..0dc4c5a1d3 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -2,12 +2,14 @@ import random import threading import time from statistics import mean +from typing import Optional from cereal import log from common.params import Params, put_nonblocking from common.realtime import sec_since_boot from selfdrive.hardware import HARDWARE from selfdrive.swaglog import cloudlog +from selfdrive.statsd import statlog CAR_VOLTAGE_LOW_PASS_K = 0.091 # LPF gain for 5s tau (dt/tau / (dt/tau + 1)) @@ -55,6 +57,7 @@ class PowerMonitoring: # Low-pass battery voltage self.car_voltage_instant_mV = peripheralState.voltage self.car_voltage_mV = ((peripheralState.voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) + statlog.gauge("car_voltage", self.car_voltage_mV / 1e3) # Cap the car battery power and save it in a param every 10-ish seconds self.car_battery_capacity_uWh = max(self.car_battery_capacity_uWh, 0) @@ -135,7 +138,7 @@ class PowerMonitoring: except Exception: cloudlog.exception("Power monitoring calculation failed") - def _perform_integration(self, t, current_power): + def _perform_integration(self, t: float, current_power: float) -> None: with self.integration_lock: try: if self.last_measurement_time: @@ -150,14 +153,14 @@ class PowerMonitoring: cloudlog.exception("Integration failed") # Get the power usage - def get_power_used(self): + def get_power_used(self) -> int: return int(self.power_used_uWh) - def get_car_battery_capacity(self): + def get_car_battery_capacity(self) -> int: return int(self.car_battery_capacity_uWh) # See if we need to disable charging - def should_disable_charging(self, ignition, in_car, offroad_timestamp): + def should_disable_charging(self, ignition: bool, in_car: bool, offroad_timestamp: Optional[float]) -> bool: if offroad_timestamp is None: return False diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index c5bf955e7f..7982afff23 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -3,7 +3,7 @@ import datetime import os import time from pathlib import Path -from typing import Dict, Optional, Tuple +from typing import Dict, NoReturn, Optional, Tuple from collections import namedtuple, OrderedDict import psutil @@ -23,6 +23,7 @@ from selfdrive.loggerd.config import get_available_percent from selfdrive.swaglog import cloudlog from selfdrive.thermald.power_monitoring import PowerMonitoring from selfdrive.version import terms_version, training_version +from selfdrive.statsd import statlog ThermalStatus = log.DeviceState.ThermalStatus NetworkType = log.DeviceState.NetworkType @@ -81,7 +82,7 @@ def set_eon_fan(val): try: i = [0x1, 0x3 | 0, 0x3 | 0x08, 0x3 | 0x10][val] bus.write_i2c_block_data(0x3d, 0, [i]) - except IOError: + except OSError: # tusb320 if val == 0: bus.write_i2c_block_data(0x67, 0xa, [0]) @@ -136,7 +137,7 @@ def handle_fan_tici(controller, max_cpu_temp, fan_speed, ignition): controller.reset() fan_pwr_out = -int(controller.update( - setpoint=(75 if ignition else (OFFROAD_DANGER_TEMP - 2)), + setpoint=75, measurement=max_cpu_temp, feedforward=interp(max_cpu_temp, [60.0, 100.0], [0, -80]) )) @@ -152,22 +153,22 @@ def set_offroad_alert_if_changed(offroad_alert: str, show_alert: bool, extra_tex set_offroad_alert(offroad_alert, show_alert, extra_text) -def thermald_thread(): +def thermald_thread() -> NoReturn: pm = messaging.PubMaster(['deviceState']) pandaState_timeout = int(1000 * 2.5 * DT_TRML) # 2.5x the expected pandaState frequency pandaState_sock = messaging.sub_sock('pandaStates', timeout=pandaState_timeout) - sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "managerState"]) + sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "managerState", "controlsState"]) fan_speed = 0 count = 0 - onroad_conditions = { + onroad_conditions: Dict[str, bool] = { "ignition": False, } - startup_conditions = {} - startup_conditions_prev = {} + startup_conditions: Dict[str, bool] = {} + startup_conditions_prev: Dict[str, bool] = {} off_ts = None started_ts = None @@ -191,6 +192,7 @@ def thermald_thread(): handle_fan = None is_uno = False ui_running_prev = False + engaged_prev = False params = Params() power_monitor = PowerMonitoring() @@ -202,10 +204,6 @@ def thermald_thread(): # TODO: use PI controller for UNO controller = PIController(k_p=0, k_i=2e-3, neg_limit=-80, pos_limit=0, rate=(1 / DT_TRML)) - # Leave flag for loggerd to indicate device was left onroad - if params.get_bool("IsOnroad"): - params.put_bool("BootedOnroad", True) - while True: pandaStates = messaging.recv_sock(pandaState_sock, wait=True) @@ -291,8 +289,12 @@ def thermald_thread(): msg.deviceState.networkInfo = network_info if nvme_temps is not None: msg.deviceState.nvmeTempC = nvme_temps + for i, temp in enumerate(nvme_temps): + statlog.gauge(f"nvme_temperature{i}", temp) if modem_temps is not None: msg.deviceState.modemTempC = modem_temps + for i, temp in enumerate(modem_temps): + statlog.gauge(f"modem_temperature{i}", temp) msg.deviceState.screenBrightnessPercent = HARDWARE.get_screen_brightness() msg.deviceState.batteryPercent = HARDWARE.get_battery_capacity() @@ -354,8 +356,23 @@ def thermald_thread(): if should_start != should_start_prev or (count == 0): params.put_bool("IsOnroad", should_start) params.put_bool("IsOffroad", not should_start) + + params.put_bool("IsEngaged", False) + engaged_prev = False HARDWARE.set_power_save(not should_start) + if sm.updated['controlsState']: + engaged = sm['controlsState'].enabled + if engaged != engaged_prev: + params.put_bool("IsEngaged", engaged) + engaged_prev = engaged + + try: + with open('/dev/kmsg', 'w') as kmsg: + kmsg.write(f"[thermald] engaged: {engaged}") + except Exception: + pass + if should_start: off_ts = None if started_ts is None: @@ -409,6 +426,23 @@ def thermald_thread(): should_start_prev = should_start startup_conditions_prev = startup_conditions.copy() + # log more stats + statlog.gauge("free_space_percent", msg.deviceState.freeSpacePercent) + statlog.gauge("gpu_usage_percent", msg.deviceState.gpuUsagePercent) + statlog.gauge("memory_usage_percent", msg.deviceState.memoryUsagePercent) + for i, usage in enumerate(msg.deviceState.cpuUsagePercent): + statlog.gauge(f"cpu{i}_usage_percent", usage) + for i, temp in enumerate(msg.deviceState.cpuTempC): + statlog.gauge(f"cpu{i}_temperature", temp) + for i, temp in enumerate(msg.deviceState.gpuTempC): + statlog.gauge(f"gpu{i}_temperature", temp) + statlog.gauge("memory_temperature", msg.deviceState.memoryTempC) + statlog.gauge("ambient_temperature", msg.deviceState.ambientTempC) + for i, temp in enumerate(msg.deviceState.pmicTempC): + statlog.gauge(f"pmic{i}_temperature", temp) + statlog.gauge("fan_speed_percent_desired", msg.deviceState.fanSpeedPercentDesired) + statlog.gauge("screen_brightness_percent", msg.deviceState.screenBrightnessPercent) + # report to server once every 10 minutes if (count % int(600. / DT_TRML)) == 0: if EON and started_ts is None and msg.deviceState.memoryUsagePercent > 40: @@ -424,7 +458,7 @@ def thermald_thread(): count += 1 -def main(): +def main() -> NoReturn: thermald_thread() diff --git a/selfdrive/timezoned.py b/selfdrive/timezoned.py index 74da5fe35c..6a7e9122c1 100755 --- a/selfdrive/timezoned.py +++ b/selfdrive/timezoned.py @@ -3,6 +3,7 @@ import json import os import time import subprocess +from typing import NoReturn import requests from timezonefinder import TimezoneFinder @@ -30,7 +31,7 @@ def set_timezone(valid_timezones, timezone): cloudlog.exception(f"Error setting timezone to {timezone}") -def main(): +def main() -> NoReturn: params = Params() tf = TimezoneFinder() diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index a301725bae..df96c222c0 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -7,6 +7,7 @@ import signal import subprocess import time import glob +from typing import NoReturn import sentry_sdk @@ -15,7 +16,7 @@ from common.file_helpers import mkdirs_exists_ok from selfdrive.hardware import TICI, HARDWARE from selfdrive.loggerd.config import ROOT from selfdrive.swaglog import cloudlog -from selfdrive.version import get_branch, get_commit, get_dirty, get_origin, get_version +from selfdrive.version import get_branch, get_commit, is_dirty, get_origin, get_version MAX_SIZE = 100000 * 10 # mal size is 40-100k, allow up to 1M if TICI: @@ -197,7 +198,7 @@ def report_tombstone_apport(fn): pass -def main(): +def main() -> NoReturn: clear_apport_folder() # Clear apport folder on start, otherwise duplicate crashes won't register initial_tombstones = set(get_tombstones()) @@ -207,7 +208,7 @@ def main(): dongle_id = Params().get("DongleId", encoding='utf-8') sentry_sdk.set_user({"id": dongle_id}) - sentry_sdk.set_tag("dirty", get_dirty()) + sentry_sdk.set_tag("dirty", is_dirty()) sentry_sdk.set_tag("origin", get_origin()) sentry_sdk.set_tag("branch", get_branch()) sentry_sdk.set_tag("commit", get_commit()) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 5d656f889e..5f8a96edf0 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1,6 +1,7 @@ moc_* *.moc +_mui watch3 installer/installers/* replay/replay diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 52e4e0c42c..62f93c13a9 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -73,6 +73,9 @@ if GetOption('extras'): # buidl updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) + # build mui + qt_env.Program("_mui", ["mui.cc"], LIBS=qt_libs) + # build installers senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') @@ -117,8 +120,7 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs) replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs) - - qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11']) + qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) if GetOption('test'): qt_env.Program('replay/tests/test_replay', ['replay/tests/test_runner.cc', 'replay/tests/test_replay.cc'], LIBS=[replay_libs]) diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc new file mode 100644 index 0000000000..55b9a47474 --- /dev/null +++ b/selfdrive/ui/mui.cc @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/qt_window.h" + +class StatusBar : public QGraphicsRectItem { + private: + QLinearGradient linear_gradient; + QRadialGradient radial_gradient; + QTimer animation_timer; + const int animation_length = 10; + int animation_index = 0; + + public: + StatusBar(double x, double y, double width, double height) : QGraphicsRectItem {x, y, width, height} { + linear_gradient = QLinearGradient(0, 0, 0, height/2); + linear_gradient.setSpread(QGradient::ReflectSpread); + + radial_gradient = QRadialGradient(width/2, height/2, width/8); + QObject::connect(&animation_timer, &QTimer::timeout, [=]() { + animation_index++; + animation_index %= animation_length; + }); + animation_timer.start(50); + } + + void solidColor(QColor color) { + QColor dark_color = QColor(color); + dark_color.setAlphaF(0.5); + + linear_gradient.setColorAt(0, dark_color); + linear_gradient.setColorAt(1, color); + setBrush(QBrush(linear_gradient)); + } + + // these need to be called continuously for the animations to work. + // can probably clean that up with some more abstractions + void blinkingColor(QColor color) { + QColor dark_color = QColor(color); + dark_color.setAlphaF(0.1); + + int radius = (rect().width() / animation_length) * animation_index; + QPoint center = QPoint(rect().width()/2, rect().height()/2); + radial_gradient.setCenter(center); + radial_gradient.setFocalPoint(center); + radial_gradient.setRadius(radius); + + radial_gradient.setColorAt(1, dark_color); + radial_gradient.setColorAt(0, color); + setBrush(QBrush(radial_gradient)); + } + + void laneChange(cereal::LateralPlan::LaneChangeDirection direction) { + QColor dark_color = QColor(bg_colors[STATUS_ENGAGED]); + dark_color.setAlphaF(0.1); + + int x = (rect().width() / animation_length) * animation_index; + QPoint center = QPoint(((direction == cereal::LateralPlan::LaneChangeDirection::RIGHT) ? x : (rect().width() - x)), rect().height()/2); + radial_gradient.setCenter(center); + radial_gradient.setFocalPoint(center); + radial_gradient.setRadius(rect().width()/5); + + radial_gradient.setColorAt(1, dark_color); + radial_gradient.setColorAt(0, bg_colors[STATUS_ENGAGED]); + setBrush(QBrush(radial_gradient)); + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override { + painter->setPen(QPen()); + painter->setBrush(brush()); + + double rounding_radius = rect().height()/2; + painter->drawRoundedRect(rect(), rounding_radius, rounding_radius); + } +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QWidget w; + setMainWindow(&w); + + w.setStyleSheet("background-color: black;"); + + // our beautiful UI + QVBoxLayout *layout = new QVBoxLayout(&w); + + QGraphicsScene *scene = new QGraphicsScene(); + StatusBar *status_bar = new StatusBar(0, 0, 1000, 50); + scene->addItem(status_bar); + + QGraphicsView *graphics_view = new QGraphicsView(scene); + layout->insertSpacing(0, 400); + layout->addWidget(graphics_view, 0, Qt::AlignCenter); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [=]() { + static SubMaster sm({"deviceState", "controlsState", "lateralPlan"}); + + bool onroad_prev = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + sm.update(0); + + bool onroad = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + + if (onroad) { + auto cs = sm["controlsState"].getControlsState(); + UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::USER_PROMPT) { + status = STATUS_WARNING; + } else if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::CRITICAL) { + status = STATUS_ALERT; + } + + auto lp = sm["lateralPlan"].getLateralPlan(); + if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::PRE_LANE_CHANGE || status == STATUS_ALERT) { + status_bar->blinkingColor(bg_colors[status]); + } else if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_STARTING || + lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_FINISHING) { + status_bar->laneChange(lp.getLaneChangeDirection()); + } else { + status_bar->solidColor(bg_colors[status]); + } + } + + if ((onroad != onroad_prev) || sm.frame < 2) { + Hardware::set_brightness(50); + Hardware::set_display_power(onroad); + } + }); + timer.start(50); + + return a.exec(); +} diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 1e89264952..df4f474943 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -19,7 +19,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { sidebar = new Sidebar(this); main_layout->addWidget(sidebar); - QObject::connect(this, &HomeWindow::update, sidebar, &Sidebar::updateState); QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings); slayout = new QStackedLayout(); @@ -31,15 +30,13 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { onroad = new OnroadWindow(this); slayout->addWidget(onroad); - QObject::connect(this, &HomeWindow::update, onroad, &OnroadWindow::updateStateSignal); - QObject::connect(this, &HomeWindow::offroadTransitionSignal, onroad, &OnroadWindow::offroadTransitionSignal); - driver_view = new DriverViewWindow(this); connect(driver_view, &DriverViewWindow::done, [=] { showDriverView(false); }); slayout->addWidget(driver_view); setAttribute(Qt::WA_NoSystemBackground); + QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition); } void HomeWindow::showSidebar(bool show) { @@ -53,7 +50,6 @@ void HomeWindow::offroadTransition(bool offroad) { } else { slayout->setCurrentWidget(onroad); } - emit offroadTransitionSignal(offroad); } void HomeWindow::showDriverView(bool show) { diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 0e65ef05e9..732071c153 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -43,10 +43,6 @@ signals: void openSettings(); void closeSettings(); - // forwarded signals - void update(const UIState &s); - void offroadTransitionSignal(bool offroad); - public slots: void offroadTransition(bool offroad); void showDriverView(bool show); diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index e1bfe96c8e..5e5fdb40cf 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -106,7 +106,7 @@ void MapWindow::initLayers() { } void MapWindow::timerUpdate() { - if (!QUIState::ui_state.scene.started) { + if (!uiState()->scene.started) { return; } @@ -387,7 +387,7 @@ void MapInstructions::updateDistance(float d) { d = std::max(d, 0.0f); QString distance_str; - if (QUIState::ui_state.scene.is_metric) { + if (uiState()->scene.is_metric) { if (d > 500) { distance_str.setNum(d / 1000, 'f', 1); distance_str += " km"; @@ -498,10 +498,9 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct fn += "turn_straight"; } - QPixmap pix(fn + ICON_SUFFIX); auto icon = new QLabel; int wh = active ? 125 : 75; - icon->setPixmap(pix.scaled(wh, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {wh, wh}, Qt::IgnoreAspectRatio)); icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); lane_layout->addWidget(icon); } @@ -620,7 +619,7 @@ void MapETA::updateETA(float s, float s_typical, float d) { // Distance QString distance_str; float num = 0; - if (QUIState::ui_state.scene.is_metric) { + if (uiState()->scene.is_metric) { num = d / 1000.0; distance_unit->setText("km"); } else { diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 6f03e2ed4a..3ab5b999b2 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -26,7 +26,7 @@ void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { } DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverState"}), QWidget(parent) { - face_img = QImage("../assets/img_driver_face.png").scaled(FACE_IMG_SIZE, FACE_IMG_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); + face_img = loadPixmap("../assets/img_driver_face.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } void DriverViewScene::showEvent(QShowEvent* event) { @@ -97,5 +97,5 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { const int img_x = is_rhd ? rect2.right() - FACE_IMG_SIZE - img_offset : rect2.left() + img_offset; const int img_y = rect2.bottom() - FACE_IMG_SIZE - img_offset; p.setOpacity(face_detected ? 1.0 : 0.3); - p.drawImage(img_x, img_y, face_img); + p.drawPixmap(img_x, img_y, face_img); } diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 8eab76a3dd..4d4a4358ef 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -24,7 +24,7 @@ protected: private: Params params; SubMaster sm; - QImage face_img; + QPixmap face_img; bool is_rhd = false; bool frame_updated = false; }; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index dd519dd661..2d5651dcce 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -158,7 +158,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(roamingToggle); // APN settings - ButtonControl *editApnButton = new ButtonControl("APN settings", "EDIT"); + ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT"); connect(editApnButton, &ButtonControl::clicked, [=]() { const bool roamingEnabled = params.getBool("GsmRoaming"); const QString cur_apn = QString::fromStdString(params.get("GsmApn")); diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index e6593f7679..458464239e 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -16,6 +16,11 @@ TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { } void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { + if (click_timer.elapsed() < 250) { + return; + } + click_timer.restart(); + if (boundingRect[currentIndex].contains(e->x(), e->y())) { if (currentIndex == 9) { const QRect yes = QRect(692, 842, 492, 148); @@ -40,6 +45,7 @@ void TrainingGuide::showEvent(QShowEvent *event) { currentIndex = 0; image.load(img_path + "step0.png"); + click_timer.start(); } void TrainingGuide::paintEvent(QPaintEvent *event) { diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 7ae72649d1..9424c07d19 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -74,6 +75,7 @@ private: QString img_path; QVector boundingRect; + QElapsedTimer click_timer; signals: void completedTraining(); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index eee4dab052..83802a1d90 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -38,7 +38,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { { "IsLdwEnabled", "Enable Lane Departure Warnings", - "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph).", + "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h).", "../assets/offroad/icon_warning.png", }, { @@ -98,7 +98,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); if (!locked) { - connect(parent, &SettingsWindow::offroadTransition, toggle, &ParamControl::setEnabled); + connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); } addItem(toggle); } @@ -144,7 +144,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { addItem(regulatoryBtn); } - QObject::connect(parent, &SettingsWindow::offroadTransition, [=](bool offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { btn->setEnabled(offroad); } @@ -165,13 +165,9 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { QObject::connect(poweroff_btn, &QPushButton::clicked, this, &DevicePanel::poweroff); setStyleSheet(R"( - QPushButton { - height: 120px; - border-radius: 15px; - } - #reboot_btn { background-color: #393939; } + #reboot_btn { height: 120px; border-radius: 15px; background-color: #393939; } #reboot_btn:pressed { background-color: #4a4a4a; } - #poweroff_btn { background-color: #E22C2C; } + #poweroff_btn { height: 120px; border-radius: 15px; background-color: #E22C2C; } #poweroff_btn:pressed { background-color: #FF2424; } )"); addItem(power_layout); @@ -191,8 +187,8 @@ void DevicePanel::updateCalibDescription() { double pitch = calib.getRpyCalib()[1] * (180 / M_PI); double yaw = calib.getRpyCalib()[2] * (180 / M_PI); desc += QString(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); } } catch (kj::Exception) { qInfo() << "invalid CalibrationParams"; @@ -202,10 +198,10 @@ void DevicePanel::updateCalibDescription() { } void DevicePanel::reboot() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { Params().putBool("DoReboot", true); } } @@ -215,10 +211,10 @@ void DevicePanel::reboot() { } void DevicePanel::poweroff() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { Params().putBool("DoShutdown", true); } } @@ -251,7 +247,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { params.putBool("DoUninstall", true); } }); - connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool))); + connect(uiState(), &UIState::offroadTransition, uninstallBtn, &QPushButton::setEnabled); QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn}; for (QWidget* w : widgets) { @@ -290,15 +286,14 @@ void SoftwarePanel::updateLabels() { osVersionLbl->setText(QString::fromStdString(Hardware::get_os_version()).trimmed()); } -QWidget * network_panel(QWidget * parent) { -#ifdef QCOM - QWidget *w = new QWidget(parent); - QVBoxLayout *layout = new QVBoxLayout(w); +C2NetworkPanel::C2NetworkPanel(QWidget *parent) : QWidget(parent) { + QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(50, 0, 50, 0); ListWidget *list = new ListWidget(); list->setSpacing(30); // wifi + tethering buttons +#ifdef QCOM auto wifiBtn = new ButtonControl("Wi-Fi Settings", "OPEN"); QObject::connect(wifiBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_wifi(); }); list->addItem(wifiBtn); @@ -306,17 +301,42 @@ QWidget * network_panel(QWidget * parent) { auto tetheringBtn = new ButtonControl("Tethering Settings", "OPEN"); QObject::connect(tetheringBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_tethering(); }); list->addItem(tetheringBtn); +#endif + ipaddress = new LabelControl("IP Address", ""); + list->addItem(ipaddress); // SSH key management list->addItem(new SshToggle()); list->addItem(new SshControl()); - layout->addWidget(list); layout->addStretch(1); +} + +void C2NetworkPanel::showEvent(QShowEvent *event) { + ipaddress->setText(getIPAddress()); +} + +QString C2NetworkPanel::getIPAddress() { + std::string result = util::check_output("ifconfig wlan0"); + if (result.empty()) return ""; + + const std::string inetaddrr = "inet addr:"; + std::string::size_type begin = result.find(inetaddrr); + if (begin == std::string::npos) return ""; + + begin += inetaddrr.length(); + std::string::size_type end = result.find(' ', begin); + if (end == std::string::npos) return ""; + + return result.substr(begin, end - begin).c_str(); +} + +QWidget *network_panel(QWidget *parent) { +#ifdef QCOM + return new C2NetworkPanel(parent); #else - Networking *w = new Networking(parent); + return new Networking(parent); #endif - return w; } void SettingsWindow::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 7fc5a8581f..c3acf3d94a 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -24,7 +24,6 @@ protected: signals: void closeSettings(); - void offroadTransition(bool offroad); void reviewTrainingGuide(); void showDriverView(); @@ -77,3 +76,14 @@ private: Params params; QFileSystemWatcher *fs_watch; }; + +class C2NetworkPanel: public QWidget { + Q_OBJECT +public: + explicit C2NetworkPanel(QWidget* parent = nullptr); + +private: + void showEvent(QShowEvent *event) override; + QString getIPAddress(); + LabelControl *ipaddress; +}; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 53cc963674..e394d1248a 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -41,8 +41,8 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { alerts->raise(); setAttribute(Qt::WA_OpaquePaintEvent); - QObject::connect(this, &OnroadWindow::updateStateSignal, this, &OnroadWindow::updateState); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, this, &OnroadWindow::offroadTransition); + QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); + QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); } void OnroadWindow::updateState(const UIState &s) { @@ -51,6 +51,8 @@ void OnroadWindow::updateState(const UIState &s) { if (s.sm->updated("controlsState") || !alert.equal({})) { if (alert.type == "controlsUnresponsive") { bgColor = bg_colors[STATUS_ALERT]; + } else if (alert.type == "controlsUnresponsivePermanent") { + bgColor = bg_colors[STATUS_DISENGAGED]; } alerts->updateAlert(alert, bgColor); } @@ -76,10 +78,11 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { - if (map == nullptr && (QUIState::ui_state.has_prime || !MAPBOX_TOKEN.isEmpty())) { + if (map == nullptr && (uiState()->has_prime || !MAPBOX_TOKEN.isEmpty())) { MapWindow * m = new MapWindow(get_mapbox_settings()); m->setFixedWidth(topWidget(this)->width() / 2); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition); + m->offroadTransition(offroad); + QObject::connect(uiState(), &UIState::offroadTransition, m, &MapWindow::offroadTransition); split->addWidget(m, 0, Qt::AlignRight); map = m; } @@ -162,8 +165,8 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { // OnroadHud OnroadHud::OnroadHud(QWidget *parent) : QWidget(parent) { - engage_img = QPixmap("../assets/img_chffr_wheel.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - dm_img = QPixmap("../assets/img_driver_face.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); connect(this, &OnroadHud::valueChanged, [=] { update(); }); } @@ -274,7 +277,7 @@ void NvgWindow::initializeGL() { void NvgWindow::updateFrameMat(int w, int h) { CameraViewWidget::updateFrameMat(w, h); - UIState *s = &QUIState::ui_state; + UIState *s = uiState(); s->fb_w = w; s->fb_h = h; auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; @@ -348,8 +351,8 @@ void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV void NvgWindow::paintGL() { CameraViewWidget::paintGL(); - UIState *s = &QUIState::ui_state; - if (s->scene.world_objects_visible) { + UIState *s = uiState(); + if (s->worldObjectsVisible()) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); @@ -379,6 +382,6 @@ void NvgWindow::paintGL() { void NvgWindow::showEvent(QShowEvent *event) { CameraViewWidget::showEvent(event); - ui_update_params(&QUIState::ui_state); + ui_update_params(uiState()); prev_draw_t = millis_since_boot(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 37460c8ba6..05811c5060 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -97,10 +97,6 @@ private: QWidget *map = nullptr; QHBoxLayout* split; -signals: - void updateStateSignal(const UIState &s); - void offroadTransitionSignal(bool offroad); - private slots: void offroadTransition(bool offroad); void updateState(const UIState &s); diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py index 4dd99951bf..905d41a634 100644 --- a/selfdrive/ui/qt/python_helpers.py +++ b/selfdrive/ui/qt/python_helpers.py @@ -1,4 +1,3 @@ - import os from cffi import FFI diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index d2c0f9bc30..7ef1c833ab 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -5,7 +5,7 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con timer = new QTimer(this); timer->setTimerType(Qt::VeryCoarseTimer); QObject::connect(timer, &QTimer::timeout, [=]() { - if ((!QUIState::ui_state.scene.started || while_onroad) && QUIState::ui_state.awake && !active()) { + if ((!uiState()->scene.started || while_onroad) && uiState()->awake && !active()) { sendRequest(requestURL); } }); diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 73c4e2f8ce..cdff1fce86 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -26,14 +26,16 @@ void Sidebar::drawMetric(QPainter &p, const QString &label, QColor c, int y) { } Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { - home_img = QImage("../assets/images/button_home.png").scaled(180, 180, Qt::KeepAspectRatio, Qt::SmoothTransformation); - settings_img = QImage("../assets/images/button_settings.png").scaled(settings_btn.width(), settings_btn.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + home_img = loadPixmap("../assets/images/button_home.png", {180, 180}); + settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio); connect(this, &Sidebar::valueChanged, [=] { update(); }); setAttribute(Qt::WA_OpaquePaintEvent); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); setFixedWidth(300); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); } void Sidebar::mouseReleaseEvent(QMouseEvent *event) { @@ -88,9 +90,9 @@ void Sidebar::paintEvent(QPaintEvent *event) { // static imgs p.setOpacity(0.65); - p.drawImage(settings_btn.x(), settings_btn.y(), settings_img); + p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img); p.setOpacity(1.0); - p.drawImage(60, 1080 - 180 - 40, home_img); + p.drawPixmap(60, 1080 - 180 - 40, home_img); // network int x = 58; diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 6cea9bdb6d..5d1b921750 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -32,7 +32,7 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void drawMetric(QPainter &p, const QString &label, QColor c, int y); - QImage home_img, settings_img; + QPixmap home_img, settings_img; const QMap network_type = { {cereal::DeviceState::NetworkType::NONE, "--"}, {cereal::DeviceState::NetworkType::WIFI, "Wi-Fi"}, diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc index 8948d2adb7..b9868f6035 100644 --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -19,8 +19,8 @@ TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { setFixedSize(spinner_size); // pre-compute all the track imgs. make this a gif instead? - QPixmap comma_img = QPixmap("../assets/img_spinner_comma.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QPixmap track_img = QPixmap("../assets/img_spinner_track.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); + QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); QPixmap pm(spinner_size); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index b1defb0081..16d5c174b5 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -98,21 +98,6 @@ void initApp() { } } -ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } - -void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { - emit clicked(); -} - -// Fix stylesheets -void ClickableWidget::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - - void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { static std::map levels = { {QtMsgType::QtDebugMsg, CLOUDLOG_DEBUG}, @@ -136,3 +121,11 @@ QWidget* topWidget (QWidget* widget) { while (widget->parentWidget() != nullptr) widget=widget->parentWidget(); return widget; } + +QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMode aspectRatioMode) { + if (size.isEmpty()) { + return QPixmap(fileName); + } else { + return QPixmap(fileName).scaled(size, aspectRatioMode, Qt::SmoothTransformation); + } +} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 588dac704f..1b9461fabf 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -4,8 +4,8 @@ #include #include -#include #include +#include #include #include @@ -21,28 +21,4 @@ QString timeAgo(const QDateTime &date); void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); void initApp(); QWidget* topWidget (QWidget* widget); - - -// convenience class for wrapping layouts -class LayoutWidget : public QWidget { - Q_OBJECT - -public: - LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { - setLayout(l); - }; -}; - -class ClickableWidget : public QWidget { - Q_OBJECT - -public: - ClickableWidget(QWidget *parent = nullptr); - -protected: - void mouseReleaseEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *) override; - -signals: - void clicked(); -}; +QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 4203d146e0..a16923894d 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -31,8 +31,8 @@ const char frame_fragment_shader[] = "#version 150 core\n" #else "#version 300 es\n" -#endif "precision mediump float;\n" +#endif "uniform sampler2D uTexture;\n" "in vec4 vTexCoord;\n" "out vec4 colorOut;\n" @@ -51,28 +51,22 @@ const mat4 device_transform = {{ 0.0, 0.0, 0.0, 1.0, }}; -mat4 get_driver_view_transform() { +mat4 get_driver_view_transform(int screen_width, int screen_height, int stream_width, int stream_height) { const float driver_view_ratio = 1.333; mat4 transform; - if (Hardware::TICI()) { - // from dmonitoring.cc - const int full_width_tici = 1928; - const int full_height_tici = 1208; - const int adapt_width_tici = 954; - const int crop_x_offset = -72; - const int crop_y_offset = -144; - const float yscale = full_height_tici * driver_view_ratio / adapt_width_tici; - const float xscale = yscale*(1080)/(2160)*full_width_tici/full_height_tici; + if (stream_width == TICI_CAM_WIDTH) { + const float yscale = stream_height * driver_view_ratio / tici_dm_crop::width; + const float xscale = yscale*screen_height/screen_width*stream_width/stream_height; transform = (mat4){{ - xscale, 0.0, 0.0, xscale*crop_x_offset/full_width_tici*2, - 0.0, yscale, 0.0, yscale*crop_y_offset/full_height_tici*2, + xscale, 0.0, 0.0, xscale*tici_dm_crop::x_offset/stream_width*2, + 0.0, yscale, 0.0, yscale*tici_dm_crop::y_offset/stream_height*2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; } else { // frame from 4/3 to 16/9 display transform = (mat4){{ - driver_view_ratio*(1080)/(1920), 0.0, 0.0, 0.0, + driver_view_ratio * screen_height / screen_width, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, @@ -178,7 +172,7 @@ void CameraViewWidget::hideEvent(QHideEvent *event) { void CameraViewWidget::updateFrameMat(int w, int h) { if (zoomed_view) { if (stream_type == VISION_STREAM_RGB_FRONT) { - frame_mat = matmul(device_transform, get_driver_view_transform()); + frame_mat = matmul(device_transform, get_driver_view_transform(w, h, stream_width, stream_height)); } else { auto intrinsic_matrix = stream_type == VISION_STREAM_RGB_WIDE ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; float zoom = ZOOM / intrinsic_matrix.v[0]; @@ -205,13 +199,18 @@ void CameraViewWidget::updateFrameMat(int w, int h) { } void CameraViewWidget::paintGL() { - if (latest_texture_id == -1) { - glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); - glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - return; - } + glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); + glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + std::lock_guard lk(lock); + + if (latest_texture_id == -1) return; glViewport(0, 0, width(), height()); + // sync with the PBO + if (wait_fence) { + wait_fence->wait(); + } glBindVertexArray(frame_vao); glActiveTexture(GL_TEXTURE0); @@ -296,20 +295,33 @@ void CameraViewWidget::vipcThread() { } if (VisionBuf *buf = vipc_client->recv(nullptr, 1000)) { - if (!Hardware::EON()) { - void *texture_buffer = gl_buffer->map(QOpenGLBuffer::WriteOnly); - memcpy(texture_buffer, buf->addr, buf->len); - gl_buffer->unmap(); - - // copy pixels from PBO to texture object - glBindTexture(GL_TEXTURE_2D, texture[buf->idx]->frame_tex); - glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buf->width, buf->height, GL_RGB, GL_UNSIGNED_BYTE, 0); - glBindTexture(GL_TEXTURE_2D, 0); - assert(glGetError() == GL_NO_ERROR); - // use glFinish to ensure that the texture has been uploaded. - glFinish(); + { + std::lock_guard lk(lock); + if (!Hardware::EON()) { + void *texture_buffer = gl_buffer->map(QOpenGLBuffer::WriteOnly); + + if (texture_buffer == nullptr) { + LOGE("gl_buffer->map returned nullptr"); + continue; + } + + memcpy(texture_buffer, buf->addr, buf->len); + gl_buffer->unmap(); + + // copy pixels from PBO to texture object + glBindTexture(GL_TEXTURE_2D, texture[buf->idx]->frame_tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buf->width, buf->height, GL_RGB, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + assert(glGetError() == GL_NO_ERROR); + + wait_fence.reset(new WaitFence()); + + // Ensure the fence is in the GPU command queue, or waiting on it might block + // https://www.khronos.org/opengl/wiki/Sync_Object#Flushing_and_contexts + glFlush(); + } + latest_texture_id = buf->idx; } - latest_texture_id = buf->idx; // Schedule update. update() will be invoked on the gui thread. QMetaObject::invokeMethod(this, "update"); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 4cfba3c6fb..03709cbdd8 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -36,11 +36,20 @@ protected: virtual void updateFrameMat(int w, int h); void vipcThread(); + struct WaitFence { + WaitFence() { sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } + ~WaitFence() { glDeleteSync(sync); } + void wait() { glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); } + GLsync sync = 0; + }; + bool zoomed_view; - std::atomic latest_texture_id = -1; + std::mutex lock; + int latest_texture_id = -1; GLuint frame_vao, frame_vbo, frame_ibo; mat4 frame_mat; std::unique_ptr texture[UI_BUF_COUNT]; + std::unique_ptr wait_fence; std::unique_ptr program; QColor bg = QColor("#000000"); @@ -50,7 +59,6 @@ protected: std::atomic stream_type; QThread *vipc_thread = nullptr; - protected slots: void vipcConnected(VisionIpcClient *vipc_client); }; diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 8b09ba2bb3..d4e48fc64f 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -121,3 +121,17 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { opt.initFrom(this); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); } + +ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } + +void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { + emit clicked(); +} + +// Fix stylesheets +void ClickableWidget::paintEvent(QPaintEvent *) { + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index b793326056..13cd55ff3b 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -161,3 +161,27 @@ private: QVBoxLayout outer_layout; QVBoxLayout inner_layout; }; + +// convenience class for wrapping layouts +class LayoutWidget : public QWidget { + Q_OBJECT + +public: + LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { + setLayout(l); + }; +}; + +class ClickableWidget : public QWidget { + Q_OBJECT + +public: + ClickableWidget(QWidget *parent = nullptr); + +protected: + void mouseReleaseEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *) override; + +signals: + void clicked(); +}; diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index ab6dc67d36..2e9a9e6af9 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -314,8 +314,8 @@ void SetupWidget::replyFinished(const QString &response, bool success) { bool prime = json["prime"].toBool(); - if (QUIState::ui_state.has_prime != prime) { - QUIState::ui_state.has_prime = prime; + if (uiState()->has_prime != prime) { + uiState()->has_prime = prime; Params().putBool("HasPrime", prime); } diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 7fe87ce698..0fee52f736 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -12,14 +12,10 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { main_layout->addWidget(homeWindow); QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); - QObject::connect(&qs, &QUIState::uiUpdate, homeWindow, &HomeWindow::update); - QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransition); - QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransitionSignal); settingsWindow = new SettingsWindow(this); main_layout->addWidget(settingsWindow); QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); - QObject::connect(&qs, &QUIState::offroadTransition, settingsWindow, &SettingsWindow::offroadTransition); QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { onboardingWindow->showTrainingGuide(); main_layout->setCurrentWidget(onboardingWindow); @@ -37,17 +33,15 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { main_layout->setCurrentWidget(onboardingWindow); } - device.setAwake(true, true); - QObject::connect(&qs, &QUIState::uiUpdate, &device, &Device::update); - QObject::connect(&qs, &QUIState::offroadTransition, [=](bool offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { if (!offroad) { closeSettings(); } }); - QObject::connect(&device, &Device::displayPowerChanged, [=]() { - if(main_layout->currentWidget() != onboardingWindow) { - closeSettings(); - } + QObject::connect(&device, &Device::interactiveTimout, [=]() { + if (main_layout->currentWidget() == settingsWindow) { + closeSettings(); + } }); // load fonts @@ -80,15 +74,14 @@ void MainWindow::openSettings() { void MainWindow::closeSettings() { main_layout->setCurrentWidget(homeWindow); - if (QUIState::ui_state.scene.started) { + if (uiState()->scene.started) { homeWindow->showSidebar(false); } } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { - // wake screen on tap if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TouchBegin) { - device.setAwake(true, true); + device.resetInteractiveTimout(); } #ifdef QCOM diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 069831cffe..0bd328aa8a 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -19,7 +19,6 @@ private: void closeSettings(); Device device; - QUIState qs; QStackedLayout *main_layout; HomeWindow *homeWindow; diff --git a/selfdrive/ui/replay/camera.cc b/selfdrive/ui/replay/camera.cc index b37332ae7e..1912c9b581 100644 --- a/selfdrive/ui/replay/camera.cc +++ b/selfdrive/ui/replay/camera.cc @@ -3,8 +3,6 @@ #include #include -const int YUV_BUF_COUNT = 50; - CameraServer::CameraServer(std::pair camera_size[MAX_CAMERAS], bool send_yuv) : send_yuv(send_yuv) { for (int i = 0; i < MAX_CAMERAS; ++i) { std::tie(cameras_[i].width, cameras_[i].height) = camera_size[i]; @@ -29,7 +27,7 @@ void CameraServer::startVipcServer() { std::cout << "camera[" << cam.type << "] frame size " << cam.width << "x" << cam.height << std::endl; vipc_server_->create_buffers(cam.rgb_type, UI_BUF_COUNT, true, cam.width, cam.height); if (send_yuv) { - vipc_server_->create_buffers(cam.yuv_type, YUV_BUF_COUNT, false, cam.width, cam.height); + vipc_server_->create_buffers(cam.yuv_type, YUV_BUFFER_COUNT, false, cam.width, cam.height); } if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); diff --git a/selfdrive/ui/replay/filereader.cc b/selfdrive/ui/replay/filereader.cc index 84dc76694b..1585bb42d2 100644 --- a/selfdrive/ui/replay/filereader.cc +++ b/selfdrive/ui/replay/filereader.cc @@ -39,7 +39,7 @@ std::string FileReader::download(const std::string &url, std::atomic *abor if (!result.empty()) { return result; } - if (i != max_retries_) { + if (i != max_retries_ && !(abort && *abort)) { std::cout << "download failed, retrying " << i + 1 << std::endl; } } diff --git a/selfdrive/ui/replay/framereader.cc b/selfdrive/ui/replay/framereader.cc index 32af922f1f..59e43cb58f 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/selfdrive/ui/replay/framereader.cc @@ -3,6 +3,8 @@ #include #include "libyuv.h" +#include "cereal/visionipc/visionbuf.h" + namespace { struct buffer_data { @@ -13,7 +15,8 @@ struct buffer_data { int readPacket(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; - buf_size = std::min((size_t)buf_size, bd->size - bd->offset); + assert(bd->offset <= bd->size); + buf_size = std::min((size_t)buf_size, (size_t)(bd->size - bd->offset)); if (!buf_size) return AVERROR_EOF; memcpy(buf, bd->data + bd->offset, buf_size); @@ -98,6 +101,7 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_cuda, std::at width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; + visionbuf_compute_aligned_width_and_height(width, height, &aligned_width, &aligned_height); if (has_cuda_device && !no_cuda) { if (!initHardwareDecoder(AV_HWDEVICE_TYPE_CUDA)) { @@ -218,19 +222,21 @@ bool FrameReader::copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv) { libyuv::NV12ToI420(f->data[0], f->linesize[0], f->data[1], f->linesize[1], y, width, u, width / 2, v, width / 2, width, height); libyuv::I420ToRGB24(y, width, u, width / 2, v, width / 2, - rgb, width * 3, width, height); + rgb, aligned_width * 3, width, height); } else { if (yuv) { uint8_t *u = yuv + width * height; uint8_t *v = u + (width / 2) * (height / 2); - memcpy(yuv, f->data[0], width * height); - memcpy(u, f->data[1], width / 2 * height / 2); - memcpy(v, f->data[2], width / 2 * height / 2); + libyuv::I420Copy(f->data[0], f->linesize[0], + f->data[1], f->linesize[1], + f->data[2], f->linesize[2], + yuv, width, u, width / 2, v, width / 2, + width, height); } libyuv::I420ToRGB24(f->data[0], f->linesize[0], f->data[1], f->linesize[1], f->data[2], f->linesize[2], - rgb, width * 3, width, height); + rgb, aligned_width * 3, width, height); } return true; } diff --git a/selfdrive/ui/replay/framereader.h b/selfdrive/ui/replay/framereader.h index d572b727e5..8de5c1f93e 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/selfdrive/ui/replay/framereader.h @@ -22,12 +22,13 @@ public: bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, bool no_cuda = false, std::atomic *abort = nullptr); bool get(int idx, uint8_t *rgb, uint8_t *yuv); - int getRGBSize() const { return width * height * 3; } + int getRGBSize() const { return aligned_width * aligned_height * 3; } int getYUVSize() const { return width * height * 3 / 2; } size_t getFrameCount() const { return packets.size(); } bool valid() const { return valid_; } int width = 0, height = 0; + int aligned_width = 0, aligned_height = 0; private: bool initHardwareDecoder(AVHWDeviceType hw_device_type); diff --git a/selfdrive/ui/replay/logreader.cc b/selfdrive/ui/replay/logreader.cc index 8e2836a4ff..d836ef11f8 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/selfdrive/ui/replay/logreader.cc @@ -56,15 +56,17 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool loca } bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { - raw_ = decompressBZ2(data, size); + raw_ = decompressBZ2(data, size, abort); if (raw_.empty()) { - std::cout << "failed to decompress log" << std::endl; + if (!(abort && *abort)) { + std::cout << "failed to decompress log" << std::endl; + } return false; } try { kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); - while (words.size() > 0) { + while (words.size() > 0 && !(abort && *abort)) { #ifdef HAS_MEMORY_RESOURCE Event *evt = new (mbr_) Event(words); @@ -91,11 +93,14 @@ bool LogReader::load(const std::byte *data, size_t size, std::atomic *abor } } catch (const kj::Exception &e) { std::cout << "failed to parse log : " << e.getDescription().cStr() << std::endl; - if (events.empty()) return false; - - std::cout << "read " << events.size() << " events from corrupt log" << std::endl; + if (!events.empty()) { + std::cout << "read " << events.size() << " events from corrupt log" << std::endl; + } } - std::sort(events.begin(), events.end(), Event::lessThan()); - return true; + if (!events.empty() && !(abort && *abort)) { + std::sort(events.begin(), events.end(), Event::lessThan()); + return true; + } + return false; } diff --git a/selfdrive/ui/replay/main.cc b/selfdrive/ui/replay/main.cc index 062837885b..180aecc7e1 100644 --- a/selfdrive/ui/replay/main.cc +++ b/selfdrive/ui/replay/main.cc @@ -18,9 +18,7 @@ void sigHandler(int s) { if (oldt.c_lflag) { tcsetattr(STDIN_FILENO, TCSANOW, &oldt); } - if (replay) { - replay->stop(); - } + replay->stop(); qApp->quit(); } @@ -59,6 +57,10 @@ void keyboardThread(Replay *replay_) { qDebug() << "invalid argument"; } getch(); // remove \n from entering seek + } else if (c == 'e') { + replay_->seekToFlag(FindFlag::nextEngagement); + } else if (c == 'd') { + replay_->seekToFlag(FindFlag::nextDisEngagement); } else if (c == 'm') { replay_->seekTo(+60, true); } else if (c == 'M') { @@ -69,6 +71,14 @@ void keyboardThread(Replay *replay_) { replay_->seekTo(-10, true); } else if (c == 'G') { replay_->seekTo(0, true); + } else if (c == 'x') { + if (replay_->hasFlag(REPLAY_FLAG_FULL_SPEED)) { + replay_->removeFlag(REPLAY_FLAG_FULL_SPEED); + qInfo() << "replay at normal speed"; + } else { + replay_->addFlag(REPLAY_FLAG_FULL_SPEED); + qInfo() << "replay at full speed"; + } } else if (c == ' ') { replay_->pause(!replay_->isPaused()); } @@ -103,6 +113,7 @@ int main(int argc, char *argv[]) { {"qcam", REPLAY_FLAG_QCAMERA, "load qcamera"}, {"yuv", REPLAY_FLAG_SEND_YUV, "send yuv frame"}, {"no-cuda", REPLAY_FLAG_NO_CUDA, "disable CUDA"}, + {"no-vipc", REPLAY_FLAG_NO_VIPC, "do not output video"}, }; QCommandLineParser parser; diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc index e3753ccf20..98e211fb53 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -25,22 +25,24 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s qDebug() << "services " << s; if (sm == nullptr) { - pm = new PubMaster(s); + pm = std::make_unique(s); } route_ = std::make_unique(route, data_dir); - events_ = new std::vector(); + events_ = std::make_unique>(); + new_events_ = std::make_unique>(); + qRegisterMetaType("FindFlag"); connect(this, &Replay::seekTo, this, &Replay::doSeek); + connect(this, &Replay::seekToFlag, this, &Replay::doSeekToFlag); + connect(this, &Replay::stop, this, &Replay::doStop); connect(this, &Replay::segmentChanged, this, &Replay::queueSegment); } Replay::~Replay() { - stop(); - delete pm; - delete events_; + doStop(); } -void Replay::stop() { +void Replay::doStop() { if (!stream_thread_ && segments_.empty()) return; qDebug() << "shutdown: in progress..."; @@ -63,8 +65,10 @@ bool Replay::load() { } for (auto &[n, f] : route_->segments()) { - if ((!f.rlog.isEmpty() || !f.qlog.isEmpty()) && (!f.road_cam.isEmpty() || !f.qcamera.isEmpty())) { - segments_[n] = nullptr; + bool has_log = !f.rlog.isEmpty() || !f.qlog.isEmpty(); + bool has_video = !f.road_cam.isEmpty() || !f.qcamera.isEmpty(); + if (has_log && (has_video || hasFlag(REPLAY_FLAG_NO_VIPC))) { + segments_.insert({n, nullptr}); } } if (segments_.empty()) { @@ -112,6 +116,55 @@ void Replay::doSeek(int seconds, bool relative) { queueSegment(); } +void Replay::doSeekToFlag(FindFlag flag) { + if (flag == FindFlag::nextEngagement) { + qInfo() << "seeking to the next engagement..."; + } else if (flag == FindFlag::nextDisEngagement) { + qInfo() << "seeking to the disengagement..."; + } + + updateEvents([&]() { + if (auto next = find(flag)) { + uint64_t tm = *next - 2 * 1e9; // seek to 2 seconds before next + if (tm <= cur_mono_time_) { + return true; + } + + cur_mono_time_ = tm; + current_segment_ = currentSeconds() / 60; + return isSegmentMerged(current_segment_); + } else { + qWarning() << "seeking failed"; + return true; + } + }); + + queueSegment(); +} + +std::optional Replay::find(FindFlag flag) { + // Search in all segments + for (const auto &[n, _] : segments_) { + if (n < current_segment_) continue; + + LogReader log; + bool cache_to_local = true; // cache qlog to local for fast seek + if (!log.load(route_->at(n).qlog.toStdString(), nullptr, cache_to_local, 0, 3)) continue; + + for (const Event *e : log.events) { + if (e->mono_time > cur_mono_time_ && e->which == cereal::Event::Which::CONTROLS_STATE) { + const auto cs = e->event.getControlsState(); + if (flag == FindFlag::nextEngagement && cs.getEnabled()) { + return e->mono_time; + } else if (flag == FindFlag::nextDisEngagement && !cs.getEnabled()) { + return e->mono_time; + } + } + } + } + return std::nullopt; +} + void Replay::pause(bool pause) { updateEvents([=]() { qInfo() << (pause ? "paused..." : "resuming"); @@ -181,28 +234,27 @@ void Replay::queueSegment() { void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { // merge 3 segments in sequence. std::vector segments_need_merge; + size_t new_events_size = 0; for (auto it = begin; it != end && it->second->isLoaded() && segments_need_merge.size() < 3; ++it) { segments_need_merge.push_back(it->first); + new_events_size += it->second->log->events.size(); } if (segments_need_merge != segments_merged_) { qDebug() << "merge segments" << segments_need_merge; - std::vector *new_events = new std::vector(); - new_events->reserve(std::accumulate(segments_need_merge.begin(), segments_need_merge.end(), 0, - [=](int v, int n) { return v + segments_[n]->log->events.size(); })); + new_events_->clear(); + new_events_->reserve(new_events_size); for (int n : segments_need_merge) { - auto &e = segments_[n]->log->events; - auto middle = new_events->insert(new_events->end(), e.begin(), e.end()); - std::inplace_merge(new_events->begin(), middle, new_events->end(), Event::lessThan()); + const auto &e = segments_[n]->log->events; + auto middle = new_events_->insert(new_events_->end(), e.begin(), e.end()); + std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); } - auto prev_events = events_; updateEvents([&]() { - events_ = new_events; + events_.swap(new_events_); segments_merged_ = segments_need_merge; return true; }); - delete prev_events; } } @@ -224,13 +276,15 @@ void Replay::startStream(const Segment *cur_segment) { } // start camera server - std::pair camera_size[MAX_CAMERAS] = {}; - for (auto type : ALL_CAMERAS) { - if (auto &fr = cur_segment->frames[type]) { - camera_size[type] = {fr->width, fr->height}; + if (!hasFlag(REPLAY_FLAG_NO_VIPC)) { + std::pair camera_size[MAX_CAMERAS] = {}; + for (auto type : ALL_CAMERAS) { + if (auto &fr = cur_segment->frames[type]) { + camera_size[type] = {fr->width, fr->height}; + } } + camera_server_ = std::make_unique(camera_size, hasFlag(REPLAY_FLAG_SEND_YUV)); } - camera_server_ = std::make_unique(camera_size, flags_ & REPLAY_FLAG_SEND_YUV); // start stream thread stream_thread_ = new QThread(); @@ -258,8 +312,8 @@ void Replay::publishFrame(const Event *e) { {cereal::Event::DRIVER_ENCODE_IDX, DriverCam}, {cereal::Event::WIDE_ROAD_ENCODE_IDX, WideRoadCam}, }; - if ((e->which == cereal::Event::DRIVER_ENCODE_IDX && !(flags_ & REPLAY_FLAG_DCAM)) || - (e->which == cereal::Event::WIDE_ROAD_ENCODE_IDX && !(flags_ & REPLAY_FLAG_ECAM))) { + if ((e->which == cereal::Event::DRIVER_ENCODE_IDX && !hasFlag(REPLAY_FLAG_DCAM)) || + (e->which == cereal::Event::WIDE_ROAD_ENCODE_IDX && !hasFlag(REPLAY_FLAG_ECAM))) { return; } auto eidx = capnp::AnyStruct::Reader(e->event).getPointerSection()[0].getAs(); @@ -320,21 +374,26 @@ void Replay::stream() { // reset start times evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); - } else if (behind_ns > 0) { + } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { precise_nano_sleep(behind_ns); } - if (evt->frame) { - publishFrame(evt); - } else { + if (!evt->frame) { publishMessage(evt); + } else if (camera_server_) { + if (hasFlag(REPLAY_FLAG_FULL_SPEED)) { + camera_server_->waitFinish(); + } + publishFrame(evt); } } } // wait for frame to be sent before unlock.(frameReader may be deleted after unlock) - camera_server_->waitFinish(); + if (camera_server_) { + camera_server_->waitFinish(); + } - if (eit == events_->end() && !(flags_ & REPLAY_FLAG_NO_LOOP)) { + if (eit == events_->end() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { int last_segment = segments_.rbegin()->first; if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) { qInfo() << "reaches the end of route, restart from beginning"; diff --git a/selfdrive/ui/replay/replay.h b/selfdrive/ui/replay/replay.h index 2d30d90e4b..4f97990506 100644 --- a/selfdrive/ui/replay/replay.h +++ b/selfdrive/ui/replay/replay.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include "selfdrive/ui/replay/camera.h" @@ -17,6 +19,13 @@ enum REPLAY_FLAGS { REPLAY_FLAG_QCAMERA = 0x0040, REPLAY_FLAG_SEND_YUV = 0x0080, REPLAY_FLAG_NO_CUDA = 0x0100, + REPLAY_FLAG_FULL_SPEED = 0x0200, + REPLAY_FLAG_NO_VIPC = 0x0400, +}; + +enum class FindFlag { + nextEngagement, + nextDisEngagement }; class Replay : public QObject { @@ -28,21 +37,28 @@ public: ~Replay(); bool load(); void start(int seconds = 0); - void stop(); void pause(bool pause); bool isPaused() const { return paused_; } + inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } + inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } + inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } signals: void segmentChanged(); void seekTo(int seconds, bool relative); + void seekToFlag(FindFlag flag); + void stop(); protected slots: void queueSegment(); + void doStop(); void doSeek(int seconds, bool relative); + void doSeekToFlag(FindFlag flag); void segmentLoadFinished(bool sucess); protected: typedef std::map> SegmentMap; + std::optional find(FindFlag flag); void startStream(const Segment *cur_segment); void stream(); void setCurrentSegment(int n); @@ -69,14 +85,15 @@ protected: bool events_updated_ = false; uint64_t route_start_ts_ = 0; uint64_t cur_mono_time_ = 0; - std::vector *events_ = nullptr; + std::unique_ptr> events_; + std::unique_ptr> new_events_; std::vector segments_merged_; // messaging SubMaster *sm = nullptr; - PubMaster *pm = nullptr; + std::unique_ptr pm; std::vector sockets_; std::unique_ptr route_; std::unique_ptr camera_server_; - uint32_t flags_ = REPLAY_FLAG_NONE; + std::atomic flags_ = REPLAY_FLAG_NONE; }; diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index 23c27073cb..fe6e21a91a 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -91,16 +91,16 @@ void Route::addFileToSegment(int n, const QString &file) { Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) { // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog - const QString file_list[] = { + const std::array file_list = { (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam, flags & REPLAY_FLAG_DCAM ? files.driver_cam : "", flags & REPLAY_FLAG_ECAM ? files.wide_road_cam : "", files.rlog.isEmpty() ? files.qlog : files.rlog, }; - for (int i = 0; i < std::size(file_list); i++) { - if (!file_list[i].isEmpty()) { - loading_++; - synchronizer_.addFuture(QtConcurrent::run([=] { loadFile(i, file_list[i].toStdString()); })); + for (int i = 0; i < file_list.size(); ++i) { + if (!file_list[i].isEmpty() && (!(flags & REPLAY_FLAG_NO_VIPC) || i >= MAX_CAMERAS)) { + ++loading_; + synchronizer_.addFuture(QtConcurrent::run(this, &Segment::loadFile, i, file_list[i].toStdString())); } } } diff --git a/selfdrive/ui/replay/tests/test_replay.cc b/selfdrive/ui/replay/tests/test_replay.cc index 5b9b7cdeb7..6063dfe7d5 100644 --- a/selfdrive/ui/replay/tests/test_replay.cc +++ b/selfdrive/ui/replay/tests/test_replay.cc @@ -17,11 +17,17 @@ TEST_CASE("httpMultiPartDownload") { const size_t chunk_size = 5 * 1024 * 1024; std::string content; SECTION("download to file") { - REQUIRE(httpDownload(TEST_RLOG_URL, filename, chunk_size)); + bool ret = false; + for (int i = 0; i < 3 && !ret; ++i) { + ret = httpDownload(TEST_RLOG_URL, filename, chunk_size); + } + REQUIRE(ret); content = util::read_file(filename); } SECTION("download to buffer") { - content = httpGet(TEST_RLOG_URL, chunk_size); + for (int i = 0; i < 3 && content.empty(); ++i) { + content = httpGet(TEST_RLOG_URL, chunk_size); + } REQUIRE(!content.empty()); } REQUIRE(content.size() == 9112651); diff --git a/selfdrive/ui/replay/util.cc b/selfdrive/ui/replay/util.cc index d6791465f2..d6eba2e2a8 100644 --- a/selfdrive/ui/replay/util.cc +++ b/selfdrive/ui/replay/util.cc @@ -17,13 +17,14 @@ namespace { -static std::atomic enable_http_logging = false; - struct CURLGlobalInitializer { CURLGlobalInitializer() { curl_global_init(CURL_GLOBAL_DEFAULT); } ~CURLGlobalInitializer() { curl_global_cleanup(); } }; +static CURLGlobalInitializer curl_initializer; +static std::atomic enable_http_logging = false; + template struct MultiPartWriter { T *buf; @@ -68,7 +69,7 @@ std::string formattedDataSize(size_t size) { } // namespace -size_t getRemoteFileSize(const std::string &url) { +size_t getRemoteFileSize(const std::string &url, std::atomic *abort) { CURL *curl = curl_easy_init(); if (!curl) return -1; @@ -76,14 +77,20 @@ size_t getRemoteFileSize(const std::string &url) { curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, dumy_write_cb); curl_easy_setopt(curl, CURLOPT_HEADER, 1); curl_easy_setopt(curl, CURLOPT_NOBODY, 1); - CURLcode res = curl_easy_perform(curl); - double content_length = -1; - if (res == CURLE_OK) { - curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); - } else { - std::cout << "Download failed: error code: " << res << std::endl; + + CURLM *cm = curl_multi_init(); + curl_multi_add_handle(cm, curl); + int still_running = 1; + while (still_running > 0 && !(abort && *abort)) { + CURLMcode mc = curl_multi_perform(cm, &still_running); + if (!mc) curl_multi_wait(cm, nullptr, 0, 1000, nullptr); } + + double content_length = -1; + curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &content_length); + curl_multi_remove_handle(cm, curl); curl_easy_cleanup(curl); + curl_multi_cleanup(cm); return content_length > 0 ? (size_t)content_length : 0; } @@ -98,8 +105,6 @@ void enableHttpLogging(bool enable) { template bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t content_length, std::atomic *abort) { - static CURLGlobalInitializer curl_initializer; - int parts = 1; if (chunk_size > 0 && content_length > 10 * 1024 * 1024) { parts = std::nearbyint(content_length / (float)chunk_size); @@ -177,7 +182,7 @@ bool httpDownload(const std::string &url, T &buf, size_t chunk_size, size_t cont } std::string httpGet(const std::string &url, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url); + size_t size = getRemoteFileSize(url, abort); if (size == 0) return {}; std::string result(size, '\0'); @@ -185,7 +190,7 @@ std::string httpGet(const std::string &url, size_t chunk_size, std::atomic } bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size, std::atomic *abort) { - size_t size = getRemoteFileSize(url); + size_t size = getRemoteFileSize(url, abort); if (size == 0) return false; std::ofstream of(file, std::ios::binary | std::ios::out); @@ -193,11 +198,11 @@ bool httpDownload(const std::string &url, const std::string &file, size_t chunk_ return httpDownload(url, of, chunk_size, size, abort); } -std::string decompressBZ2(const std::string &in) { - return decompressBZ2((std::byte *)in.data(), in.size()); +std::string decompressBZ2(const std::string &in, std::atomic *abort) { + return decompressBZ2((std::byte *)in.data(), in.size(), abort); } -std::string decompressBZ2(const std::byte *in, size_t in_size) { +std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort) { if (in_size == 0) return {}; bz_stream strm = {}; @@ -223,10 +228,10 @@ std::string decompressBZ2(const std::byte *in, size_t in_size) { if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { out.resize(out.size() * 2); } - } while (bzerror == BZ_OK); + } while (bzerror == BZ_OK && !(abort && *abort)); BZ2_bzDecompressEnd(&strm); - if (bzerror == BZ_STREAM_END) { + if (bzerror == BZ_STREAM_END && !(abort && *abort)) { out.resize(strm.total_out_lo32); return out; } diff --git a/selfdrive/ui/replay/util.h b/selfdrive/ui/replay/util.h index 726e65cb94..cd4b179cdc 100644 --- a/selfdrive/ui/replay/util.h +++ b/selfdrive/ui/replay/util.h @@ -5,10 +5,10 @@ std::string sha256(const std::string &str); void precise_nano_sleep(long sleep_ns); -std::string decompressBZ2(const std::string &in); -std::string decompressBZ2(const std::byte *in, size_t in_size); +std::string decompressBZ2(const std::string &in, std::atomic *abort = nullptr); +std::string decompressBZ2(const std::byte *in, size_t in_size, std::atomic *abort = nullptr); void enableHttpLogging(bool enable); std::string getUrlWithoutQuery(const std::string &url); -size_t getRemoteFileSize(const std::string &url); +size_t getRemoteFileSize(const std::string &url, std::atomic *abort = nullptr); std::string httpGet(const std::string &url, size_t chunk_size = 0, std::atomic *abort = nullptr); bool httpDownload(const std::string &url, const std::string &file, size_t chunk_size = 0, std::atomic *abort = nullptr); diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 4e88f41b1c..f303aa92bf 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -67,7 +67,7 @@ void Sound::setAlert(const Alert &alert) { for (auto &[s, loops] : sounds) { // Only stop repeating sounds if (s->loopsRemaining() > 1 || s->loopsRemaining() == QSoundEffect::Infinite) { - s->setLoopCount(0); + s->stop(); } } diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index b493c3a3d8..82b360fd38 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -16,7 +16,7 @@ const std::tuple sound_list[] = { {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite}, {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite}, - {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", 10}, + {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite}, }; class Sound : public QObject { diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index 6b3453026b..6b6aea4477 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -18,7 +18,7 @@ if __name__ == "__main__": while True: print("setting alert update") params.put_bool("UpdateAvailable", True) - r = open(os.path.join(BASEDIR, "RELEASES.md"), "r").read() + r = open(os.path.join(BASEDIR, "RELEASES.md")).read() r = r[:r.find('\n\n')] # Slice latest release notes params.put("ReleaseNotes", r + "\n") diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 53d73c1043..c33b61c19e 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -3,6 +3,8 @@ #include #include +#include + #include "common/transformations/orientation.hpp" #include "selfdrive/common/params.h" #include "selfdrive/common/swaglog.h" @@ -36,17 +38,17 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, static int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height) { const auto line_x = line.getX(); int max_idx = 0; - for (int i = 0; i < TRAJECTORY_SIZE && line_x[i] < path_height; ++i) { + for (int i = 1; i < TRAJECTORY_SIZE && line_x[i] <= path_height; ++i) { max_idx = i; } return max_idx; } -static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, std::optional line) { +static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::ModelDataV2::XYZTData::Reader &line) { for (int i = 0; i < 2; ++i) { auto lead_data = (i == 0) ? radar_state.getLeadOne() : radar_state.getLeadTwo(); if (lead_data.getStatus()) { - float z = line ? (*line).getZ()[get_path_length_idx(*line, lead_data.getDRel())] : 0.0; + float z = line.getZ()[get_path_length_idx(line, lead_data.getDRel())]; calib_frame_to_full_frame(s, lead_data.getDRel(), -lead_data.getYRel(), z + 1.22, &s->scene.lead_vertices[i]); } } @@ -106,20 +108,14 @@ static void update_sockets(UIState *s) { static void update_state(UIState *s) { SubMaster &sm = *(s->sm); UIScene &scene = s->scene; - s->running_time = 1e-9 * (nanos_since_boot() - sm["deviceState"].getDeviceState().getStartedMonoTime()); if (sm.updated("modelV2")) { update_model(s, sm["modelV2"].getModelV2()); } - if (sm.updated("radarState")) { - std::optional line; - if (sm.rcv_frame("modelV2") > 0) { - line = sm["modelV2"].getModelV2().getPosition(); - } - update_leads(s, sm["radarState"].getRadarState(), line); + if (sm.updated("radarState") && sm.rcv_frame("modelV2") >= s->scene.started_frame) { + update_leads(s, sm["radarState"].getRadarState(), sm["modelV2"].getModelV2().getPosition()); } if (sm.updated("liveCalibration")) { - scene.world_objects_visible = true; auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); Eigen::Vector3d rpy; rpy << rpy_list[0], rpy_list[1], rpy_list[2]; @@ -190,68 +186,66 @@ void ui_update_params(UIState *s) { s->scene.is_metric = Params().getBool("IsMetric"); } -static void update_status(UIState *s) { - if (s->scene.started && s->sm->updated("controlsState")) { - auto controls_state = (*s->sm)["controlsState"].getControlsState(); +void UIState::updateStatus() { + if (scene.started && sm->updated("controlsState")) { + auto controls_state = (*sm)["controlsState"].getControlsState(); auto alert_status = controls_state.getAlertStatus(); if (alert_status == cereal::ControlsState::AlertStatus::USER_PROMPT) { - s->status = STATUS_WARNING; + status = STATUS_WARNING; } else if (alert_status == cereal::ControlsState::AlertStatus::CRITICAL) { - s->status = STATUS_ALERT; + status = STATUS_ALERT; } else { - s->status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } } // Handle onroad/offroad transition - static bool started_prev = false; - if (s->scene.started != started_prev) { - if (s->scene.started) { - s->status = STATUS_DISENGAGED; - s->scene.started_frame = s->sm->frame; - s->scene.end_to_end = Params().getBool("EndToEndToggle"); - s->wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; + if (scene.started != started_prev) { + if (scene.started) { + status = STATUS_DISENGAGED; + scene.started_frame = sm->frame; + scene.end_to_end = Params().getBool("EndToEndToggle"); + wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; } - // Invisible until we receive a calibration message. - s->scene.world_objects_visible = false; + started_prev = scene.started; + emit offroadTransition(!scene.started); + } else if (sm->frame == 1) { + emit offroadTransition(!scene.started); } - started_prev = s->scene.started; } - -QUIState::QUIState(QObject *parent) : QObject(parent) { - ui_state.sm = std::make_unique>({ +UIState::UIState(QObject *parent) : QObject(parent) { + sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", }); Params params; - ui_state.wide_camera = Hardware::TICI() ? params.getBool("EnableWideCamera") : false; - ui_state.has_prime = params.getBool("HasPrime"); + wide_camera = Hardware::TICI() ? params.getBool("EnableWideCamera") : false; + has_prime = params.getBool("HasPrime"); // update timer timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &QUIState::update); + QObject::connect(timer, &QTimer::timeout, this, &UIState::update); timer->start(1000 / UI_FREQ); } -void QUIState::update() { - update_sockets(&ui_state); - update_state(&ui_state); - update_status(&ui_state); - - if (ui_state.scene.started != started_prev || ui_state.sm->frame == 1) { - started_prev = ui_state.scene.started; - emit offroadTransition(!ui_state.scene.started); - } +void UIState::update() { + update_sockets(this); + update_state(this); + updateStatus(); - if (ui_state.sm->frame % UI_FREQ == 0) { + if (sm->frame % UI_FREQ == 0) { watchdog_kick(); } - emit uiUpdate(ui_state); + emit uiUpdate(*this); } Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { + setAwake(true); + resetInteractiveTimout(); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); } void Device::update(const UIState &s) { @@ -259,20 +253,20 @@ void Device::update(const UIState &s) { updateWakefulness(s); // TODO: remove from UIState and use signals - QUIState::ui_state.awake = awake; + uiState()->awake = awake; } -void Device::setAwake(bool on, bool reset) { +void Device::setAwake(bool on) { if (on != awake) { awake = on; Hardware::set_display_power(awake); LOGD("setting display power %d", awake); emit displayPowerChanged(awake); } +} - if (reset) { - awake_timeout = 30 * UI_FREQ; - } +void Device::resetInteractiveTimout() { + interactive_timeout = (ignition_on ? 10 : 30) * UI_FREQ; } void Device::updateBrightness(const UIState &s) { @@ -290,16 +284,6 @@ void Device::updateBrightness(const UIState &s) { // Scale back to 10% to 100% clipped_brightness = std::clamp(100.0f * clipped_brightness, 10.0f, 100.0f); - - // Limit brightness if running for too long - if (Hardware::TICI()) { - const float MAX_BRIGHTNESS_HOURS = 4; - const float HOURLY_BRIGHTNESS_DECREASE = 5; - float ui_running_hours = s.running_time / (60*60); - float anti_burnin_max_percent = std::clamp(100.0f - HOURLY_BRIGHTNESS_DECREASE * (ui_running_hours - MAX_BRIGHTNESS_HOURS), - 30.0f, 100.0f); - clipped_brightness = std::min(clipped_brightness, anti_burnin_max_percent); - } } int brightness = brightness_filter.update(clipped_brightness); @@ -308,23 +292,40 @@ void Device::updateBrightness(const UIState &s) { } if (brightness != last_brightness) { - std::thread{Hardware::set_brightness, brightness}.detach(); + if (!brightness_future.isRunning()) { + brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); + last_brightness = brightness; + } } - last_brightness = brightness; +} + +bool Device::motionTriggered(const UIState &s) { + static float accel_prev = 0; + static float gyro_prev = 0; + + bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2; + bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15; + + gyro_prev = s.scene.gyro_sensor; + accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples; + + return (!awake && accel_trigger && gyro_trigger); } void Device::updateWakefulness(const UIState &s) { - awake_timeout = std::max(awake_timeout - 1, 0); - - bool should_wake = s.scene.started || s.scene.ignition; - if (!should_wake) { - // tap detection while display is off - bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2; - bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15; - should_wake = accel_trigger && gyro_trigger; - gyro_prev = s.scene.gyro_sensor; - accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples; + bool ignition_just_turned_off = !s.scene.ignition && ignition_on; + ignition_on = s.scene.ignition; + + if (ignition_just_turned_off || motionTriggered(s)) { + resetInteractiveTimout(); + } else if (interactive_timeout > 0 && --interactive_timeout == 0) { + emit interactiveTimout(); } - setAwake(awake_timeout, should_wake); + setAwake(s.scene.ignition || interactive_timeout > 0); +} + +UIState *uiState() { + static UIState ui_state; + return &ui_state; } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index fe9e96462f..bc15257a08 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -32,29 +33,38 @@ struct Alert { QString type; cereal::ControlsState::AlertSize size; AudibleAlert sound; + bool equal(const Alert &a2) { return text1 == a2.text1 && text2 == a2.text2 && type == a2.type && sound == a2.sound; } static Alert get(const SubMaster &sm, uint64_t started_frame) { + const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); if (sm.updated("controlsState")) { - const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); return {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(), cs.getAlertType().cStr(), cs.getAlertSize(), cs.getAlertSound()}; } else if ((sm.frame - started_frame) > 5 * UI_FREQ) { const int CONTROLS_TIMEOUT = 5; + const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; + // Handle controls timeout if (sm.rcv_frame("controlsState") < started_frame) { // car is started, but controlsState hasn't been seen at all return {"openpilot Unavailable", "Waiting for controls to start", "controlsWaiting", cereal::ControlsState::AlertSize::MID, AudibleAlert::NONE}; - } else if ((nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT) { + } else if (controls_missing > CONTROLS_TIMEOUT) { // car is started, but controls is lagging or died - return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", - "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, - AudibleAlert::WARNING_IMMEDIATE}; + if (cs.getEnabled() && (controls_missing - CONTROLS_TIMEOUT) < 10) { + return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", + "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, + AudibleAlert::WARNING_IMMEDIATE}; + } else { + return {"Controls Unresponsive", "Reboot Device", + "controlsUnresponsivePermanent", cereal::ControlsState::AlertSize::MID, + AudibleAlert::NONE}; + } } } return {}; @@ -82,8 +92,6 @@ typedef struct { typedef struct UIScene { mat3 view_from_calib; - bool world_objects_visible; - cereal::PandaState::PandaType pandaType; // modelV2 @@ -101,7 +109,16 @@ typedef struct UIScene { uint64_t started_frame; } UIScene; -typedef struct UIState { +class UIState : public QObject { + Q_OBJECT + +public: + UIState(QObject* parent = 0); + void updateStatus(); + inline bool worldObjectsVisible() const { + return sm->rcv_frame("liveCalibration") > scene.started_frame; + }; + int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -114,19 +131,6 @@ typedef struct UIState { QTransform car_space_transform; bool wide_camera; - - float running_time; -} UIState; - - -class QUIState : public QObject { - Q_OBJECT - -public: - QUIState(QObject* parent = 0); - - // TODO: get rid of this, only use signal - inline static UIState ui_state = {0}; signals: void uiUpdate(const UIState &s); @@ -140,6 +144,7 @@ private: bool started_prev = true; }; +UIState *uiState(); // device management class @@ -154,22 +159,23 @@ private: const float accel_samples = 5*UI_FREQ; bool awake = false; - int awake_timeout = 0; - float accel_prev = 0; - float gyro_prev = 0; + int interactive_timeout = 0; + bool ignition_on = false; int last_brightness = 0; FirstOrderFilter brightness_filter; - - QTimer *timer; + QFuture brightness_future; void updateBrightness(const UIState &s); void updateWakefulness(const UIState &s); + bool motionTriggered(const UIState &s); + void setAwake(bool on); signals: void displayPowerChanged(bool on); + void interactiveTimout(); public slots: - void setAwake(bool on, bool reset); + void resetInteractiveTimout(); void update(const UIState &s); }; diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 0a203e3cd0..1a87798619 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -40,7 +40,7 @@ from common.params import Params from selfdrive.hardware import EON, TICI, HARDWARE from selfdrive.swaglog import cloudlog from selfdrive.controls.lib.alertmanager import set_offroad_alert -from selfdrive.version import get_tested_branch +from selfdrive.version import is_tested_branch LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") @@ -142,7 +142,7 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - now = datetime.datetime.utcnow() dt = now - last_update if failed_count > 15 and exception is not None: - if get_tested_branch(): + if is_tested_branch(): extra_text = "Ensure the software is correctly installed" else: extra_text = exception @@ -342,7 +342,7 @@ def fetch_update(wait_helper: WaitTimeHelper) -> bool: new_version = cur_hash != upstream_hash git_fetch_result = check_git_fetch_result(git_fetch_output) - cloudlog.info("comparing %s to %s" % (cur_hash, upstream_hash)) + cloudlog.info(f"comparing {cur_hash} to {upstream_hash}") if new_version or git_fetch_result: cloudlog.info("Running update") @@ -370,7 +370,7 @@ def fetch_update(wait_helper: WaitTimeHelper) -> bool: return new_version -def main(): +def main() -> None: params = Params() if params.get_bool("DisableUpdates"): @@ -380,7 +380,7 @@ def main(): ov_lock_fd = open(LOCK_FILE, 'w') try: fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError as e: + except OSError as e: raise RuntimeError("couldn't get overlay lock; is another instance running?") from e # Set low io priority @@ -400,7 +400,7 @@ def main(): overlay_init.unlink(missing_ok=True) first_run = True - last_fetch_time = 0 + last_fetch_time = 0.0 update_failed_count = 0 # Set initial params for offroad alerts @@ -459,7 +459,11 @@ def main(): exception = str(e) overlay_init.unlink(missing_ok=True) - set_params(new_version, update_failed_count, exception) + try: + set_params(new_version, update_failed_count, exception) + except Exception: + cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") + wait_helper.sleep(60) dismount_overlay() diff --git a/selfdrive/version.py b/selfdrive/version.py index 2ddfc93c69..0c1a44f236 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -7,7 +7,6 @@ from functools import lru_cache from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog - TESTED_BRANCHES = ['devel', 'release2-staging', 'release3-staging', 'dashcam-staging', 'release2', 'release3', 'dashcam'] training_version: bytes = b"0.2.0" @@ -54,20 +53,32 @@ def get_origin(default: Optional[str] = None) -> Optional[str]: return run_cmd_default(["git", "config", "--get", "remote.origin.url"], default=default) +@cache +def get_normalized_origin(default: Optional[str] = None) -> Optional[str]: + return get_origin()\ + .replace("git@", "", 1)\ + .replace(".git", "", 1)\ + .replace("https://", "", 1)\ + .replace(":", "/", 1) + + @cache def get_version() -> str: with open(os.path.join(os.path.dirname(os.path.abspath(__file__)), "common", "version.h")) as _versionf: version = _versionf.read().split('"')[1] return version +@cache +def get_short_version() -> str: + return get_version().split('-')[0] @cache -def get_prebuilt() -> bool: +def is_prebuilt() -> bool: return os.path.exists(os.path.join(BASEDIR, 'prebuilt')) @cache -def get_comma_remote() -> bool: +def is_comma_remote() -> bool: origin = get_origin() if origin is None: return False @@ -76,12 +87,12 @@ def get_comma_remote() -> bool: @cache -def get_tested_branch() -> bool: +def is_tested_branch() -> bool: return get_short_branch() in TESTED_BRANCHES @cache -def get_dirty() -> bool: +def is_dirty() -> bool: origin = get_origin() branch = get_branch() if (origin is None) or (branch is None): @@ -90,7 +101,7 @@ def get_dirty() -> bool: dirty = False try: # Actually check dirty files - if not get_prebuilt(): + if not is_prebuilt(): # This is needed otherwise touched files might show up as modified try: subprocess.check_call(["git", "update-index", "--refresh"]) @@ -99,16 +110,7 @@ def get_dirty() -> bool: dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"]) != 0) - # Log dirty files - if dirty and get_comma_remote(): - try: - dirty_files = run_cmd(["git", "diff-index", branch, "--"]) - cloudlog.event("dirty comma branch", version=get_version(), dirty=dirty, origin=origin, branch=branch, - dirty_files=dirty_files, commit=get_commit(), origin_commit=get_commit(branch)) - except subprocess.CalledProcessError: - pass - - dirty = dirty or (not get_comma_remote()) + dirty = dirty or (not is_comma_remote()) dirty = dirty or ('master' in branch) except subprocess.CalledProcessError: @@ -125,9 +127,11 @@ if __name__ == "__main__": params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - print("Dirty: %s" % get_dirty()) - print("Version: %s" % get_version()) - print("Origin: %s" % get_origin()) - print("Branch: %s" % get_branch()) - print("Short branch: %s" % get_short_branch()) - print("Prebuilt: %s" % get_prebuilt()) + print(f"Dirty: {is_dirty()}") + print(f"Version: {get_version()}") + print(f"Short version: {get_short_version()}") + print(f"Origin: {get_origin()}") + print(f"Normalized origin: {get_normalized_origin()}") + print(f"Branch: {get_branch()}") + print(f"Short branch: {get_short_branch()}") + print(f"Prebuilt: {is_prebuilt()}") diff --git a/third_party/acados/Darwin/lib/libacados.dylib b/third_party/acados/Darwin/lib/libacados.dylib new file mode 100755 index 0000000000..056074e168 Binary files /dev/null and b/third_party/acados/Darwin/lib/libacados.dylib differ diff --git a/third_party/acados/Darwin/lib/libblasfeo.dylib b/third_party/acados/Darwin/lib/libblasfeo.dylib new file mode 100755 index 0000000000..e984307a2c Binary files /dev/null and b/third_party/acados/Darwin/lib/libblasfeo.dylib differ diff --git a/third_party/acados/Darwin/lib/libhpipm.dylib b/third_party/acados/Darwin/lib/libhpipm.dylib new file mode 100755 index 0000000000..a600c765bb Binary files /dev/null and b/third_party/acados/Darwin/lib/libhpipm.dylib differ diff --git a/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib new file mode 100755 index 0000000000..185100d2cd Binary files /dev/null and b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib differ diff --git a/third_party/acados/Darwin/lib/libqpOASES_e.dylib b/third_party/acados/Darwin/lib/libqpOASES_e.dylib new file mode 120000 index 0000000000..e3d94c4bc2 --- /dev/null +++ b/third_party/acados/Darwin/lib/libqpOASES_e.dylib @@ -0,0 +1 @@ +libqpOASES_e.3.1.dylib \ No newline at end of file diff --git a/third_party/acados/Darwin/t_renderer b/third_party/acados/Darwin/t_renderer new file mode 100755 index 0000000000..7ee3eda293 Binary files /dev/null and b/third_party/acados/Darwin/t_renderer differ diff --git a/tools/README.md b/tools/README.md index 81bff69e1d..a61d9268d2 100644 --- a/tools/README.md +++ b/tools/README.md @@ -1,68 +1,43 @@ -openpilot tools -============ +# openpilot tools -CTF -============ - -Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). - -SSH -============ - -Connect to your comma device using [SSH](ssh/README.md) +## System Requirements +openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embdedded hardware](https://github.com/commaai/openpilot#running-on-pc). We also have a CI test to verify that openpilot builds on macOS, but the tools are untested. For the best experience, stick to Ubuntu 20.04, otherwise openpilot and the tools should work with minimal to no modifications on macOS and other Linux systems. -System requirements -============ +## Setup your PC -openpilot is developed and tested on **Ubuntu 20.04**, which is the primary development target aside from the [supported embdedded hardware](https://github.com/commaai/openpilot#running-on-pc). We also have a CI test to verify that openpilot builds on macOS, but the tools are untested. For the best experience, stick to Ubuntu 20.04, otherwise openpilot and the tools should work with minimal to no modifications on macOS and other Linux systems. -Setup your PC -============ -1. Clone openpilot into your home directory: +First, clone openpilot: ``` bash cd ~ -git clone --recurse-submodules https://github.com/commaai/openpilot.git -``` - -2. Run the setup script: +git clone https://github.com/commaai/openpilot.git -Ubuntu 20.04 LTS: -``` bash -openpilot/tools/ubuntu_setup.sh -``` -MacOS: -``` bash -openpilot/tools/mac_setup.sh +cd openpilot +git submodule update --init ``` -3. Ensure you have a working OpenCL runtime: - -You can verify your OpenCL installation with the `clinfo` command. +Then, run the setup script: -If you do not have any working platforms, you can download drivers from your GPU vendor's site. -On Ubuntu you can just install one of the packages returned by `apt search opencl-icd`. - -4. Activate the Python environment: +``` bash +# for Ubuntu 20.04 LTS +tools/ubuntu_setup.sh -Execute the following command in root openpilot directory: -```bash -pipenv shell +# for macOS +tools/mac_setup.sh ``` -Your shell prompt should change to something similar to `(openpilot) user@machine:~/openpilot$ `. +Activate a shell with the install Python dependencies: -5. Build openpilot by running SCons in the root of the openpilot directory ``` bash -cd openpilot && scons -j$(nproc) +cd openpilot && pipenv shell ``` -6. Try out some tools! - -NOTE: you can always run `update_requirements.sh` to pull in new python dependencies. +Build openpilot with this command: +``` bash +scons -u -j$(nproc) +``` -Windows ------------- +### Windows Neither openpilot nor any of the tools are developed or tested on Windows, but the [Windows Subsystem for Linux (WSL)](https://docs.microsoft.com/en-us/windows/wsl/about) should get Windows users a similiar experience to Ubuntu. [WSL 2](https://docs.microsoft.com/en-us/windows/wsl/compare-versions) specifically has been reported by several users to be a seamless experience. @@ -70,43 +45,22 @@ Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install GUI applications do not work with WSL out of the box. You will have to either [upgrade your system to Windows 11](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) or [set up an Xorg server](https://techcommunity.microsoft.com/t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242). -Tools -============ - -[Plot logs](plotjuggler) -------------- - -Easily plot openpilot logs with [PlotJuggler](https://github.com/facontidavide/PlotJuggler), an open source tool for visualizing time series data. - - -[Run openpilot in a simulator](sim) -------------- - -Test openpilots performance in a simulated environment. The [CARLA simulator](https://github.com/carla-simulator/carla) allows you to set a variety of features like: -* Weather -* Environment physics -* Cars -* Traffic and pedestrians - - -[Replay a drive](replay) -------------- -Review video and log data from routes and stream CAN messages to your device. - - -[Debug car controls](joystick) -------------- - -Use a joystick to control your car. - - -Welcomed contributions -============= +## CTF +Learn about the openpilot ecosystem and tools by playing our [CTF](/tools/CTF.md). -* Documentation: code comments, better tutorials, etc -* Support for platforms other than Ubuntu 20.04 -* Performance improvements -* More tools: anything that you think might be helpful to others. +## Directory Structure -![Imgur](https://i.imgur.com/IdfBgwK.jpg) +``` +├── ubuntu_setup.sh # Setup script for Ubuntu +├── mac_setup.sh # Setup script for macOS +├── joystick/ # Control your car with a joystick +├── lib/ # Libraries to support the tools and reading openpilot logs +├── plotjuggler/ # A tool to plot openpilot logs +├── replay/ # Replay drives and mock openpilot services +├── scripts/ # Miscellaneous scripts +├── serial/ # Tools for using the comma serial +├── sim/ # Run openpilot in a simulator +├── ssh/ # SSH into a comma device +└── webcam/ # Run openpilot on a PC with webcams +``` diff --git a/tools/joystick/README.md b/tools/joystick/README.md index 33366d1efd..5a45785db6 100644 --- a/tools/joystick/README.md +++ b/tools/joystick/README.md @@ -1,13 +1,17 @@ # Joystick -**Hardware needed**: [comma two devkit](https://comma.ai/shop/products/comma-two-devkit), laptop, joystick (optional) +**Hardware needed**: device running openpilot, laptop, joystick (optional) -With joystickd, you can connect your laptop to your comma device over the network and debug controls using a joystick or keyboard, however a joystick is recommended for more precise control. +With joystickd, you can connect your laptop to your comma device over the network and debug controls using a joystick or keyboard. +joystickd uses [inputs](https://pypi.org/project/inputs) which supports many common gamepads and joysticks. -Using a keyboard ---- +## Usage + +The car must be off, and openpilot must be offroad before starting `joystickd`. + +### Using a keyboard -To get started, ssh into your comma device and start joystickd with the following command: +SSH into your comma device and start joystickd with the following command: ```shell tools/joystick/joystickd.py --keyboard @@ -15,22 +19,26 @@ tools/joystick/joystickd.py --keyboard The available buttons and axes will print showing their key mappings. In general, the WASD keys control gas and brakes and steering torque in 5% increments. -Using a joystick ---- +### Joystick on your comma three + +Plug the joystick into your comma three aux USB-C port. Then, SSH into the device and start `joystickd.py`. + +### Joystick on your laptop -In order to use a joystick over the network, we need to run joystickd locally from your laptop and have it send `testJoystick` ZMQ packets over the network to the comma device. First connect a compatible joystick to your PC; joystickd uses [inputs](https://pypi.org/project/inputs) which supports many common gamepads and joysticks. +In order to use a joystick over the network, we need to run joystickd locally from your laptop and have it send `testJoystick` packets over the network to the comma device. -1. Connect your laptop to your comma device's hotspot and open a new ssh shell. Since joystickd is being run on your laptop, we need to write a parameter to let controlsd know to start in joystick debug mode: +1. Connect a joystick to your PC. +2. Connect your laptop to your comma device's hotspot and open a new SSH shell. Since joystickd is being run on your laptop, we need to write a parameter to let controlsd know to start in joystick debug mode: ```shell # on your comma device echo -n "1" > /data/params/d/JoystickDebugMode ``` -2. Run bridge with your laptop's IP address. This republishes the `testJoystick` packets sent from your laptop so that openpilot can receive them: +3. Run bridge with your laptop's IP address. This republishes the `testJoystick` packets sent from your laptop so that openpilot can receive them: ```shell # on your comma device cereal/messaging/bridge {LAPTOP_IP} testJoystick ``` -3. Finally, start joystickd on your laptop and tell it to publish ZMQ packets over the network: +4. Start joystickd on your laptop in ZMQ mode. ```shell # on your laptop export ZMQ=1 @@ -38,8 +46,8 @@ In order to use a joystick over the network, we need to run joystickd locally fr ``` --- -Now start your car and openpilot should go into debug mode with an alert on startup! The status of the axes will display on the alert, while button statuses print in the shell. +Now start your car and openpilot should go into joystick mode with an alert on startup! The status of the axes will display on the alert, while button statuses print in the shell. Make sure the conditions are met in the panda to allow controls (e.g. cruise control engaged). You can also make a modification to the panda code to always allow controls. -![Imgur](steer.gif) +![](steer.gif) diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index ba7b29df54..2b138b9c17 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -1,10 +1,11 @@ #!/usr/bin/env python +import os import argparse +from inputs import get_gamepad import cereal.messaging as messaging from common.numpy_fast import interp, clip from common.params import Params -from inputs import get_gamepad from tools.lib.kbhit import KBHit @@ -34,7 +35,8 @@ class Keyboard: class Joystick: def __init__(self): - self.max_axis_value = 255 # tune based on your joystick, 0 to this + self.min_axis_value = 0 + self.max_axis_value = 255 self.cancel_button = 'BTN_TRIGGER' self.axes_values = {'ABS_Y': 0., 'ABS_RZ': 0.} # gb, steer @@ -45,7 +47,10 @@ class Joystick: if event[0] == self.cancel_button and event[1] == 0: # state 0 is falling edge self.cancel = True elif event[0] in self.axes_values: - norm = -interp(event[1], [0, self.max_axis_value], [-1., 1.]) + self.max_axis_value = max(event[1], self.max_axis_value) + self.min_axis_value = min(event[1], self.min_axis_value) + + norm = -interp(event[1], [self.min_axis_value, self.max_axis_value], [-1., 1.]) self.axes_values[event[0]] = norm if abs(norm) > 0.05 else 0. # center can be noisy, deadzone of 5% else: return False @@ -58,7 +63,7 @@ def joystick_thread(use_keyboard): joystick = Keyboard() if use_keyboard else Joystick() while True: - ret = joystick.update() # processes joystick/key events and handles state of axes + ret = joystick.update() if ret: dat = messaging.new_message('testJoystick') dat.testJoystick.axes = [joystick.axes_values[a] for a in joystick.axes_values] @@ -68,19 +73,24 @@ def joystick_thread(use_keyboard): if __name__ == '__main__': - parser = argparse.ArgumentParser( - description='Publishes events from your joystick to control your car', - formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser = argparse.ArgumentParser(description='Publishes events from your joystick to control your car.\n' + 'openpilot must be offroad before starting joysticked.', + formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('--keyboard', action='store_true', help='Use your keyboard instead of a joystick') args = parser.parse_args() + if not Params().get_bool("IsOffroad") and "ZMQ" not in os.environ: + print("The car must be off before running joystickd.") + exit() + + print() if args.keyboard: - print('\nGas/brake control: `W` and `S` keys\n' + print('Gas/brake control: `W` and `S` keys\n' 'Steering control: `A` and `D` keys') print('Buttons:\n' '- `R`: Resets axes\n' '- `C`: Cancel cruise control') else: - print('\nUsing joystick, make sure to run bridge on your device if running over the network!') + print('Using joystick, make sure to run cereal/messaging/bridge on your device if running over the network!') joystick_thread(args.keyboard) diff --git a/tools/lib/auth.py b/tools/lib/auth.py index d5d2a60bb0..0e9e7705b5 100755 --- a/tools/lib/auth.py +++ b/tools/lib/auth.py @@ -109,7 +109,7 @@ def login(method): if 'code' in web_server.query_params: break elif 'error' in web_server.query_params: - print('Authentication Error: "%s". Description: "%s" ' % ( + print('Authentication Error: "{}". Description: "{}" '.format( web_server.query_params['error'], web_server.query_params.get('error_description')), file=sys.stderr) break diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index db07c20177..ac5962462e 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -50,7 +50,7 @@ def fingerprint_video(fn): with FileReader(fn) as f: header = f.read(4) if len(header) == 0: - raise DataUnreadableError("%s is empty" % fn) + raise DataUnreadableError(f"{fn} is empty") elif header == b"\x00\xc0\x12\x00": return FrameType.raw elif header == b"\x00\x00\x00\x01": @@ -90,7 +90,7 @@ def vidindex(fn, typ): try: subprocess.check_call([vidindex, typ, fn, prefix_f.name, index_f.name]) except subprocess.CalledProcessError: - raise DataUnreadableError("vidindex failed on file %s" % fn) + raise DataUnreadableError(f"vidindex failed on file {fn}") with open(index_f.name, "rb") as f: index = f.read() with open(prefix_f.name, "rb") as f: @@ -308,7 +308,7 @@ class RawFrameReader(BaseFrameReader): assert num+count <= self.frame_count if pix_fmt not in ("yuv420p", "rgb24"): - raise ValueError("Unsupported pixel format %r" % pix_fmt) + raise ValueError(f"Unsupported pixel format {pix_fmt!r}") app = [] for i in range(num, num+count): @@ -548,10 +548,10 @@ class GOPFrameReader(BaseFrameReader): assert self.frame_count is not None if num + count > self.frame_count: - raise ValueError("{} > {}".format(num + count, self.frame_count)) + raise ValueError(f"{num + count} > {self.frame_count}") if pix_fmt not in ("yuv420p", "rgb24", "yuv444p"): - raise ValueError("Unsupported pixel format %r" % pix_fmt) + raise ValueError(f"Unsupported pixel format {pix_fmt!r}") ret = [self._get_one(num + i, pix_fmt) for i in range(count)] @@ -572,15 +572,13 @@ class StreamFrameReader(StreamGOPReader, GOPFrameReader): def GOPFrameIterator(gop_reader, pix_fmt): dec = VideoStreamDecompressor(gop_reader.fn, gop_reader.vid_fmt, gop_reader.w, gop_reader.h, pix_fmt) - for frame in dec.read(): - yield frame + yield from dec.read() def FrameIterator(fn, pix_fmt, **kwargs): fr = FrameReader(fn, **kwargs) if isinstance(fr, GOPReader): - for v in GOPFrameIterator(fr, pix_fmt): - yield v + yield from GOPFrameIterator(fr, pix_fmt) else: for i in range(fr.frame_count): yield fr.get(i, pix_fmt=pix_fmt)[0] diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 1c9cc81e32..fc7a8dcb70 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -12,10 +12,10 @@ except ImportError: from cereal import log as capnp_log # this is an iterator itself, and uses private variables from LogReader -class MultiLogIterator(object): - def __init__(self, log_paths, wraparound=False): +class MultiLogIterator: + def __init__(self, log_paths, sort_by_time=False): self._log_paths = log_paths - self._wraparound = wraparound + self.sort_by_time = sort_by_time self._first_log_idx = next(i for i in range(len(log_paths)) if log_paths[i] is not None) self._current_log = self._first_log_idx @@ -26,7 +26,7 @@ class MultiLogIterator(object): def _log_reader(self, i): if self._log_readers[i] is None and self._log_paths[i] is not None: log_path = self._log_paths[i] - self._log_readers[i] = LogReader(log_path) + self._log_readers[i] = LogReader(log_path, sort_by_time=self.sort_by_time) return self._log_readers[i] @@ -41,12 +41,8 @@ class MultiLogIterator(object): self._idx = 0 self._current_log = next(i for i in range(self._current_log + 1, len(self._log_readers) + 1) if i == len(self._log_readers) or self._log_paths[i] is not None) - # wraparound if self._current_log == len(self._log_readers): - if self._wraparound: - self._current_log = self._first_log_idx - else: - raise StopIteration + raise StopIteration def __next__(self): while 1: @@ -74,8 +70,8 @@ class MultiLogIterator(object): return True -class LogReader(object): - def __init__(self, fn, canonicalize=True, only_union_types=False): +class LogReader: + def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False): data_version = None _, ext = os.path.splitext(urllib.parse.urlparse(fn).path) with FileReader(fn) as f: @@ -90,7 +86,7 @@ class LogReader(object): else: raise Exception(f"unknown extension {ext}") - self._ents = list(ents) + self._ents = list(sorted(ents, key=lambda x: x.logMonoTime) if sort_by_time else ents) self._ts = [x.logMonoTime for x in self._ents] self.data_version = data_version self._only_union_types = only_union_types @@ -112,6 +108,6 @@ if __name__ == "__main__": # below line catches those errors and replaces the bytes with \x__ codecs.register_error("strict", codecs.backslashreplace_errors) log_path = sys.argv[1] - lr = LogReader(log_path) + lr = LogReader(log_path, sort_by_time=True) for msg in lr: print(msg) diff --git a/tools/lib/robust_logreader.py b/tools/lib/robust_logreader.py index e534a7c8f0..c7feb6c3ed 100755 --- a/tools/lib/robust_logreader.py +++ b/tools/lib/robust_logreader.py @@ -13,7 +13,7 @@ from cereal import log as capnp_log class RobustLogReader(LogReader): - def __init__(self, fn, canonicalize=True, only_union_types=False): # pylint: disable=super-init-not-called + def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False): # pylint: disable=super-init-not-called data_version = None _, ext = os.path.splitext(urllib.parse.urlparse(fn).path) with FileReader(fn) as f: @@ -45,7 +45,7 @@ class RobustLogReader(LogReader): while True: try: ents = capnp_log.Event.read_multiple_bytes(dat) - self._ents = list(ents) + self._ents = list(sorted(ents, key=lambda x: x.logMonoTime) if sort_by_time else ents) break except capnp.lib.capnp.KjException: if progress is None: diff --git a/tools/lib/route.py b/tools/lib/route.py index 8c03cd9cca..c87bd04b9e 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -7,9 +7,10 @@ from itertools import chain from tools.lib.auth_config import get_token from tools.lib.api import CommaApi -SEGMENT_NAME_RE = r'[a-z0-9]{16}[|_][0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+' -EXPLORER_FILE_RE = r'^({})--([a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE) -OP_SEGMENT_DIR_RE = r'^({})$'.format(SEGMENT_NAME_RE) +ROUTE_NAME_RE = r'(?P[a-z0-9]{16})[|_/](?P[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2})' +SEGMENT_NAME_RE = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME_RE) +EXPLORER_FILE_RE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE) +OP_SEGMENT_DIR_RE = r'^(?P{})$'.format(SEGMENT_NAME_RE) QLOG_FILENAMES = ['qlog.bz2'] QCAMERA_FILENAMES = ['qcamera.ts'] @@ -18,48 +19,52 @@ CAMERA_FILENAMES = ['fcamera.hevc', 'video.hevc'] DCAMERA_FILENAMES = ['dcamera.hevc'] ECAMERA_FILENAMES = ['ecamera.hevc'] -class Route(object): - def __init__(self, route_name, data_dir=None): +class Route: + def __init__(self, name, data_dir=None): + self._name = RouteName(name) self.files = None - self.route_name = route_name.replace('_', '|') if data_dir is not None: self._segments = self._get_segments_local(data_dir) else: self._segments = self._get_segments_remote() - self.max_seg_number = self._segments[-1].canonical_name.segment_num + self.max_seg_number = self._segments[-1].name.segment_num + + @property + def name(self): + return self._name @property def segments(self): return self._segments def log_paths(self): - log_path_by_seg_num = {s.canonical_name.segment_num: s.log_path for s in self._segments} + log_path_by_seg_num = {s.name.segment_num: s.log_path for s in self._segments} return [log_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def qlog_paths(self): - qlog_path_by_seg_num = {s.canonical_name.segment_num: s.qlog_path for s in self._segments} + qlog_path_by_seg_num = {s.name.segment_num: s.qlog_path for s in self._segments} return [qlog_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def camera_paths(self): - camera_path_by_seg_num = {s.canonical_name.segment_num: s.camera_path for s in self._segments} + camera_path_by_seg_num = {s.name.segment_num: s.camera_path for s in self._segments} return [camera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def dcamera_paths(self): - dcamera_path_by_seg_num = {s.canonical_name.segment_num: s.dcamera_path for s in self._segments} + dcamera_path_by_seg_num = {s.name.segment_num: s.dcamera_path for s in self._segments} return [dcamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def ecamera_paths(self): - ecamera_path_by_seg_num = {s.canonical_name.segment_num: s.ecamera_path for s in self._segments} + ecamera_path_by_seg_num = {s.name.segment_num: s.ecamera_path for s in self._segments} return [ecamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def qcamera_paths(self): - qcamera_path_by_seg_num = {s.canonical_name.segment_num: s.qcamera_path for s in self._segments} + qcamera_path_by_seg_num = {s.name.segment_num: s.qcamera_path for s in self._segments} return [qcamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] # TODO: refactor this, it's super repetitive def _get_segments_remote(self): api = CommaApi(get_token()) - route_files = api.get('v1/route/' + self.route_name + '/files') + route_files = api.get('v1/route/' + self.name.canonical_name + '/files') self.files = list(chain.from_iterable(route_files.values())) segments = {} @@ -67,7 +72,7 @@ class Route(object): _, dongle_id, time_str, segment_num, fn = urlparse(url).path.rsplit('/', maxsplit=4) segment_name = f'{dongle_id}|{time_str}--{segment_num}' if segments.get(segment_name): - segments[segment_name] = RouteSegment( + segments[segment_name] = Segment( segment_name, url if fn in LOG_FILENAMES else segments[segment_name].log_path, url if fn in QLOG_FILENAMES else segments[segment_name].qlog_path, @@ -77,7 +82,7 @@ class Route(object): url if fn in QCAMERA_FILENAMES else segments[segment_name].qcamera_path, ) else: - segments[segment_name] = RouteSegment( + segments[segment_name] = Segment( segment_name, url if fn in LOG_FILENAMES else None, url if fn in QLOG_FILENAMES else None, @@ -87,7 +92,7 @@ class Route(object): url if fn in QCAMERA_FILENAMES else None, ) - return sorted(segments.values(), key=lambda seg: seg.canonical_name.segment_num) + return sorted(segments.values(), key=lambda seg: seg.name.segment_num) def _get_segments_local(self, data_dir): files = os.listdir(data_dir) @@ -99,20 +104,21 @@ class Route(object): op_match = re.match(OP_SEGMENT_DIR_RE, f) if explorer_match: - segment_name, fn = explorer_match.groups() - if segment_name.replace('_', '|').startswith(self.route_name): + segment_name = explorer_match.group('segment_name') + fn = explorer_match.group('file_name') + if segment_name.replace('_', '|').startswith(self.name.canonical_name): segment_files[segment_name].append((fullpath, fn)) elif op_match and os.path.isdir(fullpath): - segment_name, = op_match.groups() - if segment_name.startswith(self.route_name): + segment_name = op_match.group('segment_name') + if segment_name.startswith(self.name.canonical_name): for seg_f in os.listdir(fullpath): segment_files[segment_name].append((os.path.join(fullpath, seg_f), seg_f)) - elif f == self.route_name: + elif f == self.name.canonical_name: for seg_num in os.listdir(fullpath): if not seg_num.isdigit(): continue - segment_name = '{}--{}'.format(self.route_name, seg_num) + segment_name = f'{self.name.canonical_name}--{seg_num}' for seg_f in os.listdir(os.path.join(fullpath, seg_num)): segment_files[segment_name].append((os.path.join(fullpath, seg_num, seg_f), seg_f)) @@ -149,15 +155,15 @@ class Route(object): except StopIteration: qcamera_path = None - segments.append(RouteSegment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) + segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) if len(segments) == 0: - raise ValueError('Could not find segments for route {} in data directory {}'.format(self.route_name, data_dir)) - return sorted(segments, key=lambda seg: seg.canonical_name.segment_num) + raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}') + return sorted(segments, key=lambda seg: seg.name.segment_num) -class RouteSegment(object): +class Segment: def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path): - self._name = RouteSegmentName(name) + self._name = SegmentName(name) self.log_path = log_path self.qlog_path = qlog_path self.camera_path = camera_path @@ -167,21 +173,55 @@ class RouteSegment(object): @property def name(self): - return str(self._name) + return self._name + +class RouteName: + def __init__(self, name_str: str): + self._name_str = name_str + delim = next(c for c in self._name_str if c in ("|", "/")) + self._dongle_id, self._time_str = self._name_str.split(delim) + + assert len(self._dongle_id) == 16, self._name_str + assert len(self._time_str) == 20, self._name_str + self._canonical_name = f"{self._dongle_id}|{self._time_str}" @property - def canonical_name(self): - return self._name + def canonical_name(self) -> str: return self._canonical_name + + @property + def dongle_id(self) -> str: return self._dongle_id -class RouteSegmentName(object): - def __init__(self, name_str): - self._segment_name_str = name_str - self._route_name_str, num_str = self._segment_name_str.rsplit("--", 1) - self._num = int(num_str) + @property + def time_str(self) -> str: return self._time_str + + def __str__(self) -> str: return self._canonical_name + +class SegmentName: + # TODO: add constructor that takes dongle_id, time_str, segment_num and then create instances + # of this class instead of manually constructing a segment name (use canonical_name prop instead) + def __init__(self, name_str: str, allow_route_name=False): + self._name_str = name_str + seg_num_delim = "--" if self._name_str.count("--") == 2 else "/" + name_parts = self._name_str.rsplit(seg_num_delim, 1) + if allow_route_name and len(name_parts) == 1: + name_parts.append("-1") # no segment number + self._route_name = RouteName(name_parts[0]) + self._num = int(name_parts[1]) + self._canonical_name = f"{self._route_name._dongle_id}|{self._route_name._time_str}--{self._num}" + + @property + def canonical_name(self) -> str: return self._canonical_name + + @property + def dongle_id(self) -> str: return self._route_name.dongle_id + + @property + def time_str(self) -> str: return self._route_name.time_str + + @property + def segment_num(self) -> int: return self._num @property - def segment_num(self): - return self._num + def route_name(self) -> RouteName: return self._route_name - def __str__(self): - return self._segment_name_str + def __str__(self) -> str: return self._canonical_name diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 2161770d03..8ad2f96b3a 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -22,7 +22,7 @@ def hash_256(link): return hsh -class URLFile(object): +class URLFile: _tlocal = threading.local() def __init__(self, url, debug=False, cache=None): @@ -70,7 +70,7 @@ class URLFile(object): return self._length file_length_path = os.path.join(CACHE_DIR, hash_256(self._url) + "_length") if os.path.exists(file_length_path) and not self._force_download: - with open(file_length_path, "r") as file_length: + with open(file_length_path) as file_length: content = file_length.read() self._length = int(content) return self._length @@ -156,7 +156,7 @@ class URLFile(object): if self._debug: t2 = time.time() if t2 - t1 > 0.1: - print("get %s %r %.f slow" % (self._url, headers, t2 - t1)) + print(f"get {self._url} {headers!r} {t2 - t1:.f} slow") response_code = c.getinfo(pycurl.RESPONSE_CODE) if response_code == 416: # Requested Range Not Satisfiable diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 1072c1caaf..fa1a23dd47 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -1,19 +1,24 @@ -#!/bin/bash -e +#!/bin/bash -OP_ROOT=$(git rev-parse --show-toplevel) +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd $DIR/../ && pwd)" # Install brew if required if [[ $(command -v brew) == "" ]]; then echo "Installing Hombrew" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" + echo "[ ] installed brew t=$SECONDS" fi +# TODO: remove protobuf,protobuf-c,swig when casadi can be pip installed brew bundle --file=- <<-EOS brew "cmake" +brew "cppcheck" +brew "git-lfs" brew "zlib" brew "bzip2" -brew "rust" -brew "rustup-init" brew "capnp" brew "coreutils" brew "eigen" @@ -27,20 +32,20 @@ brew "openssl" brew "pyenv" brew "qt@5" brew "zeromq" +brew "protobuf" +brew "protobuf-c" +brew "swig" cask "gcc-arm-embedded" EOS +echo "[ ] finished brew install t=$SECONDS" + if [[ $SHELL == "/bin/zsh" ]]; then RC_FILE="$HOME/.zshrc" elif [[ $SHELL == "/bin/bash" ]]; then RC_FILE="$HOME/.bash_profile" fi -# Build requirements for macOS -# https://github.com/pyenv/pyenv/issues/1740 -# https://github.com/pyca/cryptography/blob/main/docs/installation.rst -rustup-init -y - export LDFLAGS="$LDFLAGS -L/usr/local/opt/zlib/lib" export LDFLAGS="$LDFLAGS -L/usr/local/opt/bzip2/lib" export LDFLAGS="$LDFLAGS -L/usr/local/opt/openssl@1.1/lib" @@ -50,26 +55,42 @@ export CPPFLAGS="$CPPFLAGS -I/usr/local/opt/openssl@1.1/include" export PATH="$PATH:/usr/local/opt/openssl@1.1/bin" export PATH="$PATH:/usr/local/bin" -# OpenPilot environment variables +# openpilot environment if [ -z "$OPENPILOT_ENV" ] && [ -n "$RC_FILE" ] && [ -z "$CI" ]; then - echo "export PATH=\"\$PATH:$HOME/.cargo/bin\"" >> $RC_FILE - echo "source $OP_ROOT/tools/openpilot_env.sh" >> $RC_FILE - export PATH="$PATH:\"\$HOME/.cargo/bin\"" - source "$OP_ROOT/tools/openpilot_env.sh" + echo "source $ROOT/tools/openpilot_env.sh" >> $RC_FILE + source "$ROOT/tools/openpilot_env.sh" echo "Added openpilot_env to RC file: $RC_FILE" fi -# install python -PYENV_PYTHON_VERSION=$(cat $OP_ROOT/.python-version) -PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH -pyenv install -s ${PYENV_PYTHON_VERSION} -pyenv rehash -eval "$(pyenv init -)" +# install python dependencies +$ROOT/update_requirements.sh +eval "$(pyenv init --path)" +echo "[ ] installed python dependencies t=$SECONDS" -pip install pipenv==2020.8.13 -pipenv install --dev --deploy +# install casadi +VENV=`pipenv --venv` +PYTHON_VER=3.8 +PYTHON_VERSION=$(cat $ROOT/.python-version) +if [ ! -f "$VENV/include/casadi/casadi.hpp" ]; then + echo "-- casadi manual install" + cd /tmp/ && curl -L https://github.com/casadi/casadi/archive/refs/tags/ge6.tar.gz --output casadi.tar.gz + tar -xzf casadi.tar.gz + cd casadi-ge6/ && mkdir -p build && cd build + cmake .. \ + -DWITH_PYTHON=ON \ + -DWITH_EXAMPLES=OFF \ + -DCMAKE_INSTALL_PREFIX:PATH=$VENV \ + -DPYTHON_PREFIX:PATH=$VENV/lib/python$PYTHON_VER/site-packages \ + -DPYTHON_LIBRARY:FILEPATH=$HOME/.pyenv/versions/$PYTHON_VERSION/lib/libpython$PYTHON_VER.dylib \ + -DPYTHON_EXECUTABLE:FILEPATH=$HOME/.pyenv/versions/$PYTHON_VERSION/bin/python \ + -DPYTHON_INCLUDE_DIR:PATH=$HOME/.pyenv/versions/$PYTHON_VERSION/include/python$PYTHON_VER \ + -DCMAKE_CXX_FLAGS="-ferror-limit=0" -DCMAKE_C_FLAGS="-ferror-limit=0" + CFLAGS="-ferror-limit=0" make -j$(nproc) && make install +else + echo "---- casadi found in venv. skipping build ----" +fi echo -echo "---- FINISH OPENPILOT SETUP ----" -echo "Configure your active shell env by running:" +echo "---- OPENPILOT SETUP DONE ----" +echo "Open a new shell or configure your active shell env by running:" echo "source $RC_FILE" diff --git a/tools/openpilot_env.sh b/tools/openpilot_env.sh index 81ae00d64d..59108312ac 100755 --- a/tools/openpilot_env.sh +++ b/tools/openpilot_env.sh @@ -1,5 +1,4 @@ if [ -z "$OPENPILOT_ENV" ]; then - export PYTHONPATH="$HOME/openpilot:$PYTHONPATH" export PATH="$HOME/.pyenv/bin:$PATH" # Pyenv suggests we place the below two lines in .profile before we source @@ -9,16 +8,14 @@ if [ -z "$OPENPILOT_ENV" ]; then # https://github.com/pyenv/pyenv/issues/1906 export PYENV_ROOT="$HOME/.pyenv" - unamestr=`uname` - if [[ "$unamestr" == 'Linux' ]]; then - eval "$(pyenv init --path)" - + if [[ "$(uname)" == 'Linux' ]]; then eval "$(pyenv virtualenv-init -)" - elif [[ "$unamestr" == 'Darwin' ]]; then + elif [[ "$(uname)" == 'Darwin' ]]; then # msgq doesn't work on mac export ZMQ=1 export OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES fi + eval "$(pyenv init --path)" eval "$(pyenv init -)" export OPENPILOT_ENV=1 diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index 8ab150e821..2e79aecbca 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -4,24 +4,23 @@ ## Installation -**NOTE: this is Ubuntu only for now. Pull requests for macOS support are welcome.** - Once you've cloned and are in openpilot, this command will download PlotJuggler and install our plugins: -`cd tools/plotjuggler && ./install.sh` +`cd tools/plotjuggler && ./juggle.py --install` ## Usage ``` $ ./juggle.py -h -usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_name] [segment_number] [segment_count] +usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [--install] + [route_or_segment_name] [segment_count] A helper to run PlotJuggler on openpilot routes positional arguments: - route_name The route name to plot (cabana share URL accepted) (default: None) - segment_number The index of the segment to plot (default: None) - segment_count The number of segments to plot (default: 1) + route_or_segment_name + The route or segment name to plot (cabana share URL accepted) (default: None) + segment_count The number of segments to plot (default: None) optional arguments: -h, --help show this help message and exit @@ -30,12 +29,17 @@ optional arguments: --can Parse CAN data (default: False) --stream Start PlotJuggler in streaming mode (default: False) --layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None) + --install Install or update PlotJuggler + plugins (default: False) ``` -Example: +Examples using route name: `./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36"` +Examples using segment name: + +`./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36--1"` + ## Streaming Explore live data from your car! Follow these steps to stream from your comma device to your laptop: diff --git a/tools/plotjuggler/install.sh b/tools/plotjuggler/install.sh deleted file mode 100755 index 27cb0dfb5d..0000000000 --- a/tools/plotjuggler/install.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -mkdir -p bin -cd bin - -for lib_name in libDataLoadRlog.so libDataStreamCereal.so plotjuggler; do - wget https://github.com/commaai/PlotJuggler/releases/download/latest/${lib_name}.tar.gz - tar -xf ${lib_name}.tar.gz - rm ${lib_name}.tar.gz -done diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index d488750701..7cafaf60ac 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -2,24 +2,51 @@ import os import sys import multiprocessing +import platform +import shutil import subprocess +import tarfile +import tempfile +import requests import argparse -from tempfile import NamedTemporaryFile from common.basedir import BASEDIR from selfdrive.test.process_replay.compare_logs import save_log from tools.lib.api import CommaApi from tools.lib.auth_config import get_token from tools.lib.robust_logreader import RobustLogReader -from tools.lib.route import Route +from tools.lib.route import Route, SegmentName from urllib.parse import urlparse, parse_qs juggle_dir = os.path.dirname(os.path.realpath(__file__)) DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" +RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest" +INSTALL_DIR = os.path.join(juggle_dir, "bin") + + +def install(): + m = f"{platform.system()}-{platform.machine()}" + supported = ("Linux-x86_64", "Darwin-arm64", "Darwin-x86_64") + if m not in supported: + raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}") + + if os.path.exists(INSTALL_DIR): + shutil.rmtree(INSTALL_DIR) + os.mkdir(INSTALL_DIR) + + url = os.path.join(RELEASES_URL, m + ".tar.gz") + with requests.get(url, stream=True) as r, tempfile.NamedTemporaryFile() as tmp: + r.raise_for_status() + with open(tmp.name, 'wb') as tmpf: + for chunk in r.iter_content(chunk_size=1024*1024): + tmpf.write(chunk) + + with tarfile.open(tmp.name) as tar: + tar.extractall(path=INSTALL_DIR) + def load_segment(segment_name): - print(f"Loading {segment_name}") if segment_name is None: return [] @@ -29,46 +56,51 @@ def load_segment(segment_name): print(f"Error parsing {segment_name}: {e}") return [] + def start_juggler(fn=None, dbc=None, layout=None): env = os.environ.copy() env["BASEDIR"] = BASEDIR - pj = os.getenv("PLOTJUGGLER_PATH", os.path.join(juggle_dir, "bin/plotjuggler")) - + env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}" if dbc: env["DBC_NAME"] = dbc - extra_args = [] + extra_args = "" if fn is not None: - extra_args.append(f'-d {fn}') - + extra_args += f" -d {fn}" if layout is not None: - extra_args.append(f'-l {layout}') + extra_args += f" -l {layout}" + + cmd = f'plotjuggler --plugin_folders {INSTALL_DIR}{extra_args}' + subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) - extra_args = " ".join(extra_args) - subprocess.call(f'{pj} --plugin_folders {os.path.join(juggle_dir, "bin")} {extra_args}', shell=True, env=env, cwd=juggle_dir) -def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): - if 'cabana' in route_name: - query = parse_qs(urlparse(route_name).query) +def juggle_route(route_or_segment_name, segment_count, qlog, can, layout): + segment_start = 0 + if 'cabana' in route_or_segment_name: + query = parse_qs(urlparse(route_or_segment_name).query) api = CommaApi(get_token()) logs = api.get(f'v1/route/{query["route"][0]}/log_urls?sig={query["sig"][0]}&exp={query["exp"][0]}') - elif route_name.startswith("http://") or route_name.startswith("https://") or os.path.isfile(route_name): - logs = [route_name] + elif route_or_segment_name.startswith("http://") or route_or_segment_name.startswith("https://") or os.path.isfile(route_or_segment_name): + logs = [route_or_segment_name] else: - r = Route(route_name) + route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) + segment_start = max(route_or_segment_name.segment_num, 0) + + if route_or_segment_name.segment_num != -1 and segment_count is None: + segment_count = 1 + + r = Route(route_or_segment_name.route_name.canonical_name) logs = r.qlog_paths() if qlog else r.log_paths() - if segment_number is not None: - logs = logs[segment_number:segment_number+segment_count] + segment_end = segment_start + segment_count if segment_count else -1 + logs = logs[segment_start:segment_end] if None in logs: - fallback_answer = input("At least one of the rlogs in this segment does not exist, would you like to use the qlogs? (y/n) : ") - if fallback_answer == 'y': - logs = r.qlog_paths() - if segment_number is not None: - logs = logs[segment_number:segment_number+segment_count] + ans = input(f"{logs.count(None)}/{len(logs)} of the rlogs in this segment are missing, would you like to fall back to the qlogs? (y/n) ") + if ans == 'y': + logs = r.qlog_paths()[segment_start:segment_end] else: - print(f"Please try a different {'segment' if segment_number is not None else 'route'}") + print("Please try a different route or segment") return all_data = [] @@ -85,17 +117,17 @@ def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): try: DBC = __import__(f"selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC dbc = DBC[cp.carParams.carFingerprint]['pt'] - except (ImportError, KeyError, AttributeError): + except Exception: pass break - tempfile = NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) - save_log(tempfile.name, all_data, compress=False) - del all_data + with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp: + save_log(tmp.name, all_data, compress=False) + del all_data + start_juggler(tmp.name, dbc, layout) - start_juggler(tempfile.name, dbc, layout) -def get_arg_parser(): +if __name__ == "__main__": parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -104,20 +136,21 @@ def get_arg_parser(): parser.add_argument("--can", action="store_true", help="Parse CAN data") parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") - parser.add_argument("route_name", nargs='?', help="The route name to plot (cabana share URL accepted)") - parser.add_argument("segment_number", type=int, nargs='?', help="The index of the segment to plot") - parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot", default=1) - return parser + parser.add_argument("--install", action="store_true", help="Install or update PlotJuggler + plugins") + parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot (cabana share URL accepted)") + parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot") -if __name__ == "__main__": - arg_parser = get_arg_parser() if len(sys.argv) == 1: - arg_parser.print_help() + parser.print_help() + sys.exit() + args = parser.parse_args() + + if args.install: + install() sys.exit() - args = arg_parser.parse_args(sys.argv[1:]) if args.stream: start_juggler(layout=args.layout) else: - route = DEMO_ROUTE if args.demo else args.route_name.strip() - juggle_route(route, args.segment_number, args.segment_count, args.qlog, args.can, args.layout) + route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() + juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout) diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index 3e695c1990..edaec9c80a 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -7,27 +7,20 @@ import unittest from common.basedir import BASEDIR from common.timeout import Timeout -from selfdrive.test.openpilotci import get_url +from tools.plotjuggler.juggle import install class TestPlotJuggler(unittest.TestCase): - def test_install(self): - exit_code = os.system(os.path.join(BASEDIR, "tools/plotjuggler/install.sh")) - self.assertEqual(exit_code, 0) + def test_demo(self): + install() - def test_run(self): + pj = os.path.join(BASEDIR, "tools/plotjuggler/juggle.py") + p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', + stderr=subprocess.PIPE, shell=True, start_new_session=True) - test_url = get_url("ffccc77938ddbc44|2021-01-04--16-55-41", 0) - - # Launch PlotJuggler with the executable in the bin directory - os.environ["PLOTJUGGLER_PATH"] = f'{os.path.join(BASEDIR, "tools/plotjuggler/bin/plotjuggler")}' - p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {os.path.join(BASEDIR, "tools/plotjuggler/juggle.py")} \ - "{test_url}"', stderr=subprocess.PIPE, shell=True, - start_new_session=True) - - # Wait max 60 seconds for the "Done reading Rlog data" signal from the plugin + # Wait for "Done reading Rlog data" signal from the plugin output = "\n" - with Timeout(120, error_msg=output): + with Timeout(180, error_msg=output): while output.splitlines()[-1] != "Done reading Rlog data": output += p.stderr.readline().decode("utf-8") diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 0573973f9f..7410c107c4 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -152,7 +152,7 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots.append(plot) idxs.append(name_to_arr_idx[item]) plot_select.append(i) - axs[i].set_title(", ".join("%s (%s)" % (nm, cl) + axs[i].set_title(", ".join(f"{nm} ({cl})" for (nm, cl) in zip(pl_list, plot_colors[i])), fontsize=10) axs[i].tick_params(axis="x", colors="white") axs[i].tick_params(axis="y", colors="white") diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py index e9111d9de2..39751ba551 100755 --- a/tools/scripts/fetch_image_from_route.py +++ b/tools/scripts/fetch_image_from_route.py @@ -2,7 +2,7 @@ import sys if len(sys.argv) < 4: - print("%s " % sys.argv[0]) + print(f"{sys.argv[0]} ") print('example: ./fetch_image_from_route.py "02c45f73a2e5c6e9|2020-06-01--18-03-08" 3 500') exit(0) @@ -33,5 +33,5 @@ if frame >= fr.frame_count: im = Image.fromarray(fr.get(frame, count=1, pix_fmt="rgb24")[0]) fn = "uxxx_"+route.replace("|", "_")+"_%d_%d.png" % (segment, frame) im.save(fn) -print("saved %s" % fn) +print(f"saved {fn}") diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py index 518c4ecaf6..3fefd45ba8 100644 --- a/tools/scripts/save_ubloxraw_stream.py +++ b/tools/scripts/save_ubloxraw_stream.py @@ -35,7 +35,7 @@ def main(argv): args.data_dir = os.path.dirname(args.data_dir) route = Route(args.route_name, args.data_dir) - lr = MultiLogIterator(route.log_paths(), wraparound=False) + lr = MultiLogIterator(route.log_paths()) with open(args.out_path, 'wb') as f: try: @@ -52,7 +52,7 @@ def main(argv): i += 1 except StopIteration: print('All done') - print('Writed {} msgs'.format(i)) + print(f'Writed {i} msgs') if __name__ == "__main__": diff --git a/tools/scripts/setup_ssh_keys.py b/tools/scripts/setup_ssh_keys.py index 0eb44dbd59..5b5ebfbac5 100755 --- a/tools/scripts/setup_ssh_keys.py +++ b/tools/scripts/setup_ssh_keys.py @@ -7,7 +7,7 @@ import sys if __name__ == "__main__": if len(sys.argv) < 2: - print("%s " % sys.argv[0]) + print(f"{sys.argv[0]} ") exit(1) username = sys.argv[1] diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index f6cf0d9b0b..4ffd5bea14 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -42,7 +42,7 @@ RUN pip install --upgrade pip && \ # get same tmux config used on NEOS for debugging RUN cd $HOME && \ - wget https://raw.githubusercontent.com/commaai/eon-neos-builder/master/devices/eon/home/.tmux.conf + curl -O https://raw.githubusercontent.com/commaai/eon-neos-builder/master/devices/eon/home/.tmux.conf ENV PYTHONPATH $HOME/openpilot:${PYTHONPATH} RUN mkdir -p $HOME/openpilot diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index 9924a40b11..2e19faefcd 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -93,7 +93,7 @@ class Camerad: rgb_cl = cl_array.to_device(self.queue, rgb) yuv_cl = cl_array.empty_like(rgb_cl) self.krnl(self.queue, (np.int32(self.Wdiv4), np.int32(self.Hdiv4)), None, rgb_cl.data, yuv_cl.data).wait() - yuv = np.resize(yuv_cl.get(), np.int32((rgb.size / 2))) + yuv = np.resize(yuv_cl.get(), np.int32(rgb.size / 2)) eof = self.frame_id * 0.05 # TODO: remove RGB send once the last RGB vipc subscriber is removed diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index d2111ad150..7c47e2ba4a 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -10,7 +10,7 @@ from typing import NoReturn print('Available devices:') for fn in os.listdir('/dev/input'): if fn.startswith('js'): - print(' /dev/input/%s' % (fn)) + print(f' /dev/input/{fn}') # We'll store the states here. axis_states = {} @@ -94,7 +94,7 @@ button_map = [] def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: # Open the joystick device. fn = '/dev/input/js0' - print('Opening %s...' % fn) + print(f'Opening {fn}...') jsdev = open(fn, 'rb') # Get the device name. @@ -102,7 +102,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: buf = array.array('B', [0] * 64) ioctl(jsdev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len) js_name = buf.tobytes().rstrip(b'\x00').decode('utf-8') - print('Device name: %s' % js_name) + print(f'Device name: {js_name}') # Get number of axes and buttons. buf = array.array('B', [0]) @@ -118,7 +118,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: ioctl(jsdev, 0x80406a32, buf) # JSIOCGAXMAP for _axis in buf[:num_axes]: - axis_name = axis_names.get(_axis, 'unknown(0x%02x)' % _axis) + axis_name = axis_names.get(_axis, f'unknown(0x{_axis:02x})') axis_map.append(axis_name) axis_states[axis_name] = 0.0 @@ -127,7 +127,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: ioctl(jsdev, 0x80406a34, buf) # JSIOCGBTNMAP for btn in buf[:num_buttons]: - btn_name = button_names.get(btn, 'unknown(0x%03x)' % btn) + btn_name = button_names.get(btn, f'unknown(0x{btn:03x})') button_map.append(btn_name) button_states[btn_name] = 0 @@ -153,19 +153,19 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put("throttle_%f" % normalized) + q.put(f"throttle_{normalized:f}") elif axis == "rz": # brake fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put("brake_%f" % normalized) + q.put(f"brake_{normalized:f}") elif axis == "x": # steer angle fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = fvalue - q.put("steer_%f" % normalized) + q.put(f"steer_{normalized:f}") elif mtype & 0x01: # buttons if value == 1: # press down diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 6ad7931b56..e688c79c12 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -1,6 +1,11 @@ -#!/bin/bash -e +#!/bin/bash -OP_ROOT=$(git rev-parse --show-toplevel) +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd $DIR/../ && pwd)" + +# NOTE: this is used in a docker build, so do not run any scripts here. # Install packages present in all supported versions of Ubuntu function install_ubuntu_common_requirements() { @@ -8,12 +13,12 @@ function install_ubuntu_common_requirements() { sudo apt-get install -y --no-install-recommends \ autoconf \ build-essential \ + ca-certificates \ clang \ cmake \ make \ cppcheck \ libtool \ - libstdc++-arm-none-eabi-newlib \ gcc-arm-none-eabi \ bzip2 \ liblzma-dev \ @@ -23,7 +28,6 @@ function install_ubuntu_common_requirements() { libcapnp-dev \ curl \ libcurl4-openssl-dev \ - wget \ git \ git-lfs \ ffmpeg \ @@ -45,13 +49,6 @@ function install_ubuntu_common_requirements() { libsqlite3-dev \ libusb-1.0-0-dev \ libzmq3-dev \ - libsdl1.2-dev \ - libsdl-image1.2-dev \ - libsdl-mixer1.2-dev \ - libsdl-ttf2.0-dev \ - libsmpeg-dev \ - libportmidi-dev \ - libfreetype6-dev \ libsystemd-dev \ locales \ opencl-headers \ @@ -59,17 +56,16 @@ function install_ubuntu_common_requirements() { ocl-icd-opencl-dev \ clinfo \ python-dev \ - python3-pip \ qml-module-qtquick2 \ qtmultimedia5-dev \ - qtwebengine5-dev \ qtlocation5-dev \ qtpositioning5-dev \ libqt5sql5-sqlite \ libqt5svg5-dev \ libqt5x11extras5-dev \ libreadline-dev \ - libdw1 + libdw1 \ + valgrind } # Install Ubuntu 21.10 packages @@ -117,39 +113,17 @@ else fi -# install pyenv -if ! command -v "pyenv" > /dev/null 2>&1; then - curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash -fi - -# in the openpilot repo -cd $OP_ROOT +# install python dependencies +$ROOT/update_requirements.sh source ~/.bashrc if [ -z "$OPENPILOT_ENV" ]; then - printf "\nsource %s/tools/openpilot_env.sh" "$OP_ROOT" >> ~/.bashrc + printf "\nsource %s/tools/openpilot_env.sh" "$ROOT" >> ~/.bashrc source ~/.bashrc echo "added openpilot_env to bashrc" fi -# do the rest of the git checkout -git lfs pull -git submodule init -git submodule update - -# install python -PYENV_PYTHON_VERSION=$(cat $OP_ROOT/.python-version) -PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH -pyenv install -s ${PYENV_PYTHON_VERSION} -pyenv rehash -eval "$(pyenv init -)" - -# **** in python env **** -pip install pip==21.3.1 -pip install pipenv==2021.5.29 -pipenv install --dev --deploy - echo -echo "---- FINISH OPENPILOT SETUP ----" -echo "Configure your active shell env by running:" +echo "---- OPENPILOT SETUP DONE ----" +echo "Open a new shell or configure your active shell env by running:" echo "source ~/.bashrc" diff --git a/tools/webcam/Dockerfile b/tools/webcam/Dockerfile index 7de86a4a7b..70317cd924 100644 --- a/tools/webcam/Dockerfile +++ b/tools/webcam/Dockerfile @@ -27,7 +27,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ mkdir /tmp/opencv_build && \ cd /tmp/opencv_build && \ - wget https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz && \ + curl -L -O https://github.com/opencv/opencv/archive/${OPENCV_VERSION}.tar.gz && \ tar -xvf ${OPENCV_VERSION}.tar.gz && \ mv opencv-${OPENCV_VERSION} OpenCV && \ cd OpenCV && mkdir build && cd build && \ @@ -37,4 +37,4 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ make install && \ ldconfig && \ - cd /tmp && rm -rf /tmp/opencv_build + cd / && rm -rf /tmp/* diff --git a/update_requirements.sh b/update_requirements.sh index be7d53f98b..ac9472dca2 100755 --- a/update_requirements.sh +++ b/update_requirements.sh @@ -1,52 +1,54 @@ -#!/bin/bash -e - -cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null - -if ! command -v pyenv &> /dev/null; then - echo "please install pyenv ..." - echo "https://github.com/pyenv/pyenv-installer" - echo "example:" - echo "sudo apt-get update; sudo apt-get install --no-install-recommends make build-essential libssl-dev zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev" - echo "curl https://pyenv.run | bash" - echo "echo 'export PYENV_ROOT=\"\$HOME/.pyenv\"' >> ~/.bashrc" - echo "echo 'export PATH=\"\$PYENV_ROOT/bin:\$PYENV_ROOT/shims:\$PATH\"' >> ~/.bashrc" - echo "exec \"\$SHELL\"" - exit 1 +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if ! command -v "pyenv" > /dev/null 2>&1; then + echo "pyenv install ..." + curl -L https://github.com/pyenv/pyenv-installer/raw/master/bin/pyenv-installer | bash + export PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH fi export MAKEFLAGS="-j$(nproc)" PYENV_PYTHON_VERSION=$(cat .python-version) if ! pyenv prefix ${PYENV_PYTHON_VERSION} &> /dev/null; then - echo "pyenv ${PYENV_PYTHON_VERSION} install ..." - CONFIGURE_OPTS=--enable-shared pyenv install -f ${PYENV_PYTHON_VERSION} -fi - -if ! command -v pipenv &> /dev/null; then - echo "pipenv install ..." - pip install pipenv + # no pyenv update on mac + if [ "$(uname)" == "Linux" ]; then + echo "pyenv update ..." + pyenv update + fi + echo "python ${PYENV_PYTHON_VERSION} install ..." + CONFIGURE_OPTS="--enable-shared" pyenv install -f ${PYENV_PYTHON_VERSION} fi +eval "$(pyenv init --path)" echo "update pip" pip install pip==21.3.1 -pip install pipenv==2021.5.29 +pip install pipenv==2021.11.23 -echo "pip packages install ..." if [ -d "./xx" ]; then + export PIPENV_SYSTEM=1 export PIPENV_PIPFILE=./xx/Pipfile - pipenv install --system --dev --deploy - RUN="" -else - pipenv install --dev --deploy +fi + +if [ -z "$PIPENV_SYSTEM" ]; then + echo "PYTHONPATH=${PWD}" > .env RUN="pipenv run" +else + RUN="" fi -# update shims for newly installed executables (e.g. scons) +echo "pip packages install..." +pipenv install --dev --deploy --clear pyenv rehash -echo "precommit install ..." -$RUN pre-commit install - -# for internal comma repos -[ -d "./xx" ] && (cd xx && $RUN pre-commit install) -[ -d "./notebooks" ] && (cd notebooks && $RUN pre-commit install) +if [ -f "$DIR/.pre-commit-config.yaml" ]; then + echo "precommit install ..." + $RUN pre-commit install + [ -d "./xx" ] && (cd xx && $RUN pre-commit install) + [ -d "./notebooks" ] && (cd notebooks && $RUN pre-commit install) + echo "pre-commit hooks installed" +fi