diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 1210430af27d6f..f04eaad86fd526 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 @@ -351,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: @@ -385,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 b6b45bb24e2df4..0b0df4c5161ee1 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 3e87dcc4334ee0..06c6117b18e485 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 00000000000000..460dd4b0b45d37 --- /dev/null +++ b/.lfsconfig @@ -0,0 +1,3 @@ +[lfs] + url = https://gitlab.com/commaai/openpilot-lfs.git/info/lfs + pushurl = diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d32302e88cf05d..65b60d95ce80ec 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 f906b8f65da292..2ea26b854f4a90 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 2cf24e9cc7cc91..bed4cdab731959 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 67ea043bbab031..25cc81cbb22ae5 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 2c630e70ad31a8..efbd5bf91cd67f 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,10 @@ 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) ======================== diff --git a/SConstruct b/SConstruct index 14450cec9f8925..832550b14bd401 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 497efb07eb9e74..95149bac88b073 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 497efb07eb9e74d9741ebb50e6f1e3ad2e216182 +Subproject commit 95149bac88b0734c647d9a19e67a7c61f779009b diff --git a/common/file_helpers.py b/common/file_helpers.py index 3373d82a03daaa..592b2a199a75e5 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -81,6 +81,17 @@ def _get_fileobject(): 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/realtime.py b/common/realtime.py index 697e816369814a..d577680aeeb0fd 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: diff --git a/common/spinner.py b/common/spinner.py index 27b765196fe699..57242d644dd6a3 100644 --- a/common/spinner.py +++ b/common/spinner.py @@ -24,7 +24,7 @@ def update(self, spinner_text: str): 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/transformations/camera.py b/common/transformations/camera.py index d71f865ca94426..7573877a3e13e3 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/docs/CARS.md b/docs/CARS.md index 2d674e45d72726..725526c762df8b 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 | @@ -154,7 +155,7 @@ | Subaru | Legacy 2015-18 | EyeSight | Stock | 0mph | 0mph | | Subaru | Levorg 2016 | EyeSight | Stock | 0mph | 0mph | | Subaru | Outback 2015-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 b9da6c576c2639..7a074f12de7905 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -11,6 +11,13 @@ Most open source development activity is coordinated through our [GitHub Discuss * Make sure you have a [GitHub account](https://github.com/signup/free) * Fork [our repositories](https://github.com/commaai) on GitHub +### First contribution +Try out some of these first pull requests ideas to dive into the codebase: + +* 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) + ## Pull Requests 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. diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 00000000000000..976929954f3dac 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 00000000000000..269956508556e4 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 00000000000000..3bcd24fb5d5f9d --- /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 00000000000000..6cf5f268c58c98 --- /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 0d33637a1466ad..5715a40b8b7d3f 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 @@ # 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 @@ 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 3444e31a610fe8..124feb1bfced5d 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 5a171ae91dc4f6..0fb2617a5b74ab 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/lfs-push.sh b/lfs-push.sh new file mode 100755 index 00000000000000..7e3c713b0cb216 --- /dev/null +++ b/lfs-push.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +git -c lfs.pushurl=ssh://git@gitlab.com/commaai/openpilot-lfs.git push diff --git a/opendbc b/opendbc index a2e6945429e3fe..5feeaea76a2b82 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit a2e6945429e3fe3e90ca88a4505c3ec695a58c41 +Subproject commit 5feeaea76a2b826bc18ad437a28566e32fd92764 diff --git a/panda b/panda index e3e5d17475d23a..4a0d27cacc9bd4 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit e3e5d17475d23ac3b624814baa830fb677848db1 +Subproject commit 4a0d27cacc9bd4a861d3f24041a8fe3d0fa3d2d7 diff --git a/release/files_common b/release/files_common index fdcc7b3fae5cc9..90785bfefda7fe 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 @@ -598,7 +599,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/athena/athenad.py b/selfdrive/athena/athenad.py index adfa8673f44437..a0c6a0c844005f 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -11,6 +11,7 @@ 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.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 @@ 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)) @@ -86,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) @@ -263,20 +266,35 @@ def do_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]]) + - 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) +@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) + 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 @@ -287,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} @@ -338,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() @@ -419,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": { @@ -428,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 @@ -459,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: @@ -531,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) diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index e06bae5060959f..f19540eb87d6a1 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/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index d96e32abc26b02..06d762c180904e 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -30,7 +30,7 @@ def setUpClass(cls): athenad.ROOT = tempfile.mkdtemp() athenad.SWAGLOG_DIR = swaglog.SWAGLOG_DIR = tempfile.mkdtemp() athenad.Api = MockApi - athenad.LOCAL_PORT_WHITELIST = set([cls.SOCKET_PORT]) + athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): MockParams.restore_defaults() @@ -134,15 +134,16 @@ def test_do_upload(self, host): @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 diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index dfb69e6c625940..8bba9773177a59 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -658,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/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index 24c6f2e30016e9..631b4c987ff0b6 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -69,7 +69,7 @@ def test_loopback(self): 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/car/car_helpers.py b/selfdrive/car/car_helpers.py index e07c302839858c..5bf8efce3959a5 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -69,7 +69,7 @@ def _get_interface_names(): 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 @@ -132,7 +132,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/carstate.py b/selfdrive/car/chrysler/carstate.py index ab3125b011255b..66cd178f887cbc 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -52,7 +52,7 @@ def update(self, cp, cp_cam): 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 c72f155298c6a5..4d5226570c521f 100644 --- a/selfdrive/car/chrysler/chryslercan.py +++ b/selfdrive/car/chrysler/chryslercan.py @@ -8,7 +8,7 @@ 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/fingerprints.py b/selfdrive/car/fingerprints.py index ed5892a0158360..9b280f3b4507f6 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -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 193cc43a00e004..2e46c5b6299916 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -23,7 +23,7 @@ def __init__(self, dbc_name, CP, VM): 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 self.apply_steer_last = apply_steer @@ -34,7 +34,7 @@ def update(self, enabled, CS, frame, actuators, visual_alert, pcm_cancel): 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: diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index a621ab16ab5c5c..d71e65352f96ad 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -25,7 +25,7 @@ def update(self, cp): 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 e98dec584baa74..5a8b0b2dec5d79 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 7155b3ea82d887..12cf6367e3acb5 100755 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -51,7 +51,7 @@ def update(self, c, can_strings): # 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() diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 18492682aabe08..2a331e890eb484 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -90,8 +90,21 @@ def p16(val): 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", @@ -234,7 +247,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() @@ -253,11 +266,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 diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index efc6efadb67110..36fcb3af131a73 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -108,7 +108,7 @@ def update(self, enabled, CS, frame, actuators, 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 diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index e4d644834891ab..5180d2e5110c60 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -36,7 +36,7 @@ def update(self, pt_cp, loopback_cp): 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 @@ def get_can_parser(CP): ("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 @@ def get_can_parser(CP): ("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 d9a63d32e34f04..45fb55098e4f51 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -44,6 +44,11 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ def update(self, c, can_strings): # 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 @@ def update(self, c, can_strings): 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 @@ -222,8 +228,8 @@ def apply(self, c): ret = self.CC.update(enabled, self.CS, self.frame, c.actuators, - hud_v_cruise, c.hudControl.lanesVisible, - c.hudControl.leadVisible, c.hudControl.visualAlert) + hud_v_cruise, hud_control.lanesVisible, + hud_control.leadVisible, hud_control.visualAlert) self.frame += 1 return ret diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 4cb1e0781f26ea..d1ad1c16359247 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 b87c7ade90f224..dfb74016617ad3 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] @@ -174,7 +174,6 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, 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]) @@ -224,7 +223,7 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, if CS.CP.carFingerprint in HONDA_BOSCH: 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, starting, CS.CP.carFingerprint)) + 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)) diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 12a4ae4c29bdeb..029413ad720873 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -201,11 +201,11 @@ def update(self, cp, cp_cam, cp_body): 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 diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 7fcafe67f85adb..db7104cd4f6939 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 47bbd6715d2a13..62dbb24c34e680 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -227,7 +227,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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: @@ -350,7 +350,6 @@ def update(self, c, can_strings): 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 @@ def update(self, c, can_strings): 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,8 +432,9 @@ def update(self, c, can_strings): # 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 @@ -442,9 +442,9 @@ def apply(self, c): c.actuators, c.cruiseControl.cancel, hud_v_cruise, - c.hudControl.lanesVisible, - hud_show_car=c.hudControl.leadVisible, - hud_alert=c.hudControl.visualAlert) + hud_control.lanesVisible, + hud_show_car=hud_control.leadVisible, + hud_alert=hud_control.visualAlert) self.frame += 1 return ret diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 083a52bb863d9c..cbe2369b1457c4 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1385,9 +1385,9 @@ class CAR: 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.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 = set([CAR.ACCORD, 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 32eba22ecafaa8..c32a6875d1ca0a 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -13,7 +13,7 @@ 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 @@ -107,10 +107,10 @@ def update(self, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, hu 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 diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 50031231bcb062..fd3fc78e88cc8c 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 091523ac1a9ada..89e1934cdf8bf9 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -41,11 +41,10 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def update(self, c, can_strings): 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 @@ def update(self, c, can_strings): return self.CS.out def apply(self, c): + hud_control = c.hudControl ret = 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) + 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 ret diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 6022069a2d6661..a7269e9283fab5 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -74,7 +74,7 @@ def _update(self, updated_messages): 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 df2e10dbe75608..5b63888b6b1e67 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 @@ class Buttons: 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 @@ class Buttons: 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 @@ class Buttons: } 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_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} # 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 @@ -1034,6 +1050,7 @@ class Buttons: 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 bbbe48339da354..d628f39e8d39fe 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -91,12 +91,10 @@ def get_std_params(candidate, fingerprint): 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.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.] diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index b4ae938228b885..a4c0ce705d56b2 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -22,7 +22,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 58e1dd7033a89c..f18c30176c4c7e 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -50,9 +50,11 @@ class Buttons: 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 @@ class Buttons: 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 @@ class Buttons: 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 @@ class Buttons: 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 @@ class Buttons: 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 @@ class Buttons: (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 @@ class Buttons: } # 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/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 8b40a050c19efa..40dd5da42ae78c 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -31,7 +31,7 @@ def update(self, enabled, CS, frame, actuators, cruise_cancel, hud_alert, 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 @@ def update(self, enabled, CS, frame, actuators, cruise_cancel, hud_alert, # 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( diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index 05191feedf9b99..4c605395d4a8e5 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -23,16 +23,16 @@ def __init__(self, CP): 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 @@ def update(self, cp, cp_adas, cp_cam): 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 @@ def update(self, cp, cp_adas, cp_cam): 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 @@ def update(self, cp, cp_adas, cp_cam): 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 @@ def get_can_parser(CP): ("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 @@ def get_can_parser(CP): ("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 @@ def get_cam_can_parser(CP): 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 004ea8dd0a8117..4350fb54478bc5 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -21,12 +21,12 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ def update(self, c, can_strings): return self.CS.out def apply(self, c): + hud_control = c.hudControl ret = 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) + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 return ret diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 5d90448ce27575..88adc2626048e4 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -168,7 +168,7 @@ def get_can_parser(CP): ] # CruiseControl is on can1 for OUTBACK and not used for CROSSTREK_2020H - if CP.carFingerprint not in [CAR.OUTBACK, CAR.CROSSTREK_2020H]: + if CP.carFingerprint not in (CAR.OUTBACK, CAR.CROSSTREK_2020H): signals += [ ("Cruise_On", "CruiseControl", 0), ("Cruise_Activated", "CruiseControl", 0), @@ -201,11 +201,11 @@ def get_can_parser(CP): ("Steering", 50), ] - if CP.carFingerprint in [CAR.FORESTER_PREGLOBAL, CAR.LEVORG_PREGLOBAL, CAR.WRX_PREGLOBAL]: + if CP.carFingerprint in (CAR.FORESTER_PREGLOBAL, CAR.LEVORG_PREGLOBAL, CAR.WRX_PREGLOBAL): checks += [ ("Dashlights", 20), ] - elif CP.carFingerprint in [CAR.LEGACY_PREGLOBAL, CAR.LEGACY_PREGLOBAL_2018, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018]: + elif CP.carFingerprint in (CAR.LEGACY_PREGLOBAL, CAR.LEGACY_PREGLOBAL_2018, CAR.OUTBACK_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): checks += [ ("Dashlights", 10), ] @@ -250,7 +250,7 @@ def get_can_parser(CP): ] # CruiseControl is on can1 for OUTBACK and nod used for CROSSTREK_2020H - if CP.carFingerprint not in [CAR.OUTBACK, CAR.CROSSTREK_2020H]: + if CP.carFingerprint not in (CAR.OUTBACK, CAR.CROSSTREK_2020H): checks += [ ("CruiseControl", 20), ] @@ -396,7 +396,7 @@ def get_cam_can_parser(CP): ("ES_LKAS_State", 10), ] - if CP.carFingerprint not in [CAR.CROSSTREK_2020H, CAR.OUTBACK]: + if CP.carFingerprint not in (CAR.CROSSTREK_2020H, CAR.OUTBACK): signals += [ ("Counter", "ES_Distance", 0), ("Signal1", "ES_Distance", 0), diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 91ff4ddc280fca..be92b117c3e8d4 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -119,7 +119,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ -211,8 +211,9 @@ def update(self, c, can_strings): return self.CS.out def apply(self, c): + hud_control = c.hudControl ret = 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) + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 return ret diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index c4d8e0ea306d6c..c31e409b71db34 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -602,6 +602,321 @@ class CAR: }, } +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', + ], + }, + 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', + ], + }, + 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', + ], + }, + 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', + ], + }, +} STEER_THRESHOLD = { CAR.ASCENT: 80, diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index 4efd1c1fe7c4bf..0b58632f0ac80c 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -37,7 +37,7 @@ def update(self, enabled, CS, frame, actuators, cruise_cancel): 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 diff --git a/selfdrive/car/tesla/carstate.py b/selfdrive/car/tesla/carstate.py index 0a45b6f2bb3712..39869baeaee5c6 100644 --- a/selfdrive/car/tesla/carstate.py +++ b/selfdrive/car/tesla/carstate.py @@ -50,7 +50,7 @@ def update(self, cp, cp_cam): 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 4b5b2117877a4a..e8d6ab68547961 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -25,7 +25,6 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 85c6c4c8f77d40..82800d300e258b 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -32,9 +32,9 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler 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]) @@ -52,7 +52,7 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler 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: @@ -92,10 +92,10 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler # 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)) @@ -103,7 +103,7 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler 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)) @@ -113,7 +113,7 @@ def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_aler # - 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 \ diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 7ce5907b9b0a70..f7d353e69112d4 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -79,9 +79,9 @@ def update(self, cp, cp_cam): 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 @@ def update(self, cp, cp_cam): # 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 @@ def update(self, cp, cp_cam): 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 @@ def get_can_parser(CP): ("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 6c1f3e9f41a8df..22080985db8abf 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -40,7 +40,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[]): # py 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,11 +285,12 @@ def update(self, c, can_strings): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): + hud_control = c.hudControl ret = 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.visualAlert, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leadVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 return ret diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 15c8bbfcc69dd3..f26fc72a092bb9 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 692de379b5d82d..c8f48c97680ed4 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -526,6 +526,7 @@ class CAR: 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', @@ -536,6 +537,7 @@ class CAR: 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', @@ -583,6 +585,7 @@ class CAR: 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', @@ -768,6 +771,7 @@ class CAR: 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): [ @@ -797,6 +801,7 @@ class CAR: 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', @@ -1552,6 +1557,7 @@ class CAR: }, 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', @@ -1564,6 +1570,7 @@ class CAR: 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', @@ -1606,7 +1613,7 @@ class CAR: 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'), @@ -1636,11 +1643,11 @@ class CAR: # 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 ec1e7720fa68e8..d845e7053caec8 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -75,7 +75,7 @@ def update(self, enabled, CS, frame, ext_bus, actuators, visual_alert, left_lane # **** 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"] diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 97e3094fa509e2..0e0b0dfc078d86 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -41,8 +41,8 @@ def update(self, pt_cp, cam_cp, ext_cp, trans_type): # 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 @@ -102,7 +102,7 @@ def update(self, pt_cp, cam_cp, ext_cp, trans_type): # 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 overrun coast-down (5) ret.cruiseState.available = True ret.cruiseState.enabled = True diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index b7346ebeaf4f1a..bb67a2726284b8 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -38,7 +38,7 @@ def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): 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 @@ -194,7 +194,7 @@ def update(self, c, can_strings): # 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 @@ -216,11 +216,12 @@ def update(self, c, can_strings): return self.CS.out def apply(self, c): + hud_control = c.hudControl ret = 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) + hud_control.visualAlert, + hud_control.leftLaneVisible, + hud_control.rightLaneVisible, + hud_control.leftLaneDepart, + hud_control.rightLaneDepart) self.frame += 1 return ret diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 0ca7aa512a25ac..d937ecad974829 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -101,19 +101,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: { @@ -292,6 +297,7 @@ class CAR: 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', @@ -306,6 +312,7 @@ class CAR: ], (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', @@ -322,6 +329,7 @@ class CAR: ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572N \xf1\x890681', + b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', ], }, diff --git a/selfdrive/common/modeldata.h b/selfdrive/common/modeldata.h index 9a9414cfa31f12..8d91f7be107f1c 100644 --- a/selfdrive/common/modeldata.h +++ b/selfdrive/common/modeldata.h @@ -10,33 +10,19 @@ 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); - -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); +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 int TICI_CAM_WIDTH = 1928; diff --git a/selfdrive/common/params.cc b/selfdrive/common/params.cc index ce281cf1e635c4..3cd4b7d00d7de9 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -86,7 +86,6 @@ std::unordered_map keys = { {"AccessToken", CLEAR_ON_MANAGER_START | DONT_LOG}, {"AthenadPid", PERSISTENT}, {"AthenadUploadQueue", PERSISTENT}, - {"BootedOnroad", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"CalibrationParams", PERSISTENT}, {"CarBatteryCapacity", PERSISTENT}, {"CarParams", CLEAR_ON_MANAGER_START | CLEAR_ON_PANDA_DISCONNECT | CLEAR_ON_IGNITION_ON}, @@ -122,6 +121,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}, @@ -132,6 +132,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/controls/controlsd.py b/selfdrive/controls/controlsd.py index 944b5f9e5456f9..ff02afe0d8f22e 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -38,7 +38,8 @@ 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()) @@ -150,7 +151,7 @@ def __init__(self, sm=None, pm=None, can_sock=None): 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 @@ -223,7 +224,7 @@ def update_events(self, CS): # 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) @@ -249,11 +250,11 @@ def update_events(self, CS): 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']): @@ -273,7 +274,7 @@ def update_events(self, CS): 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] @@ -294,9 +295,6 @@ def update_events(self, CS): 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 @@ -321,7 +319,7 @@ def update_events(self, CS): 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 @@ -346,7 +344,7 @@ def update_events(self, CS): 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) @@ -369,16 +367,17 @@ def data_sample(self): 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 @@ -407,7 +406,7 @@ def state_transition(self, CS): # 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 @@ -540,11 +539,12 @@ def state_control(self, CS): 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) @@ -579,46 +579,52 @@ def publish_logs(self, CS, start_time, actuators, lac_log): 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.soft_disable_timer]) self.AM.add_many(self.sm.frame, alerts) - self.AM.process_alerts(self.sm.frame, clear_event) - CC.hudControl.visualAlert = self.AM.visual_alert + 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 @@ -631,20 +637,23 @@ def publish_logs(self, CS, start_time, actuators, lac_log): # 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'] @@ -662,18 +671,20 @@ def publish_logs(self, CS, start_time, actuators, lac_log): 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 c8e702c5c7c1d0..bf93b5f47eb87a 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -5,7 +5,6 @@ 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 selfdrive.controls.lib.events import Alert @@ -36,22 +35,9 @@ def active(self, frame: int) -> bool: return frame <= self.end_frame class AlertManager: - def __init__(self): - self.reset() self.alerts: 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. - def add_many(self, frame: int, alerts: List[Alert]) -> None: for alert in alerts: key = alert.alert_type @@ -61,30 +47,18 @@ def add_many(self, frame: int, alerts: List[Alert]) -> None: 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) -> None: + def process_alerts(self, frame: int, clear_event_type=None) -> Optional[Alert]: current_alert = AlertEntry() - for k, v in self.alerts.items(): - if v.alert is None: + for v in self.alerts.values(): + if not v.alert: continue - if clear_event_type is not None and v.alert.event_type == clear_event_type: - self.alerts[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 greater = current_alert.alert is None or (v.alert.priority, v.start_frame) > (current_alert.alert.priority, current_alert.start_frame) 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 f173f5fd98e09e..14be3d5ed8fa99 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -119,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 0048ca661c333d..cd139c062af117 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 +from typing import Dict, Union, Callable, List, Optional from cereal import log, car import cereal.messaging as messaging @@ -42,33 +42,30 @@ class ET: 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 = [] @@ -129,7 +126,7 @@ def __init__(self, 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,14 +136,14 @@ def __gt__(self, alert2) -> bool: 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, @@ -155,13 +152,13 @@ def __init__(self, alert_text_2): # less harsh version of SoftDisable, where the condition is user-triggered class UserSoftDisableAlert(SoftDisableAlert): - def __init__(self, alert_text_2): + 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, @@ -239,7 +236,7 @@ def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, met 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] + 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", @@ -267,6 +264,8 @@ def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, sof EventName.stockFcw: {}, + EventName.lkasDisabled: {}, + # ********** events only containing alerts displayed in all states ********** EventName.joystickDebug: { diff --git a/selfdrive/controls/lib/lane_planner.py b/selfdrive/controls/lib/lane_planner.py index 88a1f6a672960b..660ec3e96506fb 100644 --- a/selfdrive/controls/lib/lane_planner.py +++ b/selfdrive/controls/lib/lane_planner.py @@ -44,21 +44,23 @@ def __init__(self, wide_camera=False): 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 + self.ll_x = lane_lines[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.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 @@ -67,7 +69,7 @@ def get_d_path(self, v_ego, path_t, path_xyz): 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 d1bfc33d4ccb22..c1f3d3d0a164c1 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -18,7 +18,7 @@ def update(self, active, CS, CP, CI, VM, params, last_actuators, desired_curvatu 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 diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index afffcb926b75da..b62fb56e2b860b 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -93,11 +93,11 @@ def update(self, active, CS, CP, CI, VM, params, last_actuators, curvature, curv 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 197f56899d6385..23e2ff3935c101 100644 --- a/selfdrive/controls/lib/latcontrol_lqr.py +++ b/selfdrive/controls/lib/latcontrol_lqr.py @@ -53,7 +53,7 @@ def update(self, active, CS, CP, CI, VM, params, last_actuators, desired_curvatu # 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 d208556ca884fc..b89287644d3761 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -23,7 +23,7 @@ def update(self, active, CS, CP, CI, VM, params, last_actuators, desired_curvatu 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 diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index 148e4e123d320a..f402e6e15e72c0 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 0aa2359ae7956e..a885200e525cde 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -138,7 +138,7 @@ def update(self, sm): 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 @@ def update(self, sm): 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 @@ def publish(self, sm, pm): 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 = [float(x) for x in self.y_pts] + lateralPlan.psis = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]] + lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]] + 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 d6e138abbcf32e..b14ffdc6c8071c 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -13,7 +13,7 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, - output_accel, brake_pressed, cruise_standstill): + brake_pressed, cruise_standstill): """Update longitudinal control state machine""" stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ (v_ego < CP.vEgoStopping and @@ -26,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: @@ -35,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 @@ -65,16 +58,19 @@ def update(self, active, CS, CP, long_plan, accel_limits): """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 @@ -87,8 +83,8 @@ def update(self, active, CS, CP, long_plan, accel_limits): # 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, output_accel, - CS.brakePressed, CS.cruiseState.standstill) + v_target_future, CS.brakePressed, + CS.cruiseState.standstill) if self.long_control_state == LongCtrlState.off or CS.gasPressed: self.reset(CS.vEgo) @@ -96,7 +92,7 @@ def update(self, active, CS, CP, long_plan, accel_limits): # 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 @@ -117,12 +113,6 @@ def update(self, active, CS, CP, long_plan, accel_limits): 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 final_accel = clip(output_accel, accel_limits[0], accel_limits[1]) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 4abe90f8f892b0..4c43985d1fdb35 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 3a8234706fc34a..c07110eefad3cf 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 @@ 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.v_solution = np.zeros(N+1) + self.a_solution = np.zeros(N+1) self.prev_a = np.array(self.a_solution) - self.j_solution = [0.0 for i in range(N)] + 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 @@ def reset(self): 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 @@ def set_cur_state(self, v, a): 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 @@ def update_with_xva(self, x, v, a): 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[:,None]], 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]) diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 41bae4c47518b8..f6b949316ae270 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -4,6 +4,7 @@ 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 @@ def __init__(self, CP, init_v=0.0, init_a=0.0): 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 @@ def update(self, sm): 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 @@ def update(self, sm): 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 @@ def update(self, sm): # 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') diff --git a/selfdrive/controls/lib/tests/test_alertmanager.py b/selfdrive/controls/lib/tests/test_alertmanager.py index bb6664303b7e24..2b606390c78e3a 100755 --- a/selfdrive/controls/lib/tests/test_alertmanager.py +++ b/selfdrive/controls/lib/tests/test_alertmanager.py @@ -34,9 +34,9 @@ def test_duration(self): for frame in range(duration+10): if frame < add_duration: AM.add_many(frame, [alert, ]) - AM.process_alerts(frame) + current_alert = AM.process_alerts(frame) - shown = AM.alert is not None + shown = current_alert is not None should_show = frame <= show_duration self.assertEqual(shown, should_show, msg=f"{frame=} {add_duration=} {duration=}") 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 00000000000000..f2636027e88201 --- /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 a0b1dddfd7b9f5..3f180d3252cd0a 100755 --- a/selfdrive/controls/lib/vehicle_model.py +++ b/selfdrive/controls/lib/vehicle_model.py @@ -5,7 +5,7 @@ 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 cereal import car +ACCELERATION_DUE_TO_GRAVITY = 9.8 + + class VehicleModel: def __init__(self, CP: car.CarParams): """ @@ -43,7 +46,7 @@ def update_params(self, stiffness_factor: float, steer_ratio: float) -> None: 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 @@ def steady_state_sol(self, sa: float, u: float) -> np.ndarray: 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 @@ def curvature_factor(self, u: float) -> float: 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 14de2eff328cc9..d4b0733692a360 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -135,7 +135,7 @@ def update(self, sm, rr, enable_lead): 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 @@ def update(self, sm, rr, enable_lead): 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 diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index 98767d6e2f0506..2502bed06b6655 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -61,7 +61,7 @@ def test_alert_text_length(self): 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]): diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 5f181e0a272e0d..76e809d2c469b9 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -118,6 +118,6 @@ def get_arg_parser(): 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/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 098e39fbe8778f..326e68f8e79b97 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -41,5 +41,5 @@ def get_fingerprint(lr): 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/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 204d2480c3b993..8132a46552b996 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -13,42 +13,45 @@ 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 @@ 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/rt/atrace.sh b/selfdrive/debug/internal/rt/atrace.sh deleted file mode 100755 index 826178c3299be0..00000000000000 --- 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 00593bfca4fcb4..00000000000000 --- 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 c536ca9c6df51b..00000000000000 --- 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 9e89add6bbfb7c..36eb112e4441eb 100755 --- a/selfdrive/debug/internal/sensor_test_bootloop.py +++ b/selfdrive/debug/internal/sensor_test_bootloop.py @@ -17,7 +17,7 @@ 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 b25a4218222554..00000000000000 --- 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 ba6c59220b06e8..00000000000000 --- 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 3ed3a51589ea19..00000000000000 --- 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/profiling/palanteer/.gitignore b/selfdrive/debug/profiling/palanteer/.gitignore new file mode 100644 index 00000000000000..158e7b0759723e --- /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 00000000000000..e912a9367fa234 --- /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 bcb741f54ea533..a6e67d2b24f66a 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -17,13 +17,13 @@ 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 e63122b6a20613..0a459bb7199782 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 d2bcce6a1c44ae..77ddcbc2d49cd2 100644 --- a/selfdrive/hardware/base.py +++ b/selfdrive/hardware/base.py @@ -1,11 +1,12 @@ from abc import abstractmethod, ABC from collections import namedtuple +from typing import Dict ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic']) 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/hardware.py b/selfdrive/hardware/eon/hardware.py index fa275c5a9c7ef8..4ab9f81fcf26cd 100644 --- a/selfdrive/hardware/eon/hardware.py +++ b/selfdrive/hardware/eon/hardware.py @@ -10,6 +10,12 @@ 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 @@ def get_os_version(self): 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/hardware.py b/selfdrive/hardware/tici/hardware.py index 855eee908efc62..31bfa736c031f7 100644 --- a/selfdrive/hardware/tici/hardware.py +++ b/selfdrive/hardware/tici/hardware.py @@ -320,6 +320,9 @@ def get_gpu_usage_percent(self): 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/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 5ffb17ac56a88e..26373506437e66 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -8,6 +8,7 @@ import os import copy +from typing import NoReturn import numpy as np import cereal.messaging as messaging from cereal import log @@ -99,7 +100,7 @@ def reset(self, rpy_init=RPY_INIT, valid_blocks=0, smooth_from=None): self.old_rpy = smooth_from self.old_rpy_weight = 1.0 - def get_valid_idxs(self, ): + 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)) @@ -183,11 +184,11 @@ def get_msg(self): msg.liveCalibration.rpyCalibSpread = [float(x) for x in self.calib_spread] 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']) @@ -215,7 +216,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 0350625a915c1e..35fbd90e56860f 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) { diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index dac95dcd7bbce3..031c56ceb9edeb 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -5,6 +5,7 @@ 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 @@ def generate_code(generated_dir): 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 @@ def generate_code(generated_dir): 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 @@ def generate_code(generated_dir): [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 @@ def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offs 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 7450f76be7e3bb..f22f503ee0cd74 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 fa529459328f75..023479d10e1809 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -158,7 +158,7 @@ def generate_code(generated_dir): 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 adb9098d4d61f1..199cc7e2050922 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -232,7 +232,7 @@ def generate_code(generated_dir, N=4): 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 914e0d0223714e..03de2d64622a9f 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -16,10 +16,12 @@ 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 @@ def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset): 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 @@ def handle_log(self, t, which, msg): 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_locationd.py b/selfdrive/locationd/test/test_locationd.py index 5194ce4441efc8..76f82efc10b86c 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -51,6 +51,7 @@ def test_liblocalizer(self): 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 04422a81cddc2b..9cffbeac40cff3 100644 --- a/selfdrive/locationd/test/ublox.py +++ b/selfdrive/locationd/test/ublox.py @@ -338,7 +338,7 @@ def format(self, msg): 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 @@ def write(self, buf): 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/logcatd/logcatd_systemd.cc b/selfdrive/logcatd/logcatd_systemd.cc index 7c7800dfea9347..dabb11adcbefbb 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/config.py b/selfdrive/loggerd/config.py index b626073ce41b7d..6cd20a68ab40e7 100644 --- a/selfdrive/loggerd/config.py +++ b/selfdrive/loggerd/config.py @@ -13,6 +13,13 @@ 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/tests/test_deleter.py b/selfdrive/loggerd/tests/test_deleter.py index 89904e694a0232..e0a063bd43e8b0 100644 --- a/selfdrive/loggerd/tests/test_deleter.py +++ b/selfdrive/loggerd/tests/test_deleter.py @@ -18,7 +18,7 @@ def fake_statvfs(self, d): 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 8562f5f21d60d7..54c65db73dda89 100755 --- a/selfdrive/loggerd/tests/test_uploader.py +++ b/selfdrive/loggerd/tests/test_uploader.py @@ -39,7 +39,7 @@ def emit(self, record): class TestUploader(UploaderTestCase): def setUp(self): - super(TestUploader, self).setUp() + super().setUp() log_handler.reset() def start_thread(self): diff --git a/selfdrive/logmessaged.py b/selfdrive/logmessaged.py index 9c40976562abd9..1d1fab51620092 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 5bbb4bd9761785..4b97855f348c6a 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -22,7 +22,7 @@ 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 = [] diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index fdda0deb9ac002..b07362dd4d39f2 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -5,7 +5,7 @@ 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 9f1db9d860afa4..30f4b237ed57ab 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -5,6 +5,7 @@ import subprocess import sys import traceback +from typing import List, Tuple, Union import cereal.messaging as messaging import selfdrive.crash as crash @@ -25,7 +26,7 @@ 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"), @@ -58,7 +59,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") @@ -101,12 +102,12 @@ def manager_init(): 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) @@ -118,7 +119,7 @@ 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}) @@ -130,8 +131,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) @@ -178,7 +178,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 2030b35e32d4e6..a423b1ff842386 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -4,6 +4,7 @@ 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 @@ ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None -def launcher(proc, name): +def launcher(proc: str, name: str) -> None: try: # import the process mod = importlib.import_module(proc) @@ -37,7 +38,7 @@ def launcher(proc, name): cloudlog.bind(daemon=name) # exec the process - mod.main() + getattr(mod, 'main')() except KeyboardInterrupt: cloudlog.warning(f"child {proc} got SIGINT") except Exception: @@ -47,13 +48,13 @@ def launcher(proc, name): 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() @@ -65,7 +66,8 @@ class ManagerProcess(ABC): unkillable = False daemon = False sigkill = False - proc = None + persistent = False + proc: Optional[Process] = None enabled = True name = "" @@ -75,24 +77,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 @@ -106,9 +109,9 @@ def check_watchdog(self, started): 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: @@ -118,7 +121,7 @@ def stop(self, retry=True, block=True): self.shutting_down = True if not block: - return + return None join_process(self.proc, 5) @@ -148,7 +151,7 @@ def stop(self, retry=True, block=True): return ret - def signal(self, sig): + def signal(self, sig: int) -> None: if self.proc is None: return @@ -156,6 +159,10 @@ def signal(self, sig): 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) @@ -182,10 +189,10 @@ def __init__(self, name, cwd, cmdline, enabled=True, persistent=False, drivervie 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() @@ -212,12 +219,12 @@ def __init__(self, name, module, enabled=True, persistent=False, driverview=Fals self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt - def prepare(self): + def prepare(self) -> None: if self.enabled: 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() @@ -242,10 +249,10 @@ def __init__(self, name, module, param_name, enabled=True): 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') @@ -262,18 +269,18 @@ def start(self): 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 = [] @@ -284,7 +291,8 @@ def ensure_running(procs, started, driverview=False, not_run=None): p.stop(block=False) elif p.persistent: p.start() - elif p.driverview and driverview: + elif getattr(p, 'driverview', False) and driverview: + # TODO: why is driverview an argument here? can this be done with the name? p.start() elif started: p.start() diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 8fc07a06ef1789..b5fc01364eae04 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -19,7 +19,7 @@ NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC, persistent=EON, sigkill=EON), NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), NativeProcess("ui", "selfdrive/ui", ["./ui"], persistent=True, watchdog_max_dt=(5 if TICI else None)), - NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"]), + NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], persistent=True), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), NativeProcess("boardd", "selfdrive/boardd", ["./boardd"], enabled=False), PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), @@ -36,6 +36,7 @@ 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 03b487bbbfdfa3..0facf3fa0b21bc 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) { - util::set_thread_name("calibration"); - util::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,20 +62,22 @@ 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; - bool valid = 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) { @@ -118,8 +102,8 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { 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()), valid); - posenet_publish(pm, extra.frame_id, vipc_dropped_frames, *model_output, extra.timestamp_eof, valid); + 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; @@ -138,9 +122,6 @@ int main(int argc, char **argv) { 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)); @@ -160,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/driving.cc b/selfdrive/modeld/models/driving.cc index 22d0716f67140f..69aeb91be1d7ab 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -327,10 +327,10 @@ 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 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(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index c470612dc0be8d..efb00ecb7723e5 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -205,8 +205,8 @@ def set_policy(self, model_data, car_speed): 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 b725ca9ddab5aa..e3ebdad6374710 100755 --- a/selfdrive/pandad.py +++ b/selfdrive/pandad.py @@ -4,7 +4,7 @@ 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 00000000000000..8fd1f60ca1e687 --- /dev/null +++ b/selfdrive/statsd.py @@ -0,0 +1,122 @@ +#!/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: + 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: + time.sleep(1e-3) + + started_prev = sm['deviceState'].started + sm.update(0) + + # 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 cefc2166ec5c78..beb544b1b1df13 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -97,7 +97,7 @@ def step(self, v_lead=0.0, prob=1.0, v_cruise=50.): '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/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index eb058b354c15f6..72f5e51bb2419d 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_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 012c34c80c5533..c343275b6bacb5 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -6629322b3f6fa417e3b58a1d9dfe149afce1dc66 +7d3ad941bc4ba4c923af7a1d7b48544bfc0d3e13 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index ccdbc2754f6a9e..8a13e4b18e1edf 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -89,7 +89,7 @@ def send(self, dat): 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 @@ def update(self, timeout=-1): 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 8415bc8de7d86f..6ebdb327b302a7 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -8118420b292f4c67ce7e196fc2121571da330e65 \ 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 3283ec81910576..6c33b46224569a 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -143,7 +143,7 @@ def format_diff(results, 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, f"Cars missing routes: {str(untested)}" diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 7a01e72122f63a..5f73176fabeaf2 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -5,7 +5,6 @@ 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 96bb5ffa02faff..4ea60a76a8cc58 100755 --- a/selfdrive/test/test_fingerprints.py +++ b/selfdrive/test/test_fingerprints.py @@ -19,7 +19,7 @@ def _get_fingerprints(): car_name = car_folder.split('/')[-1] try: fingerprints[car_name] = __import__(f'selfdrive.car.{car_name}.values', fromlist=['FINGERPRINTS']).FINGERPRINTS - except (ImportError, IOError, AttributeError): + except (ImportError, OSError, AttributeError): pass return fingerprints diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index bc44aa3ea1d6ff..36e8c5499f0db5 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -56,7 +56,7 @@ "./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, diff --git a/selfdrive/test/test_routes.py b/selfdrive/test/test_routes.py index a14bafcb0b54c9..2652048772cd25 100755 --- a/selfdrive/test/test_routes.py +++ b/selfdrive/test/test_routes.py @@ -81,6 +81,7 @@ 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), diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 9c0736391728e8..0dc4c5a1d355b9 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -2,12 +2,14 @@ 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 @@ def calculate(self, peripheralState, ignition): # 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 @@ def perform_pulse_measurement(now): 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 @@ def _perform_integration(self, t, current_power): 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 1b467f9627bbaf..7982afff2302f7 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -3,7 +3,7 @@ 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.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]) @@ -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 74da5fe35cbba0..6a7e9122c1ebb0 100755 --- a/selfdrive/timezoned.py +++ b/selfdrive/timezoned.py @@ -3,6 +3,7 @@ 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 e69de00937ca42..df96c222c0b559 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -7,6 +7,7 @@ import subprocess import time import glob +from typing import NoReturn import sentry_sdk @@ -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()) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 5d656f889ef9c8..5f8a96edf025ae 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 d33e5766919681..62f93c13a9992e 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') diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc new file mode 100644 index 00000000000000..55b9a474742076 --- /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/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index dd519dd6615124..2d5651dcce06fc 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/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 283c046543c6bb..4e87ed0dbdf9ad 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", }, { diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index e9f9bbfa09752d..e394d1248afe1b 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -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); } @@ -79,6 +81,7 @@ void OnroadWindow::offroadTransition(bool offroad) { if (map == nullptr && (uiState()->has_prime || !MAPBOX_TOKEN.isEmpty())) { MapWindow * m = new MapWindow(get_mapbox_settings()); m->setFixedWidth(topWidget(this)->width() / 2); + 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(); }); } @@ -349,7 +352,7 @@ void NvgWindow::paintGL() { CameraViewWidget::paintGL(); UIState *s = uiState(); - if (s->scene.world_objects_visible) { + if (s->worldObjectsVisible()) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py index 4dd99951bf2d46..905d41a6344f7e 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/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index ff9f7754d16b31..a16923894d14ee 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" @@ -202,9 +202,15 @@ void CameraViewWidget::paintGL() { 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); @@ -289,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 4cfba3c6fb2cae..03709cbdd87720 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -36,11 +36,20 @@ class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { 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 @@ class CameraViewWidget : public QOpenGLWidget, protected QOpenGLFunctions { std::atomic stream_type; QThread *vipc_thread = nullptr; - protected slots: void vipcConnected(VisionIpcClient *vipc_client); }; diff --git a/selfdrive/ui/replay/camera.cc b/selfdrive/ui/replay/camera.cc index b37332ae7ea2c5..1912c9b5811a3b 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 84dc76694ba4c7..1585bb42d207fc 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 5955d1bfcb05a3..59e43cb58f89ff 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/selfdrive/ui/replay/framereader.cc @@ -15,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); diff --git a/selfdrive/ui/replay/logreader.cc b/selfdrive/ui/replay/logreader.cc index 8e2836a4ff755e..d836ef11f87301 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 062837885b1c67..180aecc7e13ee9 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 8155eb3456744f..98e211fb53fcbe 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -31,15 +31,18 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s 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(); + doStop(); } -void Replay::stop() { +void Replay::doStop() { if (!stream_thread_ && segments_.empty()) return; qDebug() << "shutdown: in progress..."; @@ -62,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()) { @@ -111,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"); @@ -222,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(); @@ -256,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(); @@ -318,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 a3e6efaadbcb39..4f979905065f84 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 @@ class Replay : public QObject { ~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); @@ -79,5 +95,5 @@ protected slots: 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 23c27073cbc4c7..fe6e21a91ad820 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 5b9b7cdeb7db72..6063dfe7d57fec 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 d6791465f21132..d6eba2e2a87018 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 726e65cb9430a3..cd4b179cdce41d 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.h b/selfdrive/ui/soundd/sound.h index b493c3a3d8e0e2..82b360fd3850ef 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 6b3453026bcc3c..6b6aea44778733 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -18,7 +18,7 @@ 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 a8453889cbaa0b..c33b61c19eaa96 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]); } } @@ -110,15 +112,10 @@ static void update_state(UIState *s) { 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]; @@ -189,35 +186,34 @@ 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; } - UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", @@ -237,12 +233,7 @@ UIState::UIState(QObject *parent) : QObject(parent) { void UIState::update() { update_sockets(this); update_state(this); - update_status(this); - - if (scene.started != started_prev || sm->frame == 1) { - started_prev = scene.started; - emit offroadTransition(!scene.started); - } + updateStatus(); if (sm->frame % UI_FREQ == 0) { watchdog_kick(); @@ -301,9 +292,11 @@ 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) { diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 7c16e4ae6f4f2b..bc15257a085565 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 @@ -106,6 +114,10 @@ class UIState : public QObject { 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; @@ -151,6 +163,7 @@ class Device : public QObject { bool ignition_on = false; int last_brightness = 0; FirstOrderFilter brightness_filter; + QFuture brightness_future; void updateBrightness(const UIState &s); void updateWakefulness(const UIState &s); diff --git a/selfdrive/updated.py b/selfdrive/updated.py index ae3502023b0614..f5e915b074aaa0 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -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 diff --git a/selfdrive/version.py b/selfdrive/version.py index a0cb61875e58a5..0c1a44f23663fb 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -53,12 +53,24 @@ 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 is_prebuilt() -> bool: @@ -117,7 +129,9 @@ def is_dirty() -> bool: 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 00000000000000..056074e1684cd4 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 00000000000000..e984307a2c468e 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 00000000000000..a600c765bbf7bc 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 00000000000000..185100d2cdc46e 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 00000000000000..e3d94c4bc2d648 --- /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 00000000000000..7ee3eda293deb3 Binary files /dev/null and b/third_party/acados/Darwin/t_renderer differ diff --git a/tools/README.md b/tools/README.md index 81bff69e1d6d04..a61d9268d2c132 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/joystickd.py b/tools/joystick/joystickd.py index ba7b29df543fd5..0e3269556ab4c2 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -34,7 +34,8 @@ def update(self): 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 +46,10 @@ def update(self): 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 diff --git a/tools/lib/auth.py b/tools/lib/auth.py index d5d2a60bb0dcf9..0e9e7705b5a73b 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 bd7980f6e0d68c..ac5962462ee688 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -572,15 +572,13 @@ def __init__(self, fn, frame_type, index_data, readahead=False, readbehind=False 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 1c9cc81e32c7af..fc7a8dcb70f51d 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -12,10 +12,10 @@ 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 @@ def __init__(self, log_paths, wraparound=False): 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 @@ def _inc(self): 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 @@ def seek(self, ts): 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 @@ def __init__(self, fn, canonicalize=True, only_union_types=False): 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 @@ def __iter__(self): # 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 e534a7c8f06d9c..c7feb6c3ed7f46 100755 --- a/tools/lib/robust_logreader.py +++ b/tools/lib/robust_logreader.py @@ -13,7 +13,7 @@ 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 @@ def __init__(self, fn, canonicalize=True, only_union_types=False): # pylint: di 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 76ca2bcf5487df..c87bd04b9ebcdd 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -7,9 +7,10 @@ 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 @@ 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 @@ def _get_segments_remote(self): _, 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 @@ def _get_segments_remote(self): 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 @@ def _get_segments_remote(self): 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 @@ def _get_segments_local(self, data_dir): 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 = f'{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 @@ def _get_segments_local(self, data_dir): 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(f'Could not find segments for route {self.route_name} in data directory {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 @@ def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera @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 82f7c971fce0ef..8ad2f96b3ace04 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 @@ def get_length(self): 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 diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 1072c1caaff4ec..fa1a23dd4773c0 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 81ae00d64dced7..59108312acffdf 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 8ab150e82148f1..2e79aecbcacd81 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 27cb0dfb5d5efd..00000000000000 --- 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 d488750701573a..7cafaf60acf8e0 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 3e695c19905104..edaec9c80a15b0 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -7,27 +7,20 @@ 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/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py index b04826a5dbe75a..3fefd45ba84b9a 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: diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index f6cf0d9b0b7922..4ffd5bea14216d 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 9924a40b11cab9..2e19faefcd33ab 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -93,7 +93,7 @@ def cam_callback(self, image): 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/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 6ad7931b5619c7..e688c79c12be86 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 7de86a4a7be6f8..70317cd92483a3 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 be7d53f98b10c1..ac9472dca2e874 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