diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 1210430af2..1bee59e458 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -351,11 +351,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 +386,7 @@ jobs: - name: Test car models run: | ${{ env.RUN }} "scons -j$(nproc) --test && \ - FILEREADER_CACHE=1 coverage run selfdrive/test/test_models.py && \ + FILEREADER_CACHE=1 pytest selfdrive/test/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" env: NUM_JOBS: 4 diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index b6b45bb24e..0b0df4c516 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -45,7 +45,20 @@ jobs: - uses: actions/checkout@v2 with: submodules: true - lfs: true + + # HACK: cache LFS objects since they count against our quota + # https://github.com/actions/checkout/issues/165#issuecomment-657673315 + - name: Create LFS file list + run: git lfs ls-files -l | cut -d' ' -f1 | sort > .lfs-assets-id + - name: Restore LFS cache + uses: actions/cache@v2 + id: lfs-cache + with: + path: .git/lfs + key: ${{ runner.os }}-lfs-${{ hashFiles('.lfs-assets-id') }} + - name: Git LFS Pull + run: git lfs pull + - name: Build Docker image run: | eval "$BUILD" diff --git a/.gitignore b/.gitignore index 3e87dcc433..06c6117b18 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ venv/ +.env .clang-format .DS_Store .tags diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d32302e88c..65b60d95ce 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,14 +13,14 @@ repos: rev: v0.910-1 hooks: - id: mypy - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)/' additional_dependencies: ['git+https://github.com/numpy/numpy-stubs', 'types-requests', 'types-atomicwrites', 'types-pycurl'] - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(opendbc)|(laika_repo)|(rednose_repo)|(selfdrive/debug)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/debug/)/' args: - --select=F,E112,E113,E304,E502,E701,E702,E703,E71,E72,E731,W191,W6 - --statistics @@ -31,7 +31,7 @@ repos: entry: pylint language: system types: [python] - exclude: '^(pyextra)|(cereal)|(rednose)|(panda)|(laika)|(laika_repo)|(rednose_repo)/' + exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)/' - repo: local hooks: - id: cppcheck @@ -39,7 +39,7 @@ repos: entry: cppcheck language: system types: [c++] - exclude: '^(third_party)|(pyextra)|(cereal)|(opendbc)|(panda)|(tools)|(selfdrive/modeld/thneed/debug)|(selfdrive/modeld/test)|(selfdrive/camerad/test)/|(installer)' + exclude: '^(third_party/)|(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)/|(installer/)' args: - --error-exitcode=1 - --language=c++ diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index f906b8f65d..9229fb1765 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -1,70 +1,26 @@ 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}" - -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 && \ +ENV PIPENV_SYSTEM=1 +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 /tmp/* && \ + rm -rf /var/lib/apt/lists/* && \ pip uninstall -y pipenv + +ENV PYENV_VERSION=3.8.10 +ENV PYENV_ROOT="/root/.pyenv" +ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" diff --git a/Pipfile b/Pipfile index 5879ce09a9..0ec00008b3 100644 --- a/Pipfile +++ b/Pipfile @@ -25,9 +25,12 @@ pre-commit = "*" pycurl = "*" pygame = "*" pyprof2calltree = "*" +pytest = "*" +pytest-xdist = "*" reverse_geocoder = "*" scipy = "*" sphinx = "*" +sphinx-sitemap = "*" sphinx-rtd-theme = "*" breathe = "*" subprocess32 = "*" diff --git a/Pipfile.lock b/Pipfile.lock index bd7b9085af..15463e3e71 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "c9e828f81e737214b3b61a090c34abac03486acf95e461e20f3ba1c08af1aa23" + "sha256": "658c6ab1fd200a6c84b1dec7a3b15b3d01ce911a76b1201bd4495b3031eac119" }, "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": [ @@ -174,72 +174,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": [ @@ -300,7 +309,7 @@ "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" ], - "markers": "python_version < '4.0' and python_full_version >= '3.6.1'", + "markers": "python_full_version >= '3.6.1' and python_version < '4.0'", "version": "==5.10.1" }, "itsdangerous": { @@ -329,31 +338,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 +489,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 +548,63 @@ }, "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" + "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 +638,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 +701,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 +765,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 +893,11 @@ }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5", + "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.0" }, "scons": { "hashes": [ @@ -904,11 +909,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:0db297ab32e095705c20f742c3a5dac62fe15c4318681884053d0898e5abb2f6", - "sha256:789a11a87ca02491896e121efdd64e8fd93327b69e8f2f7d42f03e2569648e88" + "sha256:2a1757d6611e4bec7d672c2b7ef45afef79fed201d064f53994753303944f5a8", + "sha256:e4cb107e305b2c1b919414775fa73a9997f996447417d22b98e7610ded1e9eb5" ], "index": "pypi", - "version": "==1.5.0" + "version": "==1.5.1" }, "setproctitle": { "hashes": [ @@ -937,14 +942,6 @@ "index": "pypi", "version": "==1.2.2" }, - "setuptools": { - "hashes": [ - "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", - "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" - ], - "markers": "python_version >= '3.6'", - "version": "==59.5.0" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -982,7 +979,7 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "tqdm": { @@ -998,7 +995,7 @@ "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e", "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b" ], - "markers": "python_version < '3.10'", + "markers": "python_version < '3.10' and python_version < '3.10'", "version": "==4.0.1" }, "urllib3": { @@ -1018,11 +1015,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 +1097,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 +1111,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" @@ -1224,10 +1216,10 @@ }, "control": { "hashes": [ - "sha256:34eeca077cf002a2f22a9334c8998ec5b3bcc0fdae2aac790a923cf8bc80245a" + "sha256:8c9084bf386eafcf5d74008f780fae6dec68d243d18a380c866ac10a3549f8d3" ], "index": "pypi", - "version": "==0.9.0" + "version": "==0.9.1" }, "coverage": { "hashes": [ @@ -1284,30 +1276,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": [ @@ -1327,10 +1318,10 @@ }, "distlib": { "hashes": [ - "sha256:c8b54e8454e5bf6237cc84c20e8264c3e991e824ef27e8f1e81049867d861e31", - "sha256:d982d0751ff6eaaab5e2ec8e691d949ee80eddf01a62eaa96ddb11531fe16b05" + "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b", + "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579" ], - "version": "==0.3.3" + "version": "==0.3.4" }, "docutils": { "hashes": [ @@ -1340,6 +1331,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", @@ -1365,19 +1364,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": [ @@ -1388,19 +1387,19 @@ }, "hypothesis": { "hashes": [ - "sha256:25e0e581f999281ff765aba3c3f1e3ce289e82a1ff1b9de2c5082b37113aa448", - "sha256:adb42a52bc3c1300d0a0a221870ce5c264bee2e54b0473de6767c3ae02649b5f" + "sha256:3cae408a92917fe288b0932eb3bb758f1585bf8b6e1d4ca4a666a2835c707db3", + "sha256:5002ce1f27fd94b53b0046ac7d20dab17fd74ebc6c90d401979588eadc46f84e" ], "index": "pypi", - "version": "==6.30.1" + "version": "==6.34.1" }, "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": [ @@ -1418,6 +1417,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", @@ -1493,11 +1499,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": [ @@ -1576,52 +1582,52 @@ }, "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": [ @@ -1633,32 +1639,29 @@ }, "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": [ @@ -1669,11 +1672,11 @@ }, "myst-parser": { "hashes": [ - "sha256:40124b6f27a4c42ac7f06b385e23a9dcd03d84801e9c7130b59b3729a554b1f9", - "sha256:f7f3b2d62db7655cde658eb5d62b2ec2a4631308137bd8d10f296a40d57bbbeb" + "sha256:617a90ceda2162ebf81cd13ad17d879bd4f49e7fb5c4f177bb905272555a2268", + "sha256:a6473b9735c8c74959b49b36550725464f4aecc4481340c9a5f9153829191f83" ], "index": "pypi", - "version": "==0.15.2" + "version": "==0.16.1" }, "nodeenv": { "hashes": [ @@ -1684,75 +1687,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": [ @@ -1772,66 +1744,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": [ @@ -1848,12 +1819,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": { @@ -1865,75 +1843,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": [ @@ -1974,6 +1952,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", @@ -2030,11 +2032,11 @@ }, "requests": { "hashes": [ - "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24", - "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7" + "sha256:8e5643905bf20a308e25e4c1dd379117c09000bf8a82ebccc462cfb1b34a16b5", + "sha256:f71a09d7feba4a6b64ffd8e9d9bc60f9bf7d7e19fd0e04362acb1cfc2e3d98df" ], "index": "pypi", - "version": "==2.26.0" + "version": "==2.27.0" }, "reverse-geocoder": { "hashes": [ @@ -2078,22 +2080,6 @@ "index": "pypi", "version": "==1.7.3" }, - "setuptools": { - "hashes": [ - "sha256:6d10741ff20b89cd8c6a536ee9dc90d3002dec0226c78fb98605bfb9ef8a7adf", - "sha256:d144f85102f999444d06f9c0e8c737fd0194f10f2f7e5fdb77573f6e2fa4fad0" - ], - "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" - }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", @@ -2118,11 +2104,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": [ @@ -2132,6 +2118,13 @@ "index": "pypi", "version": "==1.0.0" }, + "sphinx-sitemap": { + "hashes": [ + "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737" + ], + "index": "pypi", + "version": "==2.2.0" + }, "sphinxcontrib-applehelp": { "hashes": [ "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", @@ -2202,23 +2195,23 @@ "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2'", "version": "==0.10.2" }, "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.10'", + "markers": "python_version < '3.10' and python_version < '3.10'", "version": "==4.0.1" }, "urllib3": { @@ -2231,11 +2224,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 6910f8d316..efbd5bf91c 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,7 +1,17 @@ +Version 0.8.13 (2022-XX-XX) +======================== + * Improved driver monitoring + * Improved camera focus on the comma two + * Subaru ECU firmware fingerprinting thanks to martinl! + * Hyundai Santa Fe Plug-in Hybrid 2022 support thanks to sunnyhaibin! + * Subaru Impreza 2020 support thanks to martinl! + Version 0.8.12 (2021-12-15) ======================== * New driving model * Improved behavior around exits + * Better pose accuracy at high speeds, allowing max speed of 90mph + * Fully incorporated comma three data into all parts of training stack * Improved follow distance * Better longitudinal policy, especially in low speed traffic * New alert sounds diff --git a/SConstruct b/SConstruct index 14450cec9f..6b45d60f2e 100644 --- a/SConstruct +++ b/SConstruct @@ -127,6 +127,7 @@ else: "/opt/homebrew/lib", "/usr/local/opt/openssl/lib", "/opt/homebrew/opt/openssl/lib", + f"#third_party/acados/{arch}/lib", "/System/Library/Frameworks/OpenGL.framework/Libraries", ] cflags += ["-DGL_SILENCE_DEPRECATION"] diff --git a/cereal b/cereal index e5a04ab458..e83ec3be7d 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit e5a04ab458afd52cf630cc9e35ccdc10efba6688 +Subproject commit e83ec3be7d83b8d4ce227e7c2027dd1ca5f930b4 diff --git a/common/ffi_wrapper.py b/common/ffi_wrapper.py index e6ee2c23f7..a228b40256 100644 --- a/common/ffi_wrapper.py +++ b/common/ffi_wrapper.py @@ -28,7 +28,7 @@ def ffi_wrap(name, c_code, c_header, tmpdir="/tmp/ccache", cflags="", libraries= try: mod = __import__(cache) except Exception: - print("cache miss {0}".format(cache)) + print(f"cache miss {cache}") compile_code(cache, c_code, c_header, tmpdir, cflags, libraries) mod = __import__(cache) finally: diff --git a/common/file_helpers.py b/common/file_helpers.py index 2037a665b0..3373d82a03 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -35,7 +35,7 @@ def get_tmpdir_on_same_filesystem(path): if len(parts) > 1 and parts[1] == "scratch": return "/scratch/tmp" elif len(parts) > 2 and parts[2] == "runner": - return "/{}/runner/tmp".format(parts[1]) + return f"/{parts[1]}/runner/tmp" return "/tmp" diff --git a/common/profiler.py b/common/profiler.py index e4d208acae..8b1a7a8cfa 100644 --- a/common/profiler.py +++ b/common/profiler.py @@ -42,4 +42,4 @@ class Profiler(): print("%30s: %9.2f avg: %7.2f percent: %3.0f IGNORED" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) else: print("%30s: %9.2f avg: %7.2f percent: %3.0f" % (n, ms*1000.0, ms*1000.0/self.iter, ms/self.tot*100)) - print("Iter clock: %2.6f TOTAL: %2.2f" % (self.tot/self.iter, self.tot)) + print(f"Iter clock: {self.tot / self.iter:2.6f} TOTAL: {self.tot:2.2f}") diff --git a/common/realtime.py b/common/realtime.py index 762410f09e..d577680aee 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -39,7 +39,7 @@ def set_realtime_priority(level: int) -> None: def set_core_affinity(core: int) -> None: if not PC: - os.sched_setaffinity(0, [core,]) + os.sched_setaffinity(0, [core,]) # type: ignore[attr-defined] def config_realtime_process(core: int, priority: int) -> None: @@ -79,7 +79,7 @@ class Ratekeeper: remaining = self._next_frame_time - sec_since_boot() self._next_frame_time += self._interval if self._print_delay_threshold is not None and remaining < -self._print_delay_threshold: - print("%s lagging by %.2f ms" % (self._process_name, -remaining * 1000)) + print(f"{self._process_name} lagging by {-remaining * 1000:.2f} ms") lagged = True self._frame += 1 self._remaining = remaining diff --git a/common/spinner.py b/common/spinner.py index 27b765196f..57242d644d 100644 --- a/common/spinner.py +++ b/common/spinner.py @@ -24,7 +24,7 @@ class Spinner(): except BrokenPipeError: pass - def update_progress(self, cur: int, total: int): + def update_progress(self, cur: float, total: float): self.update(str(round(100 * cur / total))) def close(self): diff --git a/common/tests/test_file_helpers.py b/common/tests/test_file_helpers.py index 344455ba6c..d39e66de13 100644 --- a/common/tests/test_file_helpers.py +++ b/common/tests/test_file_helpers.py @@ -8,7 +8,7 @@ from common.file_helpers import atomic_write_in_dir class TestFileHelpers(unittest.TestCase): def run_atomic_write_func(self, atomic_write_func): - path = "/tmp/tmp{}".format(uuid4()) + path = f"/tmp/tmp{uuid4()}" with atomic_write_func(path) as f: f.write("test") diff --git a/common/timeout.py b/common/timeout.py index 4d424cdc0a..d0b0ce0630 100644 --- a/common/timeout.py +++ b/common/timeout.py @@ -12,7 +12,7 @@ class Timeout: """ def __init__(self, seconds, error_msg=None): if error_msg is None: - error_msg = 'Timed out after {} seconds'.format(seconds) + error_msg = f'Timed out after {seconds} seconds' self.seconds = seconds self.error_msg = error_msg diff --git a/docs/CARS.md b/docs/CARS.md index 9c762ee7bf..95b9657b4f 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -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 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Niro Hybrid 2021 | SCC + LKAS | Stock | 0mph | 0mph | | Kia | Niro PHEV 2019 | SCC + LKAS | Stock | 10mph | 32mph | | Kia | Optima 2017 | SCC + LKAS | Stock | 0mph | 32mph | @@ -147,10 +148,10 @@ | Škoda | Scala 2020 | Driver Assistance | Stock | 0mph | 0mph | | Škoda | Superb 2015-18 | Driver Assistance | Stock | 0mph | 0mph | | Subaru | Ascent 2019 | EyeSight | Stock | 0mph | 0mph | -| Subaru | Crosstrek 2018-19 | EyeSight | Stock | 0mph | 0mph | +| Subaru | Crosstrek 2018-20 | EyeSight | Stock | 0mph | 0mph | | Subaru | Forester 2019-21 | EyeSight | Stock | 0mph | 0mph | | Subaru | Impreza 2017-19 | EyeSight | Stock | 0mph | 0mph | -| Volkswagen| Arteon 20214 | Driver Assistance | Stock | 0mph | 0mph | +| Volkswagen| Arteon 2018, 20214 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| Atlas 2018-19 | Driver Assistance | Stock | 0mph | 0mph | | Volkswagen| California 20214 | Driver Assistance | Stock | 0mph | 32mph | | Volkswagen| e-Golf 2014, 2019-20 | Driver Assistance | Stock | 0mph | 0mph | diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md index 8dcdeee509..7a074f12de 100644 --- a/docs/CONTRIBUTING.md +++ b/docs/CONTRIBUTING.md @@ -1,46 +1,48 @@ # How to contribute -Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. +Our software is open source so you can solve your own problems without needing help from others. And if you solve a problem and are so kind, you can upstream it for the rest of the world to use. Check out our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available on our [blog](https://blog.comma.ai/). +Most open source development activity is coordinated through our [GitHub Discussions](https://github.com/commaai/openpilot/discussions) and [Discord](https://discord.comma.ai). A lot of documentation is available at https://docs.comma.ai and on our [blog](https://blog.comma.ai/). -## Getting Started +### Getting Started * Setup your [development environment](../tools/) * Join our [Discord](https://discord.comma.ai) * Make sure you have a [GitHub account](https://github.com/signup/free) * Fork [our repositories](https://github.com/commaai) on GitHub -## Testing +### First contribution +Try out some of these first pull requests ideas to dive into the codebase: -### Automated Testing +* Increase our [mypy](http://mypy-lang.org/) coverage +* Write some documentation +* Tackle an open [good first issue](https://github.com/commaai/openpilot/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) -All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. +## Pull Requests -### Code Style and Linting +Pull requests should be against the master branch. Welcomed contributions include bug reports, car ports, and any [open issue](https://github.com/commaai/openpilot/issues). If you're unsure about a contribution, feel free to open a discussion, issue, or draft PR to discuss the problem you're trying to solve. -Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. +A good pull request has all of the following: +* a clearly stated purpose +* every line changed directly contributes to the stated purpose +* verification, i.e. how did you test your PR? +* justification + * if you've optimized something, post benchmarks to prove it's better + * if you've improved your car's tuning, post before and after plots +* passes the CI tests -## Car Ports +### Car Ports We've released a [Model Port guide](https://blog.comma.ai/openpilot-port-guide-for-toyota-models/) for porting to Toyota/Lexus models. If you port openpilot to a substantially new car brand, see this more generic [Brand Port guide](https://blog.comma.ai/how-to-write-a-car-port-for-openpilot/). -## Pull Requests +## Testing -Pull requests should be against the master branch. Before running master on in-car hardware, you'll need to clone the submodules too. That can be done by recursively cloning the repository: -``` -git clone https://github.com/commaai/openpilot.git --recursive -``` -Or alternatively, when on the master branch: -``` -git submodule update --init -``` -The reasons for having submodules on a dedicated repository and our new development philosophy can be found in our [post about externalization](https://blog.comma.ai/a-2020-theme-externalization/). -Modules that are in seperate repositories include: -* cereal -* laika -* opendbc -* panda -* rednose +### Automated Testing + +All PRs and commits are automatically checked by GitHub Actions. Check out `.github/workflows/` for what GitHub Actions runs. Any new tests should be added to GitHub Actions. + +### Code Style and Linting + +Code is automatically checked for style by GitHub Actions as part of the automated tests. You can also run these tests yourself by running `pre-commit run --all`. diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000..976929954f Binary files /dev/null and b/docs/_static/favicon.ico differ diff --git a/docs/_static/logo.png b/docs/_static/logo.png new file mode 100644 index 0000000000..2699565085 Binary files /dev/null and b/docs/_static/logo.png differ diff --git a/docs/_static/robots.txt b/docs/_static/robots.txt new file mode 100644 index 0000000000..3bcd24fb5d --- /dev/null +++ b/docs/_static/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Sitemap: https://docs.comma.ai/sitemap.xml \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 4f692c9277..21b29ffb62 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -16,16 +16,22 @@ 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('..')) -OPENPILOT_ROOT = os.path.abspath(r'../../') # from openpilot/build/docs + +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 --------------------------------------------------- @@ -39,8 +45,34 @@ extensions = [ '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'] @@ -54,43 +86,38 @@ exclude_patterns = [] # Breathe Configuration # breathe_default_project = "c_docs" -breathe_build_directory = f"{OPENPILOT_ROOT}/build/docs/html/xml" +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", - } + "h": "cc", +} breathe_implementation_filename_extensions = ['.c', '.cc', '.cpp'] breathe_doxygen_config_options = {} -breathe_projects_source = { - # "loggerd" : ("../../../selfdrive/loggerd", ["logger.h"]) - } +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(OPENPILOT_ROOT): - found = False - breath_src = {} - breathe_srcs_list = [] - - for file in files: - ccFile = os.path.join(root, file)[:-2] +".cc" +for root, dirs, files in os.walk(BASEDIR): + found = False + breath_src = {} + breathe_srcs_list = [] - if file.endswith(".h") and exists(ccFile): - f = os.path.join(root, file) - parent_dir = os.path.basename(os.path.dirname(f)) - parent_dir_abs = os.path.dirname(f) - print(f"\tFOUND: {f} in {parent_dir} ({parent_dir_abs})") + for file in files: + ccFile = os.path.join(root, file)[:-2] + ".cc" - breathe_srcs_list.append(file) - # breathe_srcs_list.append(ccFile) - found = True + if file.endswith(".h") and exists(ccFile): + f = os.path.join(root, file) + parent_dir = os.path.basename(os.path.dirname(f)) + parent_dir_abs = os.path.dirname(f) + print(f"\tFOUND: {f} in {parent_dir} ({parent_dir_abs})") - # print(f"\tbreathe_srcs_list: {breathe_srcs_list}") + breathe_srcs_list.append(file) + found = True - if found: - breath_src[parent_dir] = (parent_dir_abs, breathe_srcs_list) - breathe_projects_source.update(breath_src) + if found: + breath_src[parent_dir] = (parent_dir_abs, breathe_srcs_list) + breathe_projects_source.update(breath_src) print(f"breathe_projects_source: {breathe_projects_source.keys()}") # input("Press Enter to continue...") @@ -101,8 +128,18 @@ print(f"breathe_projects_source: {breathe_projects_source.keys()}") # 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/opendbc b/opendbc index 61534fd131..2bab99fd86 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 61534fd13162d714432c5685f2c59fafe7a96823 +Subproject commit 2bab99fd861786312bde676823e21d80eeeb01fa diff --git a/panda b/panda index 652367d2e8..79f5e6fe86 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 652367d2e82f21f996c2217857d20ea05567ad62 +Subproject commit 79f5e6fe860d4f84876f08ddeea00eb6361cb05d diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index d196926611..996ac97f8e 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -34,7 +34,7 @@ from selfdrive.version import get_version, get_origin, get_short_branch, get_com 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) @@ -56,6 +56,28 @@ UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', ' cur_upload_items = {} +class UploadQueueCache(): + params = Params() + + @staticmethod + def initialize(upload_queue): + try: + upload_queue_json = UploadQueueCache.params.get("AthenadUploadQueue") + if upload_queue_json is not None: + for item in json.loads(upload_queue_json): + upload_queue.put(UploadItem(**item)) + except Exception: + cloudlog.exception("athena.UploadQueueCache.initialize.exception") + + @staticmethod + def cache(upload_queue): + try: + items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads] + UploadQueueCache.params.put("AthenadUploadQueue", json.dumps(items)) + except Exception: + cloudlog.exception("athena.UploadQueueCache.cache.exception") + + def handle_long_poll(ws): end_event = threading.Event() @@ -111,6 +133,7 @@ def upload_handler(end_event): try: cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True) + if cur_upload_items[tid].id in cancelled_uploads: cancelled_uploads.remove(cur_upload_items[tid].id) continue @@ -120,6 +143,7 @@ def upload_handler(end_event): cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1) _do_upload(cur_upload_items[tid], cb) + UploadQueueCache.cache(upload_queue) except (requests.exceptions.Timeout, requests.exceptions.ConnectionError, requests.exceptions.SSLError) as e: cloudlog.warning(f"athena.upload_handler.retry {e} {cur_upload_items[tid]}") @@ -131,6 +155,8 @@ def upload_handler(end_event): current=False ) upload_queue.put_nowait(item) + UploadQueueCache.cache(upload_queue) + cur_upload_items[tid] = None for _ in range(RETRY_DELAY): @@ -248,6 +274,7 @@ def uploadFileToUrl(fn, url, headers): item = item._replace(id=upload_id) upload_queue.put_nowait(item) + UploadQueueCache.cache(upload_queue) return {"enqueued": 1, "item": item._asdict()} @@ -260,7 +287,7 @@ def listUploadQueue(): @dispatcher.add_method def cancelUpload(upload_id): - upload_ids = set(item.id for item in list(upload_queue.queue)) + upload_ids = {item.id for item in list(upload_queue.queue)} if upload_id not in upload_ids: return 404 @@ -280,8 +307,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port): cloudlog.debug("athena.startLocalProxy.starting") - params = Params() - dongle_id = params.get("DongleId").decode('utf8') + dongle_id = Params().get("DongleId").decode('utf8') identity_token = Api(dongle_id).get_token() ws = create_connection(remote_ws_uri, cookie="jwt=" + identity_token, @@ -312,7 +338,7 @@ def getPublicKey(): if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): return None - with open(PERSIST + '/comma/id_rsa.pub', 'r') as f: + with open(PERSIST + '/comma/id_rsa.pub') as f: return f.read() @@ -393,7 +419,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": { @@ -525,6 +551,7 @@ def backoff(retries): def main(): params = Params() dongle_id = params.get("DongleId", encoding='utf-8') + UploadQueueCache.initialize(upload_queue) ws_uri = ATHENA_HOST + "/ws/v2/" + dongle_id api = Api(dongle_id) diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index fa95eacd8e..58ad58310f 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -6,7 +6,7 @@ from multiprocessing import Process from common.params import Params from selfdrive.manager.process import launcher from selfdrive.swaglog import cloudlog -from selfdrive.version import get_version, get_dirty +from selfdrive.version import get_version, is_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -14,12 +14,12 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') - cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty()) + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty()) try: while 1: cloudlog.info("starting athena daemon") - proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad',)) + proc = Process(name='athenad', target=launcher, args=('selfdrive.athena.athenad', 'athenad')) proc.start() proc.join() cloudlog.event("athenad exited", exitcode=proc.exitcode) diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 831b668297..26655b4a37 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -50,18 +50,28 @@ class MockApi(): class MockParams(): - def __init__(self): - self.params = { - "DongleId": b"0000000000000000", - "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private" # noqa: E501 - } + default_params = { + "DongleId": b"0000000000000000", + "GithubSshKeys": b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC307aE+nuHzTAgaJhzSf5v7ZZQW9gaperjhCmyPyl4PzY7T1mDGenTlVTN7yoVFZ9UfO9oMQqo0n1OwDIiqbIFxqnhrHU0cYfj88rI85m5BEKlNu5RdaVTj1tcbaPpQc5kZEolaI1nDDjzV0lwS7jo5VYDHseiJHlik3HH1SgtdtsuamGR2T80q1SyW+5rHoMOJG73IH2553NnWuikKiuikGHUYBd00K1ilVAK2xSiMWJp55tQfZ0ecr9QjEsJ+J/efL4HqGNXhffxvypCXvbUYAFSddOwXUPo5BTKevpxMtH+2YrkpSjocWA04VnTYFiPG6U4ItKmbLOTFZtPzoez private", # noqa: E501 + "AthenadUploadQueue": '[]' + } + params = default_params.copy() + + @staticmethod + def restore_defaults(): + MockParams.params = MockParams.default_params.copy() def get(self, k, encoding=None): - ret = self.params.get(k) + ret = MockParams.params.get(k) if ret is not None and encoding is not None: ret = ret.decode(encoding) return ret + def put(self, k, v): + if k not in MockParams.params: + raise KeyError(f"key: {k} not in MockParams") + MockParams.params[k] = v + class MockWebsocket(): def __init__(self, recv_queue, send_queue): diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 5bf34d1bc7..bf6d3940f9 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -21,19 +21,22 @@ from selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher from selfdrive.athena.tests.helpers import MockWebsocket, MockParams, MockApi, EchoSocket, with_http_server from cereal import messaging + class TestAthenadMethods(unittest.TestCase): @classmethod def setUpClass(cls): cls.SOCKET_PORT = 45454 + athenad.Params = MockParams athenad.ROOT = tempfile.mkdtemp() athenad.SWAGLOG_DIR = swaglog.SWAGLOG_DIR = tempfile.mkdtemp() - athenad.Params = MockParams athenad.Api = MockApi - athenad.LOCAL_PORT_WHITELIST = set([cls.SOCKET_PORT]) + athenad.LOCAL_PORT_WHITELIST = {cls.SOCKET_PORT} def setUp(self): + MockParams.restore_defaults() athenad.upload_queue = queue.Queue() athenad.cur_upload_items.clear() + athenad.cancelled_uploads.clear() for i in os.listdir(athenad.ROOT): p = os.path.join(athenad.ROOT, i) @@ -249,6 +252,26 @@ class TestAthenadMethods(unittest.TestCase): items = dispatcher["listUploadQueue"]() self.assertEqual(len(items), 0) + def test_upload_queue_persistence(self): + item1 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id1') + item2 = athenad.UploadItem(path="_", url="_", headers={}, created_at=int(time.time()), id='id2') + + athenad.upload_queue.put_nowait(item1) + athenad.upload_queue.put_nowait(item2) + + # Ensure cancelled items are not persisted + athenad.cancelled_uploads.add(item2.id) + + # serialize item + athenad.UploadQueueCache.cache(athenad.upload_queue) + + # deserialize item + athenad.upload_queue.queue.clear() + athenad.UploadQueueCache.initialize(athenad.upload_queue) + + self.assertEqual(athenad.upload_queue.qsize(), 1) + self.assertDictEqual(athenad.upload_queue.queue[-1]._asdict(), item1._asdict()) + @mock.patch('selfdrive.athena.athenad.create_connection') def test_startLocalProxy(self, mock_create_connection): end_event = threading.Event() diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 3f66efc9fe..0ca943de2e 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -205,17 +205,17 @@ Panda *usb_connect(std::string serial="", uint32_t index=0) { } void can_send_thread(std::vector pandas, bool fake_send) { - set_thread_name("boardd_can_send"); + util::set_thread_name("boardd_can_send"); AlignedBuffer aligned_buf; - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "sendcan"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "sendcan")); assert(subscriber != NULL); subscriber->setTimeout(100); // run as fast as messages come in while (!do_exit && check_all_connected(pandas)) { - Message * msg = subscriber->receive(); + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -223,27 +223,20 @@ void can_send_thread(std::vector pandas, bool fake_send) { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); //Dont send if older than 1 second - if (nanos_since_boot() - event.getLogMonoTime() < 1e9) { - if (!fake_send) { - for (const auto& panda : pandas) { - panda->can_send(event.getSendcan()); - } + if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { + for (const auto& panda : pandas) { + panda->can_send(event.getSendcan()); } } - - delete msg; } - - delete subscriber; - delete context; } void can_recv_thread(std::vector pandas) { - set_thread_name("boardd_can_recv"); + util::set_thread_name("boardd_can_recv"); // can = 8006 PubMaster pm({"can"}); @@ -415,9 +408,11 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { } void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofing_started) { - set_thread_name("boardd_panda_state"); + util::set_thread_name("boardd_panda_state"); Params params; + SubMaster sm({"controlsState"}); + Panda *peripheral_panda = pandas[0]; bool ignition_last = false; std::future safety_future; @@ -452,8 +447,11 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin ignition_last = ignition; + sm.update(0); + const bool engaged = sm.allAliveAndValid({"controlsState"}) && sm["controlsState"].getControlsState().getEnabled(); + for (const auto &panda : pandas) { - panda->send_heartbeat(); + panda->send_heartbeat(engaged); } util::sleep_for(500); } @@ -461,7 +459,7 @@ void panda_state_thread(PubMaster *pm, std::vector pandas, bool spoofin void peripheral_control_thread(Panda *panda) { - set_thread_name("boardd_peripheral_control"); + util::set_thread_name("boardd_peripheral_control"); SubMaster sm({"deviceState", "driverCameraState"}); @@ -547,12 +545,12 @@ static void pigeon_publish_raw(PubMaster &pm, const std::string &dat) { } void pigeon_thread(Panda *panda) { - set_thread_name("boardd_pigeon"); + util::set_thread_name("boardd_pigeon"); PubMaster pm({"ubloxRaw"}); bool ignition_last = false; - Pigeon *pigeon = Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda); + std::unique_ptr pigeon(Hardware::TICI() ? Pigeon::connect("/dev/ttyHS0") : Pigeon::connect(panda)); std::unordered_map last_recv_time; std::unordered_map cls_max_dt = { @@ -620,8 +618,6 @@ void pigeon_thread(Panda *panda) { // 10ms - 100 Hz util::sleep_for(10); } - - delete pigeon; } int main(int argc, char *argv[]) { @@ -629,9 +625,9 @@ int main(int argc, char *argv[]) { if (!Hardware::PC()) { int err; - err = set_realtime_priority(54); + err = util::set_realtime_priority(54); assert(err == 0); - err = set_core_affinity({Hardware::TICI() ? 4 : 3}); + err = util::set_core_affinity({Hardware::TICI() ? 4 : 3}); assert(err == 0); } diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 751f735ca5..6850ad41b2 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -340,8 +340,8 @@ void Panda::set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode) usb_write(0xe6, (uint16_t)power_mode, 0); } -void Panda::send_heartbeat() { - usb_write(0xf3, 1, 0); +void Panda::send_heartbeat(bool engaged) { + usb_write(0xf3, engaged, 0); } void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index fe69189489..1a18a7f15a 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -108,7 +108,7 @@ class Panda { std::optional get_serial(); void set_power_saving(bool power_saving); void set_usb_power_mode(cereal::PeripheralState::UsbPowerMode power_mode); - void send_heartbeat(); + void send_heartbeat(bool engaged); void set_can_speed_kbps(uint16_t bus, uint16_t speed); void set_data_speed_kbps(uint16_t bus, uint16_t speed); void can_send(capnp::List::Reader can_data_list); diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index 24c6f2e300..631b4c987f 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -69,7 +69,7 @@ class TestBoardd(unittest.TestCase): for __ in range(random.randrange(100)): bus = random.choice([b for b in range(3*num_pandas) if b % 4 != 3]) addr = random.randrange(1, 1<<29) - dat = bytes([random.getrandbits(8) for _ in range(random.randrange(1, 9))]) + dat = bytes(random.getrandbits(8) for _ in range(random.randrange(1, 9))) sent_msgs[bus].add((addr, dat)) to_send.append(make_can_msg(addr, dat, bus)) sendcan.send(can_list_to_can_capnp(to_send, msgtype='sendcan')) diff --git a/selfdrive/camerad/cameras/camera_common.cc b/selfdrive/camerad/cameras/camera_common.cc index b901dc39d3..424ad78ab3 100644 --- a/selfdrive/camerad/cameras/camera_common.cc +++ b/selfdrive/camerad/cameras/camera_common.cc @@ -200,7 +200,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setMeasuredGreyFraction(frame_data.measured_grey_fraction); framed.setTargetGreyFraction(frame_data.target_grey_fraction); framed.setLensPos(frame_data.lens_pos); - framed.setLensSag(frame_data.lens_sag); framed.setLensErr(frame_data.lens_err); framed.setLensTruePos(frame_data.lens_true_pos); } @@ -351,7 +350,7 @@ void *processing_thread(MultiCameraState *cameras, CameraState *cs, process_thre } else { thread_name = "WideRoadCamera"; } - set_thread_name(thread_name); + util::set_thread_name(thread_name); uint32_t cnt = 0; while (!do_exit) { @@ -408,11 +407,11 @@ static void driver_cam_auto_exposure(CameraState *c, SubMaster &sm) { camera_autoexposure(c, set_exposure_target(b, rect.x1, rect.x2, rect.x_skip, rect.y1, rect.y2, rect.y_skip)); } -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt) { +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { int j = Hardware::TICI() ? 1 : 3; if (cnt % j == 0) { - sm->update(0); - driver_cam_auto_exposure(c, *sm); + s->sm->update(0); + driver_cam_auto_exposure(c, *(s->sm)); } MessageBuilder msg; auto framed = msg.initEvent().initDriverCameraState(); @@ -421,5 +420,5 @@ void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, if (env_send_driver) { framed.setImage(get_frame_image(&c->buf)); } - pm->send("driverCameraState", msg); + s->pm->send("driverCameraState", msg); } diff --git a/selfdrive/camerad/cameras/camera_common.h b/selfdrive/camerad/cameras/camera_common.h index 20c00c5b5b..75bd79bfdf 100644 --- a/selfdrive/camerad/cameras/camera_common.h +++ b/selfdrive/camerad/cameras/camera_common.h @@ -68,7 +68,6 @@ typedef struct FrameMetadata { // Focus unsigned int lens_pos; - float lens_sag; float lens_err; float lens_true_pos; } FrameMetadata; @@ -123,7 +122,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr kj::Array get_frame_image(const CameraBuf *b); float set_exposure_target(const CameraBuf *b, int x_start, int x_end, int x_skip, int y_start, int y_end, int y_skip); std::thread start_process_thread(MultiCameraState *cameras, CameraState *cs, process_thread_cb callback); -void common_process_driver_camera(SubMaster *sm, PubMaster *pm, CameraState *c, int cnt); +void common_process_driver_camera(MultiCameraState *s, CameraState *c, int cnt); void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_id, cl_context ctx); void cameras_open(MultiCameraState *s); diff --git a/selfdrive/camerad/cameras/camera_qcom.cc b/selfdrive/camerad/cameras/camera_qcom.cc index 11383de668..ecb1c8ffe4 100644 --- a/selfdrive/camerad/cameras/camera_qcom.cc +++ b/selfdrive/camerad/cameras/camera_qcom.cc @@ -227,7 +227,7 @@ void cameras_init(VisionIpcServer *v, MultiCameraState *s, cl_device_id device_i s->stats_bufs[i].allocate(0xb80); } std::fill_n(s->lapres, std::size(s->lapres), 16160); - s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, 3); + s->lap_conv = new LapConv(device_id, ctx, s->road_cam.buf.rgb_width, s->road_cam.buf.rgb_height, s->road_cam.buf.rgb_stride, 3); } static void set_exposure(CameraState *s, float exposure_frac, float gain_frac) { @@ -852,21 +852,7 @@ static void parse_autofocus(CameraState *s, uint8_t *d) { s->focus_err = max_focus*1.0; } -static std::optional get_accel_z(SubMaster *sm) { - sm->update(0); - if(sm->updated("sensorEvents")) { - for (auto event : (*sm)["sensorEvents"].getSensorEvents()) { - if (event.which() == cereal::SensorEventData::ACCELERATION) { - if (auto v = event.getAcceleration().getV(); v.size() >= 3) - return -v[2]; - break; - } - } - } - return std::nullopt; -} - -static void do_autofocus(CameraState *s, SubMaster *sm) { +static void do_autofocus(CameraState *s) { float lens_true_pos = s->lens_true_pos.load(); if (!isnan(s->focus_err)) { // learn lens_true_pos @@ -874,23 +860,10 @@ static void do_autofocus(CameraState *s, SubMaster *sm) { lens_true_pos -= s->focus_err*focus_kp; } - if (auto accel_z = get_accel_z(sm)) { - s->last_sag_acc_z = *accel_z; - } - const float sag = (s->last_sag_acc_z / 9.8) * 128; // stay off the walls lens_true_pos = std::clamp(lens_true_pos, float(LP3_AF_DAC_DOWN), float(LP3_AF_DAC_UP)); - int target = std::clamp(lens_true_pos - sag, float(LP3_AF_DAC_DOWN), float(LP3_AF_DAC_UP)); s->lens_true_pos.store(lens_true_pos); - - /*char debug[4096]; - char *pdebug = debug; - pdebug += sprintf(pdebug, "focus "); - for (int i = 0; i < NUM_FOCUS; i++) pdebug += sprintf(pdebug, "%2x(%4d) ", s->confidence[i], s->focus[i]); - pdebug += sprintf(pdebug, " err: %7.2f offset: %6.2f sag: %6.2f lens_true_pos: %6.2f cur_lens_pos: %4d->%4d", err * focus_kp, offset, sag, s->lens_true_pos, s->cur_lens_pos, target); - LOGD(debug);*/ - - actuator_move(s, target); + actuator_move(s, lens_true_pos); } void camera_autoexposure(CameraState *s, float grey_frac) { @@ -1045,13 +1018,12 @@ static void ops_thread(MultiCameraState *s) { CameraExpInfo road_cam_op; CameraExpInfo driver_cam_op; - set_thread_name("camera_settings"); - SubMaster sm({"sensorEvents"}); + util::set_thread_name("camera_settings"); while(!do_exit) { road_cam_op = road_cam_exp.load(); if (road_cam_op.op_id != last_road_cam_op_id) { do_autoexposure(&s->road_cam, road_cam_op.grey_frac); - do_autofocus(&s->road_cam, &sm); + do_autofocus(&s->road_cam); last_road_cam_op_id = road_cam_op.op_id; } @@ -1086,10 +1058,6 @@ static void setup_self_recover(CameraState *c, const uint16_t *lapres, size_t la c->self_recover.store(self_recover); } -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1121,7 +1089,7 @@ void cameras_run(MultiCameraState *s) { std::vector threads; threads.push_back(std::thread(ops_thread, s)); threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); CameraState* cameras[2] = {&s->road_cam, &s->driver_cam}; @@ -1169,7 +1137,6 @@ void cameras_run(MultiCameraState *s) { .frame_length = (uint32_t)c->frame_length, .integ_lines = (uint32_t)c->cur_integ_lines, .lens_pos = c->cur_lens_pos, - .lens_sag = c->last_sag_acc_z, .lens_err = c->focus_err, .lens_true_pos = c->lens_true_pos, .gain = c->cur_gain_frac, diff --git a/selfdrive/camerad/cameras/camera_qcom.h b/selfdrive/camerad/cameras/camera_qcom.h index 207857e05c..87bbe4b7e7 100644 --- a/selfdrive/camerad/cameras/camera_qcom.h +++ b/selfdrive/camerad/cameras/camera_qcom.h @@ -76,7 +76,6 @@ typedef struct CameraState { // rear camera only,used for focusing unique_fd actuator_fd; std::atomic focus_err; - std::atomic last_sag_acc_z; std::atomic lens_true_pos; std::atomic self_recover; // af recovery counter, neg is patience, pos is active uint16_t cur_step_pos; diff --git a/selfdrive/camerad/cameras/camera_qcom2.cc b/selfdrive/camerad/cameras/camera_qcom2.cc index 3ea065ae73..030bbe47e1 100644 --- a/selfdrive/camerad/cameras/camera_qcom2.cc +++ b/selfdrive/camerad/cameras/camera_qcom2.cc @@ -987,11 +987,6 @@ void camera_autoexposure(CameraState *s, float grey_frac) { set_camera_exposure(s, grey_frac); } - -void process_driver_camera(MultiCameraState *s, CameraState *c, int cnt) { - common_process_driver_camera(s->sm, s->pm, c, cnt); -} - // called by processing_thread void process_road_camera(MultiCameraState *s, CameraState *c, int cnt) { const CameraBuf *b = &c->buf; @@ -1016,7 +1011,7 @@ void cameras_run(MultiCameraState *s) { LOG("-- Starting threads"); std::vector threads; threads.push_back(start_process_thread(s, &s->road_cam, process_road_camera)); - threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); + threads.push_back(start_process_thread(s, &s->driver_cam, common_process_driver_camera)); threads.push_back(start_process_thread(s, &s->wide_road_cam, process_road_camera)); // start devices diff --git a/selfdrive/camerad/cameras/camera_replay.cc b/selfdrive/camerad/cameras/camera_replay.cc index 8ff1b6aacc..b5b2e6ad29 100644 --- a/selfdrive/camerad/cameras/camera_replay.cc +++ b/selfdrive/camerad/cameras/camera_replay.cc @@ -67,12 +67,12 @@ void run_camera(CameraState *s) { } void road_camera_thread(CameraState *s) { - set_thread_name("replay_road_camera_thread"); + util::set_thread_name("replay_road_camera_thread"); run_camera(s); } // void driver_camera_thread(CameraState *s) { -// set_thread_name("replay_driver_camera_thread"); +// util::set_thread_name("replay_driver_camera_thread"); // run_camera(s); // } diff --git a/selfdrive/camerad/cameras/camera_webcam.cc b/selfdrive/camerad/cameras/camera_webcam.cc index 47da56042c..956f2dc88f 100644 --- a/selfdrive/camerad/cameras/camera_webcam.cc +++ b/selfdrive/camerad/cameras/camera_webcam.cc @@ -97,7 +97,7 @@ void run_camera(CameraState *s, cv::VideoCapture &video_cap, float *ts) { } static void road_camera_thread(CameraState *s) { - set_thread_name("webcam_road_camera_thread"); + util::set_thread_name("webcam_road_camera_thread"); cv::VideoCapture cap_road(ROAD_CAMERA_ID, cv::CAP_V4L2); // road cap_road.set(cv::CAP_PROP_FRAME_WIDTH, 853); @@ -186,7 +186,7 @@ void cameras_run(MultiCameraState *s) { threads.push_back(start_process_thread(s, &s->driver_cam, process_driver_camera)); std::thread t_rear = std::thread(road_camera_thread, &s->road_cam); - set_thread_name("webcam_thread"); + util::set_thread_name("webcam_thread"); driver_camera_thread(&s->driver_cam); t_rear.join(); diff --git a/selfdrive/camerad/imgproc/utils.cc b/selfdrive/camerad/imgproc/utils.cc index ad6452c6a6..a88b8f4bb1 100644 --- a/selfdrive/camerad/imgproc/utils.cc +++ b/selfdrive/camerad/imgproc/utils.cc @@ -55,8 +55,8 @@ static cl_program build_conv_program(cl_device_id device_id, cl_context context, return cl_program_from_file(context, device_id, "imgproc/conv.cl", args); } -LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int filter_size) - : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), +LapConv::LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size) + : width(rgb_width / NUM_SEGMENTS_X), height(rgb_height / NUM_SEGMENTS_Y), rgb_stride(rgb_stride), roi_buf(width * height * 3), result_buf(width * height) { prg = build_conv_program(device_id, ctx, width, height, filter_size); @@ -81,9 +81,9 @@ uint16_t LapConv::Update(cl_command_queue q, const uint8_t *rgb_buf, const int r const int x_offset = ROI_X_MIN + roi_id % (ROI_X_MAX - ROI_X_MIN + 1); const int y_offset = ROI_Y_MIN + roi_id / (ROI_X_MAX - ROI_X_MIN + 1); - const uint8_t *rgb_offset = rgb_buf + y_offset * height * FULL_STRIDE_X * 3 + x_offset * width * 3; + const uint8_t *rgb_offset = rgb_buf + y_offset * height * rgb_stride + x_offset * width * 3; for (int i = 0; i < height; ++i) { - memcpy(&roi_buf[i * width * 3], &rgb_offset[i * FULL_STRIDE_X * 3], width * 3); + memcpy(&roi_buf[i * width * 3], &rgb_offset[i * rgb_stride], width * 3); } constexpr int local_mem_size = (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (CONV_LOCAL_WORKSIZE + 2 * (3 / 2)) * (3 * sizeof(uint8_t)); diff --git a/selfdrive/camerad/imgproc/utils.h b/selfdrive/camerad/imgproc/utils.h index 72baa5d53a..b735975b3e 100644 --- a/selfdrive/camerad/imgproc/utils.h +++ b/selfdrive/camerad/imgproc/utils.h @@ -16,16 +16,11 @@ #define LM_THRESH 120 #define LM_PREC_THRESH 0.9 // 90 perc is blur - -// only apply to QCOM -#define FULL_STRIDE_X 1280 -#define FULL_STRIDE_Y 896 - #define CONV_LOCAL_WORKSIZE 16 class LapConv { public: - LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int filter_size); + LapConv(cl_device_id device_id, cl_context ctx, int rgb_width, int rgb_height, int rgb_stride, int filter_size); ~LapConv(); uint16_t Update(cl_command_queue q, const uint8_t *rgb_buf, const int roi_id); @@ -34,6 +29,7 @@ private: cl_program prg; cl_kernel krnl; const int width, height; + const int rgb_stride; std::vector roi_buf; std::vector result_buf; }; diff --git a/selfdrive/camerad/main.cc b/selfdrive/camerad/main.cc index 1db7130444..f06bd341df 100644 --- a/selfdrive/camerad/main.cc +++ b/selfdrive/camerad/main.cc @@ -46,9 +46,9 @@ void party(cl_device_id device_id, cl_context context) { int main(int argc, char *argv[]) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(53); + ret = util::set_realtime_priority(53); assert(ret == 0); - ret = set_core_affinity({Hardware::EON() ? 2 : 6}); + ret = util::set_core_affinity({Hardware::EON() ? 2 : 6}); assert(ret == 0 || Params().getBool("IsOffroad")); // failure ok while offroad due to offlining cores } diff --git a/selfdrive/camerad/test/test_camerad.py b/selfdrive/camerad/test/test_camerad.py index 11e070f328..ff37ad1f31 100755 --- a/selfdrive/camerad/test/test_camerad.py +++ b/selfdrive/camerad/test/test_camerad.py @@ -54,7 +54,7 @@ class TestCamerad(unittest.TestCase): self.assertTrue(abs(dfid - 1) <= SKIP_FRAME_TOLERANCE, "%s frame id diff is %d" % (camera, dfid)) dts = ct - last_ts[camera] - self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, "%s frame t(ms) diff is %f" % (camera, dts)) + self.assertTrue(abs(dts - (1000/CAMERAS[camera])) < LAG_FRAME_TOLERANCE, f"{camera} frame t(ms) diff is {dts:f}") last_frame_id[camera] = sm[camera].frameId last_ts[camera] = ct diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 786b00fa2b..6cce7e62d9 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,7 +1,7 @@ import os from common.params import Params from common.basedir import BASEDIR -from selfdrive.version import get_comma_remote, get_tested_branch +from selfdrive.version import is_comma_remote, is_tested_branch from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, VIN_UNKNOWN from selfdrive.car.fw_versions import get_fw_versions, match_fw_to_car @@ -14,7 +14,7 @@ EventName = car.CarEvent.EventName def get_startup_event(car_recognized, controller_available, fw_seen): - if get_comma_remote() and get_tested_branch(): + if is_comma_remote() and is_tested_branch(): event = EventName.startup else: event = EventName.startupMaster @@ -39,7 +39,7 @@ def get_one_can(logcan): def load_interfaces(brand_names): ret = {} for brand_name in brand_names: - path = ('selfdrive.car.%s' % brand_name) + path = f'selfdrive.car.{brand_name}' CarInterface = __import__(path + '.interface', fromlist=['CarInterface']).CarInterface if os.path.exists(BASEDIR + '/' + path.replace('.', '/') + '/carstate.py'): @@ -65,10 +65,10 @@ def _get_interface_names(): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: try: brand_name = car_folder.split('/')[-1] - model_names = __import__('selfdrive.car.%s.values' % brand_name, fromlist=['CAR']).CAR + model_names = __import__(f'selfdrive.car.{brand_name}.values', fromlist=['CAR']).CAR model_names = [getattr(model_names, c) for c in model_names.__dict__.keys() if not c.startswith("__")] brand_names[brand_name] = model_names - except (ImportError, IOError): + except (ImportError, OSError): pass return brand_names diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 46681b13e2..d491ccd4b9 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,3 +1,4 @@ +from cereal import car from selfdrive.car import apply_toyota_steer_torque_limits from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, \ create_wheel_buttons @@ -20,9 +21,8 @@ class CarController(): # this seems needed to avoid steering faults and to force the sync with the EPS counter frame = CS.lkas_counter if self.prev_frame == frame: - return [] + return car.CarControl.Actuators.new_message(), [] - # *** compute control surfaces *** # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, @@ -67,4 +67,7 @@ class CarController(): self.ccframe += 1 self.prev_frame = frame - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 1822d76a1a..a893b222f8 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -78,8 +78,6 @@ class CarInterface(CarInterfaceBase): def apply(self, c): if (self.CS.frame == -1): - return [] # if we haven't seen a frame 220, then do not update. + return car.CarControl.Actuators.new_message(), [] # if we haven't seen a frame 220, then do not update. - can_sends = self.CC.update(c.enabled, self.CS, c.actuators, c.cruiseControl.cancel, c.hudControl.visualAlert) - - return can_sends + return self.CC.update(c.enabled, self.CS, c.actuators, c.cruiseControl.cancel, c.hudControl.visualAlert) diff --git a/selfdrive/car/fingerprints.py b/selfdrive/car/fingerprints.py index b09e14a429..9b280f3b45 100644 --- a/selfdrive/car/fingerprints.py +++ b/selfdrive/car/fingerprints.py @@ -11,7 +11,7 @@ def get_attr_from_cars(attr, result=dict, combine_brands=True): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: try: car_name = car_folder.split('/')[-1] - values = __import__('selfdrive.car.%s.values' % car_name, fromlist=[attr]) + values = __import__(f'selfdrive.car.{car_name}.values', fromlist=[attr]) if hasattr(values, attr): attr_values = getattr(values, attr) else: @@ -28,7 +28,7 @@ def get_attr_from_cars(attr, result=dict, combine_brands=True): elif isinstance(attr_values, list): result += attr_values - except (ImportError, IOError): + except (ImportError, OSError): pass return result diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index 06c5f25797..6257cfd209 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -32,7 +32,7 @@ class CarController(): if (frame % 3) == 0: - curvature = self.vehicle_model.calc_curvature(actuators.steeringAngleDeg*math.pi/180., CS.out.vEgo) + curvature = self.vehicle_model.calc_curvature(math.radians(actuators.steeringAngleDeg), CS.out.vEgo, 0.0) # The use of the toggle below is handy for trying out the various LKAS modes if TOGGLE_DEBUG: @@ -83,4 +83,4 @@ class CarController(): self.main_on_last = CS.out.cruiseState.available self.steer_alert_last = steer_alert - return can_sends + return actuators, can_sends diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 8c0b1e1faa..7155b3ea82 100755 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -63,8 +63,8 @@ class CarInterface(CarInterfaceBase): # to be called @ 100hz def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.hudControl.visualAlert, c.cruiseControl.cancel) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index 5d982d6a8d..27d697331e 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -84,8 +84,21 @@ NISSAN_VERSION_RESPONSE_STANDARD = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIF NISSAN_RX_OFFSET = 0x20 +SUBARU_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) +SUBARU_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_DATA_IDENTIFICATION) + + # brand, request, response, response offset REQUESTS = [ + # Subaru + ( + "subaru", + [TESTER_PRESENT_REQUEST, SUBARU_VERSION_REQUEST], + [TESTER_PRESENT_RESPONSE, SUBARU_VERSION_RESPONSE], + DEFAULT_RX_OFFSET, + ), # Hyundai ( "hyundai", @@ -221,7 +234,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): if match_count >= 2: if log: cloudlog.error(f"Fingerprinted {candidate} using fuzzy match. {match_count} matching ECUs") - return set([candidate]) + return {candidate} else: return set() @@ -362,7 +375,7 @@ if __name__ == "__main__": print("Getting vin...") addr, vin = get_vin(logcan, sendcan, 1, retry=10, debug=args.debug) print(f"VIN: {vin}") - print("Getting VIN took %.3f s" % (time.time() - t)) + print(f"Getting VIN took {time.time() - t:.3f} s") print() t = time.time() @@ -379,4 +392,4 @@ if __name__ == "__main__": print() print("Possible matches:", candidates) - print("Getting fw took %.3f s" % (time.time() - t)) + print(f"Getting fw took {time.time() - t:.3f} s") diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 8f4d0f27ca..71587b17d9 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -14,6 +14,9 @@ class CarController(): def __init__(self, dbc_name, CP, VM): self.start_time = 0. self.apply_steer_last = 0 + self.apply_gas = 0 + self.apply_brake = 0 + self.lka_steering_cmd_counter_last = -1 self.lka_icon_status_last = (False, False) self.steer_rate_limited = False @@ -53,22 +56,22 @@ class CarController(): can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, lkas_enabled)) - if not enabled: - # Stock ECU sends max regen when not enabled. - apply_gas = P.MAX_ACC_REGEN - apply_brake = 0 - else: - apply_gas = int(round(interp(actuators.accel, P.GAS_LOOKUP_BP, P.GAS_LOOKUP_V))) - apply_brake = int(round(interp(actuators.accel, P.BRAKE_LOOKUP_BP, P.BRAKE_LOOKUP_V))) - # Gas/regen and brakes - all at 25Hz if (frame % 4) == 0: + if not enabled: + # Stock ECU sends max regen when not enabled. + self.apply_gas = P.MAX_ACC_REGEN + self.apply_brake = 0 + else: + self.apply_gas = int(round(interp(actuators.accel, P.GAS_LOOKUP_BP, P.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, P.BRAKE_LOOKUP_BP, P.BRAKE_LOOKUP_V))) + idx = (frame // 4) % 4 at_full_stop = enabled and CS.out.standstill near_stop = enabled and (CS.out.vEgo < P.NEAR_STOP_BRAKE_PHASE) - can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, apply_brake, idx, near_stop, at_full_stop)) - can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, apply_gas, idx, enabled, at_full_stop)) + can_sends.append(gmcan.create_friction_brake_command(self.packer_ch, CanBus.CHASSIS, self.apply_brake, idx, near_stop, at_full_stop)) + can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, enabled, at_full_stop)) # Send dashboard UI commands (ACC status), 25hz if (frame % 4) == 0: @@ -106,4 +109,9 @@ class CarController(): can_sends.append(gmcan.create_lka_icon_command(CanBus.SW_GMLAN, lka_active, lka_critical, steer_alert)) self.lka_icon_status_last = lka_icon_status - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / P.STEER_MAX + new_actuators.gas = self.apply_gas + new_actuators.brake = self.apply_brake + + return new_actuators, can_sends diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index e4d6448348..5180d2e511 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -36,7 +36,7 @@ class CarState(CarStateBase): if ret.brake < 10/0xd0: ret.brake = 0. - ret.gas = pt_cp.vl["AcceleratorPedal"]["AcceleratorPedal"] / 254. + ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254. ret.gasPressed = ret.gas > 1e-5 ret.steeringAngleDeg = pt_cp.vl["PSCMSteeringAngle"]["SteeringWheelAngle"] @@ -90,7 +90,7 @@ class CarState(CarStateBase): ("LeftSeatBelt", "BCMDoorBeltStatus", 0), ("RightSeatBelt", "BCMDoorBeltStatus", 0), ("TurnSignals", "BCMTurnSignals", 0), - ("AcceleratorPedal", "AcceleratorPedal", 0), + ("AcceleratorPedal2", "AcceleratorPedal2", 0), ("CruiseState", "AcceleratorPedal2", 0), ("ACCButtons", "ASCMSteeringButton", CruiseButtons.UNPRESS), ("SteeringWheelAngle", "PSCMSteeringAngle", 0), @@ -117,7 +117,6 @@ class CarState(CarStateBase): ("EPBStatus", 20), ("EBCMWheelSpdFront", 20), ("EBCMWheelSpdRear", 20), - ("AcceleratorPedal", 33), ("AcceleratorPedal2", 33), ("ASCMSteeringButton", 33), ("ECMEngineStatus", 100), diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index b06e5373aa..fb9f90a32f 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -212,7 +212,8 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - hud_v_cruise = c.hudControl.setSpeed + hud_control = c.hudControl + hud_v_cruise = hud_control.setSpeed if hud_v_cruise > 70: hud_v_cruise = 0 @@ -220,10 +221,10 @@ class CarInterface(CarInterfaceBase): # In GM, PCM faults out if ACC command overlaps user gas. enabled = c.enabled and not self.CS.out.gasPressed - can_sends = self.CC.update(enabled, self.CS, self.frame, - c.actuators, - hud_v_cruise, c.hudControl.lanesVisible, - c.hudControl.leadVisible, c.hudControl.visualAlert) + ret = self.CC.update(enabled, self.CS, self.frame, + c.actuators, + hud_v_cruise, hud_control.lanesVisible, + hud_control.leadVisible, hud_control.visualAlert) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 4cb1e0781f..d1ad1c1635 100755 --- a/selfdrive/car/gm/radar_interface.py +++ b/selfdrive/car/gm/radar_interface.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from __future__ import print_function import math from cereal import car from opendbc.can.parser import CANParser diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index e1488eda2a..9a424a1e68 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -105,6 +105,11 @@ class CarController(): self.last_pump_ts = 0. self.packer = CANPacker(dbc_name) + self.accel = 0 + self.speed = 0 + self.gas = 0 + self.brake = 0 + self.params = CarControllerParams(CP) def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, @@ -211,10 +216,9 @@ class CarController(): ts = frame * DT_CTRL if CS.CP.carFingerprint in HONDA_BOSCH: - accel = clip(accel, P.BOSCH_ACCEL_MIN, P.BOSCH_ACCEL_MAX) - bosch_gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP, P.BOSCH_GAS_LOOKUP_V) - can_sends.extend(hondacan.create_acc_commands(self.packer, enabled, active, accel, bosch_gas, idx, stopping, starting, CS.CP.carFingerprint)) - + self.accel = clip(accel, P.BOSCH_ACCEL_MIN, P.BOSCH_ACCEL_MAX) + self.gas = interp(accel, P.BOSCH_GAS_LOOKUP_BP, P.BOSCH_GAS_LOOKUP_V) + can_sends.extend(hondacan.create_acc_commands(self.packer, enabled, active, accel, self.gas, idx, stopping, starting, CS.CP.carFingerprint)) else: apply_brake = clip(self.brake_last - wind_brake, 0.0, 1.0) apply_brake = int(clip(apply_brake * P.NIDEC_BRAKE_MAX, 0, P.NIDEC_BRAKE_MAX - 1)) @@ -224,6 +228,7 @@ class CarController(): can_sends.append(hondacan.create_brake_command(self.packer, apply_brake, pump_on, pcm_override, pcm_cancel_cmd, fcw_display, idx, CS.CP.carFingerprint, CS.stock_brake)) self.apply_brake_last = apply_brake + self.brake = apply_brake / P.NIDEC_BRAKE_MAX if CS.CP.enableGasInterceptor: # way too aggressive at low speed without this @@ -233,17 +238,28 @@ class CarController(): # Sending non-zero gas when OP is not enabled will cause the PCM not to respond to throttle as expected # when you do enable. if active: - apply_gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.) + self.gas = clip(gas_mult * (gas - brake + wind_brake*3/4), 0., 1.) else: - apply_gas = 0.0 - can_sends.append(create_gas_interceptor_command(self.packer, apply_gas, idx)) - - hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car, - hud_lanes, fcw_display, acc_alert, steer_required) + self.gas = 0.0 + can_sends.append(create_gas_interceptor_command(self.packer, self.gas, idx)) # Send dashboard UI commands. if (frame % 10) == 0: idx = (frame//10) % 4 + hud = HUDData(int(pcm_accel), int(round(hud_v_cruise)), hud_car, + hud_lanes, fcw_display, acc_alert, steer_required) can_sends.extend(hondacan.create_ui_commands(self.packer, CS.CP, pcm_speed, hud, CS.is_metric, idx, CS.stock_hud)) - return can_sends + if (CS.CP.openpilotLongitudinalControl) and (CS.CP.carFingerprint not in HONDA_BOSCH): + self.speed = pcm_speed + + if not CS.CP.enableGasInterceptor: + self.gas = pcm_accel / 0xc6 + + new_actuators = actuators.copy() + new_actuators.speed = self.speed + new_actuators.accel = self.accel + new_actuators.gas = self.gas + new_actuators.brake = self.brake + + return new_actuators, can_sends diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 03074b875c..a1828622c4 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -227,7 +227,7 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 ret.steerRatio = 11.95 # as spec ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 3840], [0, 3840]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] tire_stiffness_factor = 0.677 elif candidate == CAR.ODYSSEY: @@ -301,7 +301,7 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.6], [0.18]] # TODO: can probably use some tuning else: - raise ValueError("unsupported car %s" % candidate) + raise ValueError(f"unsupported car {candidate}") # These cars use alternate user brake msg (0x1BE) if candidate in HONDA_BOSCH_ALT_BRAKE_SIGNAL: @@ -350,7 +350,6 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_body) ret.canValid = self.cp.can_valid and self.cp_cam.can_valid and (self.cp_body is None or self.cp_body.can_valid) - ret.yawRate = self.VM.yaw_rate(ret.steeringAngleDeg * CV.DEG_TO_RAD, ret.vEgo) buttonEvents = [] @@ -433,18 +432,19 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): - if c.hudControl.speedVisible: - hud_v_cruise = c.hudControl.setSpeed * CV.MS_TO_KPH + hud_control = c.hudControl + if hud_control.speedVisible: + hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH else: hud_v_cruise = 255 - can_sends = self.CC.update(c.enabled, c.active, self.CS, self.frame, - c.actuators, - c.cruiseControl.cancel, - hud_v_cruise, - c.hudControl.lanesVisible, - hud_show_car=c.hudControl.leadVisible, - hud_alert=c.hudControl.visualAlert) + ret = self.CC.update(c.enabled, c.active, self.CS, self.frame, + c.actuators, + c.cruiseControl.cancel, + hud_v_cruise, + hud_control.lanesVisible, + hud_show_car=hud_control.leadVisible, + hud_alert=hud_control.visualAlert) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 083a52bb86..cbe2369b14 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1385,9 +1385,9 @@ STEER_THRESHOLD = { CAR.CRV_EU: 400, } -HONDA_NIDEC_ALT_PCM_ACCEL = set([CAR.ODYSSEY]) -HONDA_NIDEC_ALT_SCM_MESSAGES = set([CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_EU, CAR.FIT, CAR.FREED, CAR.HRV, CAR.ODYSSEY_CHN, - CAR.PILOT, CAR.PILOT_2019, CAR.PASSPORT, CAR.RIDGELINE]) -HONDA_BOSCH = set([CAR.ACCORD, CAR.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 a8b70fcf90..61fb35664b 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -44,6 +44,7 @@ class CarController(): self.car_fingerprint = CP.carFingerprint self.steer_rate_limited = False self.last_resume_frame = 0 + self.accel = 0 def update(self, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, hud_speed, left_lane, right_lane, left_lane_depart, right_lane_depart): @@ -100,12 +101,13 @@ class CarController(): stopping = (actuators.longControlState == LongCtrlState.stopping) set_speed_in_units = hud_speed * (CV.MS_TO_MPH if CS.clu11["CF_Clu_SPEED_UNIT"] == 1 else CV.MS_TO_KPH) can_sends.extend(create_acc_commands(self.packer, enabled, accel, jerk, int(frame / 2), lead_visible, set_speed_in_units, stopping)) + self.accel = accel # 20 Hz LFA MFA message if frame % 5 == 0 and self.car_fingerprint in [CAR.SONATA, CAR.PALISADE, CAR.IONIQ, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_CEED, CAR.KIA_SELTOS, CAR.KONA_EV, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020]: + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.GENESIS_G70_2020, CAR.SANTA_FE_PHEV_2022]: can_sends.append(create_lfahda_mfc(self.packer, enabled)) # 5 Hz ACC options @@ -116,4 +118,8 @@ class CarController(): if frame % 50 == 0 and CS.CP.openpilotLongitudinalControl: can_sends.append(create_frt_radar_opt(self.packer)) - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / self.p.STEER_MAX + new_actuators.accel = self.accel + + return new_actuators, can_sends diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 50031231bc..c038ff4e9e 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -19,7 +19,7 @@ def create_lkas11(packer, frame, car_fingerprint, apply_steer, steer_req, if car_fingerprint in [CAR.SONATA, CAR.PALISADE, CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV_2021, CAR.SANTA_FE, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.GENESIS_G70_2020, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_EV, CAR.KONA_HEV, CAR.SANTA_FE_2022, - CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]: + CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022]: values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 57b313e534..9f87081fdb 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -45,7 +45,7 @@ class CarInterface(CarInterfaceBase): 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 @@ -347,8 +347,9 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, c.hudControl.setSpeed, c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, hud_control.setSpeed, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index df2e10dbe7..40c09a626e 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_EV, + CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.KIA_OPTIMA, CAR.KIA_SORENTO, CAR.KIA_STINGER]: self.STEER_MAX = 255 + else: + self.STEER_MAX = 384 self.STEER_DELTA_UP = 3 self.STEER_DELTA_DOWN = 7 self.STEER_DRIVER_ALLOWANCE = 50 @@ -41,6 +42,7 @@ class CAR: SANTA_FE = "HYUNDAI SANTA FE 2019" SANTA_FE_2022 = "HYUNDAI SANTA FE 2022" SANTA_FE_HEV_2022 = "HYUNDAI SANTA FE HYBRID 2022" + SANTA_FE_PHEV_2022 = "HYUNDAI SANTA FE PlUG-IN HYBRID 2022" SONATA = "HYUNDAI SONATA 2020" SONATA_LF = "HYUNDAI SONATA 2019" PALISADE = "HYUNDAI PALISADE 2020" @@ -498,6 +500,23 @@ FW_VERSIONS = { b'\xf1\x87391312MTC1', ], }, + CAR.SANTA_FE_PHEV_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00TM MDPS C 1.00 1.02 56310-CLAC0 4TSHC102', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMP MFC AT USA LHD 1.00 1.03 99211-S1500 210224', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x8795441-3D121\x00\xf1\x81E16\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2P16SA0o\x88^\xbe', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x87391312MTF0', + ], + }, CAR.KIA_STINGER: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', @@ -777,25 +796,22 @@ FW_VERSIONS = { b'\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.02 96400-Q4100 ', b'\xf1\x00DEev SCC F-CUP 1.00 1.03 96400-Q4100 ', - b'\xf1\x00OSev SCC F-CUP 1.00 1.01 99110-K4000 ', b'\xf1\x8799110Q4000\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4000 ', b'\xf1\x8799110Q4100\xf1\x00DEev SCC F-CUP 1.00 1.00 99110-Q4100 ', b'\xf1\x8799110Q4500\xf1\000DEev SCC F-CUP 1.00 1.00 99110-Q4500 ', - ], - (Ecu.esp, 0x7D1, None): [ - b'\xf1\x00OS IEB \r 212 \x11\x13 58520-K4000', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FNCUP 1.00 1.00 99110-Q4600 ', + b'\xf1\x8799110Q4600\xf1\x00DEev SCC FHCUP 1.00 1.00 99110-Q4600 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4000\x00 4DEEC105', b'\xf1\x00DE MDPS C 1.00 1.05 56310Q4100\x00 4DEEC105', - b'\xf1\x00OS MDPS C 1.00 1.04 56310K4050\x00 4OEDC104', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\000DEE MFC AT EUR LHD 1.00 1.00 99211-Q4100 200706', b'\xf1\x00DEE MFC AT EUR LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.00 99211-Q4000 191211', b'\xf1\x00DEE MFC AT USA LHD 1.00 1.03 95740-Q4000 180821', - b'\xf1\x00OSE LKAS AT EUR LHD 1.00 1.00 95740-K4100 W40', + b'\xf1\x00DEE MFC AT USA LHD 1.00 1.01 99211-Q4500 210428', ], }, CAR.KIA_NIRO_HEV: { @@ -981,25 +997,25 @@ FW_VERSIONS = { } CHECKSUM = { - "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022], + "crc8": [CAR.SANTA_FE, CAR.SONATA, CAR.PALISADE, CAR.KIA_SELTOS, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.SONATA_HYBRID, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022], "6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS], } FEATURES = { # which message has the gear - "use_cluster_gears": set([CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA]), - "use_tcu_gears": set([CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER]), - "use_elect_gears": set([CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021,CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]), + "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, + "use_tcu_gears": {CAR.KIA_OPTIMA, CAR.SONATA_LF, CAR.VELOSTER}, + "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_OPTIMA_H, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.IONIQ, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021,CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022}, # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "use_fca": set([CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]), + "use_fca": {CAR.SONATA, CAR.SONATA_HYBRID, CAR.ELANTRA, CAR.ELANTRA_2021, CAR.ELANTRA_HEV_2021, CAR.ELANTRA_GT_I30, CAR.KIA_STINGER, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.KONA_EV, CAR.KIA_FORTE, CAR.KIA_NIRO_EV, CAR.PALISADE, CAR.GENESIS_G70, CAR.GENESIS_G70_2020, CAR.KONA, CAR.SANTA_FE, CAR.KIA_SELTOS, CAR.KONA_HEV, CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022}, } -HYBRID_CAR = set([CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022]) # these cars use a different gas signal -EV_CAR = set([CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV]) +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_HEV, CAR.KIA_NIRO_HEV_2021, CAR.SONATA_HYBRID, CAR.KONA_HEV, CAR.IONIQ, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022} # these cars use a different gas signal +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV} # these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = set([CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_NIRO_EV, CAR.KIA_OPTIMA, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022]) +LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_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 @@ DBC = { CAR.SANTA_FE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SANTA_FE_2022: dbc_dict('hyundai_kia_generic', None), CAR.SANTA_FE_HEV_2022: dbc_dict('hyundai_kia_generic', None), + CAR.SANTA_FE_PHEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.SONATA: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), CAR.SONATA_LF: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.PALISADE: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar'), diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index bef3456a76..80626ebb2a 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,6 +1,7 @@ import os import time -from typing import Dict +from abc import abstractmethod, ABC +from typing import Dict, Tuple, List from cereal import car from common.kalman.simple_kalman import KF1D @@ -22,7 +23,7 @@ ACCEL_MIN = -3.5 # generic car and radar interfaces -class CarInterfaceBase(): +class CarInterfaceBase(ABC): def __init__(self, CP, CarController, CarState): self.CP = CP self.VM = VehicleModel(CP) @@ -48,8 +49,9 @@ class CarInterfaceBase(): return ACCEL_MIN, ACCEL_MAX @staticmethod + @abstractmethod def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None): - raise NotImplementedError + pass @staticmethod def init(CP, logcan, sendcan): @@ -82,13 +84,12 @@ class CarInterfaceBase(): ret.minEnableSpeed = -1. # enable is done by stock ACC, so ignore this ret.steerRatioRear = 0. # no rear steering, at least on the listed cars aboveA ret.openpilotLongitudinalControl = False - ret.minSpeedCan = 0.3 ret.startAccel = -0.8 ret.stopAccel = -2.0 ret.startingAccelRate = 3.2 # brake_travel/s while releasing on restart ret.stoppingDecelRate = 0.8 # brake_travel/s while trying to stop ret.vEgoStopping = 0.5 - ret.vEgoStarting = 0.5 + ret.vEgoStarting = 0.5 # needs to be >= vEgoStopping to avoid state transition oscillation ret.stoppingControl = True ret.longitudinalTuning.deadzoneBP = [0.] ret.longitudinalTuning.deadzoneV = [0.] @@ -100,13 +101,13 @@ class CarInterfaceBase(): ret.longitudinalActuatorDelayUpperBound = 0.15 return ret - # returns a car.CarState, pass in car.CarControl - def update(self, c, can_strings): - raise NotImplementedError + @abstractmethod + def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState: + pass - # return sendcan, pass in a car.CarControl - def apply(self, c): - raise NotImplementedError + @abstractmethod + def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: + pass def create_common_events(self, cs_out, extra_gears=None, gas_resume_speed=-1, pcm_enable=True): events = Events() @@ -169,7 +170,7 @@ class CarInterfaceBase(): return events -class RadarInterfaceBase(): +class RadarInterfaceBase(ABC): def __init__(self, CP): self.pts = {} self.delay = 0 @@ -183,7 +184,7 @@ class RadarInterfaceBase(): return ret -class CarStateBase: +class CarStateBase(ABC): def __init__(self, CP): self.CP = CP self.car_fingerprint = CP.carFingerprint diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 06c5eb0948..c65ff72ed5 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -58,4 +58,8 @@ class CarController(): # send steering command can_sends.append(mazdacan.create_steering_control(self.packer, CS.CP.carFingerprint, frame, apply_steer, CS.cam_lkas)) - return can_sends + + new_actuators = c.actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 8364bf000c..b4ae938228 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -95,6 +95,6 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c, self.CS, self.frame) + ret = self.CC.update(c, self.CS, self.frame) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 58e1dd7033..f18c30176c 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -50,9 +50,11 @@ FW_VERSIONS = { b'PYFC-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFD-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNF-188K2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2F-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2G-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2H-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PX2H-188K2-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX2K-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX38-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX42-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -65,6 +67,7 @@ FW_VERSIONS = { b'K131-67XK2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K131-67XK2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'K131-67XK2-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.esp, 0x760, None): [ b'K123-437K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -78,7 +81,9 @@ FW_VERSIONS = { b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-V\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-M\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-R\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -87,10 +92,12 @@ FW_VERSIONS = { b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB1-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB1-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB1-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYB2-21PS1-G\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYNC-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'SH9T-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -145,10 +152,12 @@ FW_VERSIONS = { b'BHN1-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'K070-3210X-C-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KR11-3210X-K-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], (Ecu.engine, 0x7e0, None): [ b'P5JD-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PY2P-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYJW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKC-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-188K2-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -163,11 +172,13 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'B61L-67XK2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'B61L-67XK2-Q\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'B61L-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PY2S-21PS1-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'P52G-21PS1-F\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PYKA-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYKE-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -236,7 +247,7 @@ DBC = { } # Gen 1 hardware: same CAN messages and same camera -GEN1 = set([CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6]) +GEN1 = {CAR.CX5, CAR.CX9, CAR.CX9_2021, CAR.MAZDA3, CAR.MAZDA6} # Cars with a steering lockout -STEER_LOCKOUT_CAR = set([CAR.CX5, CAR.CX9, CAR.MAZDA3, CAR.MAZDA6]) +STEER_LOCKOUT_CAR = {CAR.CX5, CAR.CX9, CAR.MAZDA3, CAR.MAZDA6} diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index 999e735c73..bc1f6dcf6b 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -88,4 +88,5 @@ class CarInterface(CarInterfaceBase): def apply(self, c): # in mock no carcontrols - return [] + actuators = car.CarControl.Actuators.new_message() + return actuators, [] diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index 41ba9f6599..8b40a050c1 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -87,4 +87,7 @@ class CarController(): self.packer, lkas_hud_info_msg, steer_hud_alert )) - return can_sends + new_actuators = actuators.copy() + new_actuators.steeringAngleDeg = apply_angle + + return new_actuators, can_sends diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 18ae885f83..86c1630fb6 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -78,9 +78,10 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, c.hudControl.rightLaneVisible, - c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index 6163d7329c..72b07f9192 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -8,11 +8,11 @@ class CarController(): def __init__(self, dbc_name, CP, VM): self.apply_steer_last = 0 self.es_distance_cnt = -1 - self.es_accel_cnt = -1 self.es_lkas_cnt = -1 self.cruise_button_prev = 0 self.steer_rate_limited = False + self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) def update(self, enabled, CS, frame, actuators, pcm_cancel_cmd, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart): @@ -20,23 +20,23 @@ class CarController(): can_sends = [] # *** steering *** - if (frame % CarControllerParams.STEER_STEP) == 0: + if (frame % self.p.STEER_STEP) == 0: - apply_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) + apply_steer = int(round(actuators.steer * self.p.STEER_MAX)) # limits due to driver torque new_steer = int(round(apply_steer)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, CarControllerParams) + apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p) self.steer_rate_limited = new_steer != apply_steer if not enabled: apply_steer = 0 if CS.CP.carFingerprint in PREGLOBAL_CARS: - can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, CarControllerParams.STEER_STEP)) + can_sends.append(subarucan.create_preglobal_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) else: - can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, CarControllerParams.STEER_STEP)) + can_sends.append(subarucan.create_steering_control(self.packer, apply_steer, frame, self.p.STEER_STEP)) self.apply_steer_last = apply_steer @@ -44,7 +44,7 @@ class CarController(): # *** alerts and pcm cancel *** if CS.CP.carFingerprint in PREGLOBAL_CARS: - if self.es_accel_cnt != CS.es_accel_msg["Counter"]: + if self.es_distance_cnt != CS.es_distance_msg["Counter"]: # 1 = main, 2 = set shallow, 3 = set deep, 4 = resume shallow, 5 = resume deep # disengage ACC when OP is disengaged if pcm_cancel_cmd: @@ -60,8 +60,8 @@ class CarController(): cruise_button = 0 self.cruise_button_prev = cruise_button - can_sends.append(subarucan.create_es_throttle_control(self.packer, cruise_button, CS.es_accel_msg)) - self.es_accel_cnt = CS.es_accel_msg["Counter"] + can_sends.append(subarucan.create_preglobal_es_distance(self.packer, cruise_button, CS.es_distance_msg)) + self.es_distance_cnt = CS.es_distance_msg["Counter"] else: if self.es_distance_cnt != CS.es_distance_msg["Counter"]: @@ -72,4 +72,7 @@ class CarController(): can_sends.append(subarucan.create_es_lkas(self.packer, CS.es_lkas_msg, enabled, visual_alert, left_line, right_line, left_lane_depart, right_lane_depart)) self.es_lkas_cnt = CS.es_lkas_msg["Counter"] - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/subaru/carstate.py b/selfdrive/car/subaru/carstate.py index 1f56f09ff7..e9728e557b 100644 --- a/selfdrive/car/subaru/carstate.py +++ b/selfdrive/car/subaru/carstate.py @@ -65,14 +65,13 @@ class CarState(CarStateBase): ret.steerError = cp.vl["Steering_Torque"]["Steer_Error_1"] == 1 if self.car_fingerprint in PREGLOBAL_CARS: - self.cruise_button = cp_cam.vl["ES_CruiseThrottle"]["Cruise_Button"] + self.cruise_button = cp_cam.vl["ES_Distance"]["Cruise_Button"] self.ready = not cp_cam.vl["ES_DashStatus"]["Not_Ready_Startup"] - self.es_accel_msg = copy.copy(cp_cam.vl["ES_CruiseThrottle"]) else: ret.steerWarning = cp.vl["Steering_Torque"]["Steer_Warning"] == 1 ret.cruiseState.nonAdaptive = cp_cam.vl["ES_DashStatus"]["Conventional_Cruise"] == 1 - self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"]) self.es_lkas_msg = copy.copy(cp_cam.vl["ES_LKAS_State"]) + self.es_distance_msg = copy.copy(cp_cam.vl["ES_Distance"]) return ret @@ -168,28 +167,28 @@ class CarState(CarStateBase): ("Cruise_Set_Speed", "ES_DashStatus", 0), ("Not_Ready_Startup", "ES_DashStatus", 0), - ("Throttle_Cruise", "ES_CruiseThrottle", 0), - ("Signal1", "ES_CruiseThrottle", 0), - ("Cruise_Activated", "ES_CruiseThrottle", 0), - ("Signal2", "ES_CruiseThrottle", 0), - ("Brake_On", "ES_CruiseThrottle", 0), - ("Distance_Swap", "ES_CruiseThrottle", 0), - ("Standstill", "ES_CruiseThrottle", 0), - ("Signal3", "ES_CruiseThrottle", 0), - ("Close_Distance", "ES_CruiseThrottle", 0), - ("Signal4", "ES_CruiseThrottle", 0), - ("Standstill_2", "ES_CruiseThrottle", 0), - ("Cruise_Fault", "ES_CruiseThrottle", 0), - ("Signal5", "ES_CruiseThrottle", 0), - ("Counter", "ES_CruiseThrottle", 0), - ("Signal6", "ES_CruiseThrottle", 0), - ("Cruise_Button", "ES_CruiseThrottle", 0), - ("Signal7", "ES_CruiseThrottle", 0), + ("Cruise_Throttle", "ES_Distance", 0), + ("Signal1", "ES_Distance", 0), + ("Car_Follow", "ES_Distance", 0), + ("Signal2", "ES_Distance", 0), + ("Brake_On", "ES_Distance", 0), + ("Distance_Swap", "ES_Distance", 0), + ("Standstill", "ES_Distance", 0), + ("Signal3", "ES_Distance", 0), + ("Close_Distance", "ES_Distance", 0), + ("Signal4", "ES_Distance", 0), + ("Standstill_2", "ES_Distance", 0), + ("Cruise_Fault", "ES_Distance", 0), + ("Signal5", "ES_Distance", 0), + ("Counter", "ES_Distance", 0), + ("Signal6", "ES_Distance", 0), + ("Cruise_Button", "ES_Distance", 0), + ("Signal7", "ES_Distance", 0), ] checks = [ ("ES_DashStatus", 20), - ("ES_CruiseThrottle", 20), + ("ES_Distance", 20), ] else: signals = [ diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 86bba542cc..4023e062d2 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -45,6 +45,16 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 20.], [0., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] + if candidate == CAR.IMPREZA_2020: + ret.mass = 1480. + STD_CARGO_KG + ret.wheelbase = 2.67 + ret.centerToFront = ret.wheelbase * 0.5 + ret.steerRatio = 17 # learned, 14 stock + ret.steerActuatorDelay = 0.1 + ret.lateralTuning.pid.kf = 0.00005 + ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] + ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] + if candidate == CAR.FORESTER: ret.mass = 1568. + STD_CARGO_KG ret.wheelbase = 2.67 @@ -112,8 +122,9 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, - c.cruiseControl.cancel, c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, c.hudControl.rightLaneVisible, c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, + c.cruiseControl.cancel, hud_control.visualAlert, + hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 6485de7901..86ec5e8bd4 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -80,11 +80,11 @@ def create_preglobal_steering_control(packer, apply_steer, frame, steer_step): return packer.make_can_msg("ES_LKAS", 0, values) -def create_es_throttle_control(packer, cruise_button, es_accel_msg): +def create_preglobal_es_distance(packer, cruise_button, es_distance_msg): - values = copy.copy(es_accel_msg) + values = copy.copy(es_distance_msg) values["Cruise_Button"] = cruise_button - values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_CruiseThrottle") + values["Checksum"] = subaru_preglobal_checksum(packer, values, "ES_Distance") - return packer.make_can_msg("ES_CruiseThrottle", 0, values) + return packer.make_can_msg("ES_Distance", 0, values) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index 39f8e5e598..1c72988eee 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -5,17 +5,22 @@ from cereal import car Ecu = car.CarParams.Ecu class CarControllerParams: - STEER_MAX = 2047 # max_steer 4095 - STEER_STEP = 2 # how often we update the steer cmd - STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max - STEER_DELTA_DOWN = 70 # torque decrease per refresh - STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting - STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily - STEER_DRIVER_FACTOR = 1 # from dbc + def __init__(self, CP): + if CP.carFingerprint == CAR.IMPREZA_2020: + self.STEER_MAX = 1439 + else: + self.STEER_MAX = 2047 + self.STEER_STEP = 2 # how often we update the steer cmd + self.STEER_DELTA_UP = 50 # torque increase per refresh, 0.8s to max + self.STEER_DELTA_DOWN = 70 # torque decrease per refresh + self.STEER_DRIVER_ALLOWANCE = 60 # allowed driver torque before start limiting + self.STEER_DRIVER_MULTIPLIER = 10 # weight driver torque heavily + self.STEER_DRIVER_FACTOR = 1 # from dbc class CAR: ASCENT = "SUBARU ASCENT LIMITED 2019" IMPREZA = "SUBARU IMPREZA LIMITED 2019" + IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020" FORESTER = "SUBARU FORESTER 2019" FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018" LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018" @@ -30,6 +35,14 @@ FINGERPRINTS = { CAR.IMPREZA: [{ 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 827: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 }], + CAR.IMPREZA_2020: [{ + # SUBARU CROSSTREK SPORT 2020 + 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1617: 8, 1632: 8, 1650: 8, 1677: 8, 1697: 8, 1722: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8, 1968: 8, 1976: 8, 2015: 8, 2016: 8, 2024: 8 + }, + # IMPREZA 2020 + { + 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 544: 8, 545: 8, 546: 8, 554: 8, 557: 8, 576: 8, 577: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 1614: 8, 1617: 8, 1632: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1743: 8, 1759: 8, 1786: 5, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 + }], CAR.FORESTER: [{ # Forester 2019-2020 2: 8, 64: 8, 65: 8, 72: 8, 73: 8, 280: 8, 281: 8, 282: 8, 290: 8, 312: 8, 313: 8, 314: 8, 315: 8, 316: 8, 326: 8, 372: 8, 544: 8, 545: 8, 546: 8, 552: 8, 554: 8, 557: 8, 576: 8, 577: 8, 722: 8, 801: 8, 802: 8, 803: 8, 805: 8, 808: 8, 811: 8, 816: 8, 826: 8, 837: 8, 838: 8, 839: 8, 842: 8, 912: 8, 915: 8, 940: 8, 961: 8, 984: 8, 1614: 8, 1617: 8, 1632: 8, 1650: 8, 1651: 8, 1657: 8, 1658: 8, 1677: 8, 1697: 8, 1698: 8, 1722: 8, 1743: 8, 1759: 8, 1787: 5, 1788: 8, 1809: 8, 1813: 8, 1817: 8, 1821: 8, 1840: 8, 1848: 8, 1924: 8, 1932: 8, 1952: 8, 1960: 8 @@ -68,9 +81,57 @@ FINGERPRINTS = { }], } +FW_VERSIONS = { + CAR.ASCENT: { + # 2019 Ascent UDM + # Ecu, addr, subaddr: ROM ID + (Ecu.esp, 0x7b0, None): [ + b'\xa5 \x19\x02\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x85\xc0\xd0\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00d\xb9\x1f@ \x10', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xbb,\xa0t\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\x00\xfe\xf7\x00\x00', + ], + }, + CAR.IMPREZA: { + # 2018 Crosstrek EDM + # 2018 Impreza ADM + # Ecu, addr, subaddr: ROM ID + (Ecu.esp, 0x7b0, None): [ + b'\x7a\x94\x3f\x90\x00', + b'\xa2 \x185\x00', + ], + (Ecu.eps, 0x746, None): [ + b'\x7a\xc0\x0c\x00', + b'z\xc0\b\x00', + ], + (Ecu.fwdCamera, 0x787, None): [ + b'\x00\x00\x64\xb5\x1f\x40\x20\x0e', + b'\x00\x00d\xdc\x1f@ \x0e', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xaa\x61\x66\x73\x07', + b'\xbeacr\a', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xe3\xe5\x46\x31\x00', + b'\xe4\xe5\x061\x00', + ], + }, +} + STEER_THRESHOLD = { CAR.ASCENT: 80, CAR.IMPREZA: 80, + CAR.IMPREZA_2020: 80, CAR.FORESTER: 80, CAR.FORESTER_PREGLOBAL: 75, CAR.LEGACY_PREGLOBAL: 75, @@ -81,6 +142,7 @@ STEER_THRESHOLD = { DBC = { CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None), CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None), + CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None), CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None), CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index 7e6a2f2e9a..4efd1c1fe7 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -62,4 +62,7 @@ class CarController(): # TODO: HUD control - return can_sends + new_actuators = actuators.copy() + new_actuators.steeringAngleDeg = apply_angle + + return actuators, can_sends diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 45dc0a7239..4b5b211787 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -71,6 +71,6 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.cruiseControl.cancel) + ret = self.CC.update(c.enabled, self.CS, self.frame, c.actuators, c.cruiseControl.cancel) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index 93dc05c838..930619ee67 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -59,7 +59,7 @@ class TestCarInterfaces(unittest.TestCase): car_interface.apply(CC) # Test radar interface - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % car_params.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface radar_interface = RadarInterface(car_params) assert radar_interface diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index 1997e6c111..2be745df4f 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -19,12 +19,12 @@ class CarController(): self.steer_rate_limited = False self.packer = CANPacker(dbc_name) + self.gas = 0 + self.accel = 0 def update(self, enabled, active, CS, frame, actuators, pcm_cancel_cmd, hud_alert, left_line, right_line, lead, left_lane_depart, right_lane_depart): - # *** compute control surfaces *** - # gas and brake if CS.CP.enableGasInterceptor and enabled: MAX_INTERCEPTOR_GAS = 0.5 @@ -89,20 +89,22 @@ class CarController(): # we can spam can to cancel the system even if we are using lat only control if (frame % 3 == 0 and CS.CP.openpilotLongitudinalControl) or pcm_cancel_cmd: - lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present do ACC can be engaged + lead = lead or CS.out.vEgo < 12. # at low speed we always assume the lead is present so ACC can be engaged # Lexus IS uses a different cancellation message if pcm_cancel_cmd and CS.CP.carFingerprint in [CAR.LEXUS_IS, CAR.LEXUS_RC]: can_sends.append(create_acc_cancel_command(self.packer)) elif CS.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) + self.accel = pcm_accel_cmd else: can_sends.append(create_accel_command(self.packer, 0, pcm_cancel_cmd, False, lead, CS.acc_type)) - if frame % 2 == 0 and CS.CP.enableGasInterceptor: + if frame % 2 == 0 and CS.CP.enableGasInterceptor and CS.CP.openpilotLongitudinalControl: # send exactly zero if gas cmd is zero. Interceptor will send the max between read value and gas cmd. # This prevents unexpected pedal range rescaling can_sends.append(create_gas_interceptor_command(self.packer, interceptor_gas_cmd, frame // 2)) + self.gas = interceptor_gas_cmd # ui mesg is at 100Hz but we send asap if: # - there is something to display @@ -120,15 +122,19 @@ class CarController(): send_ui = True if (frame % 100 == 0 or send_ui): - can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart)) + can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, left_line, right_line, left_lane_depart, right_lane_depart, enabled)) if frame % 100 == 0 and CS.CP.enableDsu: can_sends.append(create_fcw_command(self.packer, fcw_alert)) - #*** static msgs *** - + # *** static msgs *** for (addr, cars, bus, fr_step, vl) in STATIC_DSU_MSGS: if frame % fr_step == 0 and CS.CP.enableDsu and CS.CP.carFingerprint in cars: can_sends.append(make_can_msg(addr, vl, bus)) - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.accel = self.accel + new_actuators.gas = self.gas + + return new_actuators, can_sends diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index f9d6b586fd..5c13d2b1b0 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -57,40 +57,15 @@ class CarInterface(CarInterfaceBase): ret.mass = 2860. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid set_lat_tune(ret.lateralTuning, LatTunes.PID_A) - elif candidate == CAR.LEXUS_RX: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 14.8 - tire_stiffness_factor = 0.5533 - ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_B) - - elif candidate == CAR.LEXUS_RXH: + elif candidate in [CAR.LEXUS_RX, CAR.LEXUS_RXH, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2]: stop_and_go = True ret.wheelbase = 2.79 ret.steerRatio = 16. # 14.8 is spec end-to-end - tire_stiffness_factor = 0.444 # not optimized yet + ret.wheelSpeedFactor = 1.035 + tire_stiffness_factor = 0.5533 ret.mass = 4481. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max set_lat_tune(ret.lateralTuning, LatTunes.PID_C) - elif candidate == CAR.LEXUS_RX_TSS2: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 14.8 - tire_stiffness_factor = 0.5533 # not optimized yet - ret.mass = 4387. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_D) - ret.wheelSpeedFactor = 1.035 - - elif candidate == CAR.LEXUS_RXH_TSS2: - stop_and_go = True - ret.wheelbase = 2.79 - ret.steerRatio = 16.0 # 14.8 is spec end-to-end - tire_stiffness_factor = 0.444 # not optimized yet - ret.mass = 4481.0 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - set_lat_tune(ret.lateralTuning, LatTunes.PID_E) - ret.wheelSpeedFactor = 1.035 - elif candidate in [CAR.CHR, CAR.CHRH]: stop_and_go = True ret.wheelbase = 2.63906 @@ -312,12 +287,12 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz def apply(self, c): - - can_sends = self.CC.update(c.enabled, c.active, self.CS, self.frame, - c.actuators, c.cruiseControl.cancel, - c.hudControl.visualAlert, c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, c.hudControl.leadVisible, - c.hudControl.leftLaneDepart, c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, c.active, self.CS, self.frame, + c.actuators, c.cruiseControl.cancel, + hud_control.visualAlert, hud_control.leftLaneVisible, + hud_control.rightLaneVisible, hud_control.leadVisible, + hud_control.leftLaneDepart, hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 5a78e3c7b6..0852991611 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -67,11 +67,11 @@ def create_fcw_command(packer, fcw): return packer.make_can_msg("ACC_HUD", 0, values) -def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart): +def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart, enabled): values = { "RIGHT_LINE": 3 if right_lane_depart else 1 if right_line else 2, "LEFT_LINE": 3 if left_lane_depart else 1 if left_line else 2, - "BARRIERS" : 3 if left_lane_depart else 2 if right_lane_depart else 0, + "BARRIERS" : 1 if enabled else 0, "SET_ME_X0C": 0x0c, "SET_ME_X2C": 0x2c, "SET_ME_X38": 0x38, diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py index 15c8bbfcc6..f26fc72a09 100644 --- a/selfdrive/car/toyota/tunes.py +++ b/selfdrive/car/toyota/tunes.py @@ -80,10 +80,6 @@ def set_lat_tune(tune, name): tune.pid.kpV = [0.2] tune.pid.kiV = [0.05] tune.pid.kf = 0.00003 - elif name == LatTunes.PID_B: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00006 elif name == LatTunes.PID_C: tune.pid.kpV = [0.6] tune.pid.kiV = [0.1] @@ -92,10 +88,6 @@ def set_lat_tune(tune, name): tune.pid.kpV = [0.6] tune.pid.kiV = [0.1] tune.pid.kf = 0.00007818594 - elif name == LatTunes.PID_E: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.15] - tune.pid.kf = 0.00007818594 elif name == LatTunes.PID_F: tune.pid.kpV = [0.723] tune.pid.kiV = [0.0428] diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 692de379b5..a70a6d8163 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -536,6 +536,7 @@ FW_VERSIONS = { b'\x018966312P9200\x00\x00\x00\x00', b'\x018966312P9300\x00\x00\x00\x00', b'\x018966312Q2300\x00\x00\x00\x00', + b'\x018966312Q8000\x00\x00\x00\x00', b'\x018966312R0000\x00\x00\x00\x00', b'\x018966312R0100\x00\x00\x00\x00', b'\x018966312R1000\x00\x00\x00\x00', @@ -583,6 +584,7 @@ FW_VERSIONS = { b'\x01F152612B60\x00\x00\x00\x00\x00\x00', b'\x01F152612B61\x00\x00\x00\x00\x00\x00', b'\x01F152612B71\x00\x00\x00\x00\x00\x00', + b'\x01F152612B81\x00\x00\x00\x00\x00\x00', b'\x01F152612B90\x00\x00\x00\x00\x00\x00', b'\x01F152612C00\x00\x00\x00\x00\x00\x00', b'F152602191\x00\x00\x00\x00\x00\x00', @@ -768,6 +770,7 @@ FW_VERSIONS = { b'\x01896630EB2100\x00\x00\x00\x00', b'\x01896630EB2200\x00\x00\x00\x00', b'\x01896630EC4000\x00\x00\x00\x00', + b'\x01896630ED9000\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -797,6 +800,7 @@ FW_VERSIONS = { b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630EB3100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', + b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', @@ -1552,6 +1556,7 @@ FW_VERSIONS = { }, CAR.PRIUS_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x028966347B1000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966347C8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x038966347C0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00897CF4710101\x00\x00\x00\x00', @@ -1564,6 +1569,7 @@ FW_VERSIONS = { b'F152647510\x00\x00\x00\x00\x00\x00', b'F152647520\x00\x00\x00\x00\x00\x00', b'F152647521\x00\x00\x00\x00\x00\x00', + b'F152647531\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B47070\x00\x00\x00\x00\x00\x00', @@ -1636,11 +1642,11 @@ DBC = { # Toyota/Lexus Safety Sense 2.0 and 2.5 -TSS2_CAR = set([CAR.RAV4_TSS2, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, +TSS2_CAR = {CAR.RAV4_TSS2, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.LEXUS_RX_TSS2, CAR.LEXUS_RXH_TSS2, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2, - CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2]) + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2} -NO_DSU_CAR = TSS2_CAR | set([CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH]) +NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} # no resume button press required -NO_STOP_TIMER_CAR = TSS2_CAR | set([CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH]) +NO_STOP_TIMER_CAR = TSS2_CAR | {CAR.RAV4H, CAR.HIGHLANDERH, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_ESH} diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index 977818dbd5..a0119e2a52 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -110,4 +110,7 @@ class CarController(): self.graButtonStatesToSend = None self.graMsgSentCount = 0 - return can_sends + new_actuators = actuators.copy() + new_actuators.steer = self.apply_steer_last / P.STEER_MAX + + return new_actuators, can_sends diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 560e64ce2d..84bfe98771 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -152,7 +152,7 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.84 else: - raise ValueError("unsupported car %s" % candidate) + raise ValueError(f"unsupported car {candidate}") ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.45 @@ -216,11 +216,12 @@ class CarInterface(CarInterfaceBase): return self.CS.out def apply(self, c): - can_sends = self.CC.update(c.enabled, self.CS, self.frame, self.ext_bus, c.actuators, - c.hudControl.visualAlert, - c.hudControl.leftLaneVisible, - c.hudControl.rightLaneVisible, - c.hudControl.leftLaneDepart, - c.hudControl.rightLaneDepart) + hud_control = c.hudControl + ret = self.CC.update(c.enabled, self.CS, self.frame, self.ext_bus, c.actuators, + hud_control.visualAlert, + hud_control.leftLaneVisible, + hud_control.rightLaneVisible, + hud_control.leftLaneDepart, + hud_control.rightLaneDepart) self.frame += 1 - return can_sends + return ret diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 822326acda..d937ecad97 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 @@ FW_VERSIONS = { b'\xf1\x8704E906024AK\xf1\x899937', b'\xf1\x8704E906024AS\xf1\x899912', b'\xf1\x8704E906024B \xf1\x895594', + b'\xf1\x8704E906024C \xf1\x899970', b'\xf1\x8704E906024L \xf1\x895595', b'\xf1\x8704E906024L \xf1\x899970', b'\xf1\x8704E906027MS\xf1\x896223', @@ -306,6 +312,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AG\xf1\x890336\xf1\x82\02314171231313500314611011630169333463100', + b'\xf1\x875Q0959655AG\xf1\x890338\xf1\x82\x1314171231313500314611011630169333463100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314642011650169333463100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02314171231313500314643011650169333463100', b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\02311170031313300314240011150119333433100', @@ -322,6 +329,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572N \xf1\x890681', + b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', ], }, @@ -741,6 +749,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704L906026FP\xf1\x891196', b'\xf1\x8704L906026KB\xf1\x894071', + b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906264A \xf1\x890002', ], @@ -748,6 +757,7 @@ FW_VERSIONS = { b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870D9300011T \xf1\x894801', b'\xf1\x870D9300012 \xf1\x894940', + b'\xf1\x870GC300043 \xf1\x892301', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', @@ -759,6 +769,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522UZ070303', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\00563UZ060700', b'\xf1\x875Q0910143B \xf1\x892201\xf1\x82\x0563UZ060600', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567UZ070600', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x873Q0907572B \xf1\x890192', diff --git a/selfdrive/common/modeldata.h b/selfdrive/common/modeldata.h index 9a9414cfa3..8d91f7be10 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 a405d21609..f0508cace0 100644 --- a/selfdrive/common/params.cc +++ b/selfdrive/common/params.cc @@ -85,6 +85,7 @@ private: 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}, diff --git a/selfdrive/common/util.cc b/selfdrive/common/util.cc index f115b13dc7..534a7f4456 100644 --- a/selfdrive/common/util.cc +++ b/selfdrive/common/util.cc @@ -20,6 +20,8 @@ #include #endif // __linux__ +namespace util { + void set_thread_name(const char* name) { #ifdef __linux__ // pthread_setname_np is dumb (fails instead of truncates) @@ -56,8 +58,6 @@ int set_core_affinity(std::vector cores) { #endif } -namespace util { - std::string read_file(const std::string& fn) { std::ifstream f(fn, std::ios::binary | std::ios::in); if (f.is_open()) { diff --git a/selfdrive/common/util.h b/selfdrive/common/util.h index a47cce68ff..9a6a4d9bdb 100644 --- a/selfdrive/common/util.h +++ b/selfdrive/common/util.h @@ -37,13 +37,12 @@ const double MS_TO_MPH = MS_TO_KPH * KM_TO_MILE; const double METER_TO_MILE = KM_TO_MILE / 1000.0; const double METER_TO_FOOT = 3.28084; -void set_thread_name(const char* name); +namespace util { +void set_thread_name(const char* name); int set_realtime_priority(int level); int set_core_affinity(std::vector cores); -namespace util { - // ***** Time helpers ***** struct tm get_time(); bool time_valid(struct tm sys_time); diff --git a/selfdrive/common/version.h b/selfdrive/common/version.h index aa786903c4..f1f7df9baf 100644 --- a/selfdrive/common/version.h +++ b/selfdrive/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.12" +#define COMMA_VERSION "0.8.13" diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index fb58d56472..f70a220499 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -28,6 +28,7 @@ from selfdrive.locationd.calibrationd import Calibration from selfdrive.hardware import HARDWARE, TICI, EON from selfdrive.manager.process_config import managed_processes +SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS LANE_DEPARTURE_THRESHOLD = 0.1 STEER_ANGLE_SATURATION_TIMEOUT = 1.0 / DT_CTRL @@ -149,7 +150,7 @@ class Controls: self.v_cruise_kph_last = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 - self.can_error_counter = 0 + self.can_rcv_error_counter = 0 self.last_blinker_frame = 0 self.saturated_count = 0 self.distance_traveled = 0 @@ -158,6 +159,7 @@ class Controls: self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = False self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0} + self.last_actuators = car.CarControl.Actuators.new_message() # TODO: no longer necessary, aside from process replay self.sm['liveParameters'].valid = True @@ -251,7 +253,7 @@ class Controls: LaneChangeState.laneChangeFinishing]: self.events.add(EventName.laneChange) - if self.can_rcv_error or not CS.canValid: + if not CS.canValid: self.events.add(EventName.canError) for i, pandaState in enumerate(self.sm['pandaStates']): @@ -271,7 +273,7 @@ class Controls: self.events.add(EventName.radarFault) elif not self.sm.valid["pandaStates"]: self.events.add(EventName.usbError) - elif not self.sm.all_alive_and_valid(): + elif not self.sm.all_alive_and_valid() or self.can_rcv_error: self.events.add(EventName.commIssue) if not self.logged_comm_issue: invalid = [s for s, valid in self.sm.valid.items() if not valid] @@ -300,7 +302,7 @@ class Controls: # Check for mismatch between openpilot and car's PCM cruise_mismatch = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) self.cruise_mismatch_counter = self.cruise_mismatch_counter + 1 if cruise_mismatch else 0 - if self.cruise_mismatch_counter > int(1. / DT_CTRL): + if self.cruise_mismatch_counter > int(3. / DT_CTRL): self.events.add(EventName.cruiseMismatch) # Check for FCW @@ -344,7 +346,7 @@ class Controls: self.events.add(EventName.localizerMalfunction) # Check if all manager processes are running - not_running = set(p.name for p in self.sm['managerState'].processes if not p.running) + not_running = {p.name for p in self.sm['managerState'].processes if not p.running} if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES): self.events.add(EventName.processNotRunning) @@ -376,7 +378,7 @@ class Controls: # 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 @@ -430,7 +432,7 @@ class Controls: if self.state == State.enabled: if self.events.any(ET.SOFT_DISABLE): self.state = State.softDisabling - self.soft_disable_timer = int(3 / DT_CTRL) + self.soft_disable_timer = int(SOFT_DISABLE_TIME / DT_CTRL) self.current_alert_types.append(ET.SOFT_DISABLE) # SOFT DISABLING @@ -509,7 +511,7 @@ class Controls: lat_plan.psis, lat_plan.curvatures, lat_plan.curvatureRates) - actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(lat_active, CS, self.CP, self.VM, params, + actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(lat_active, CS, self.CP, self.VM, params, self.last_actuators, desired_curvature, desired_curvature_rate) else: lac_log = log.ControlsState.LateralDebugState.new_message() @@ -577,59 +579,66 @@ class Controls: CC.active = self.active CC.actuators = actuators - if len(self.sm['liveLocationKalman'].orientationNED.value) > 2: - CC.roll = self.sm['liveLocationKalman'].orientationNED.value[0] - CC.pitch = self.sm['liveLocationKalman'].orientationNED.value[1] + orientation_value = self.sm['liveLocationKalman'].orientationNED.value + if len(orientation_value) > 2: + CC.roll = orientation_value[0] + CC.pitch = orientation_value[1] CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True - CC.hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS) - CC.hudControl.speedVisible = self.enabled - CC.hudControl.lanesVisible = self.enabled - CC.hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead + hudControl = CC.hudControl + hudControl.setSpeed = float(self.v_cruise_kph * CV.KPH_TO_MS) + hudControl.speedVisible = self.enabled + hudControl.lanesVisible = self.enabled + hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead - CC.hudControl.rightLaneVisible = True - CC.hudControl.leftLaneVisible = True + hudControl.rightLaneVisible = True + hudControl.leftLaneVisible = True recent_blinker = (self.sm.frame - self.last_blinker_frame) * DT_CTRL < 5.0 # 5s blinker cooldown ldw_allowed = self.is_ldw_enabled and CS.vEgo > LDW_MIN_SPEED and not recent_blinker \ and not self.active and self.sm['liveCalibration'].calStatus == Calibration.CALIBRATED - meta = self.sm['modelV2'].meta - if len(meta.desirePrediction) and ldw_allowed: + model_v2 = self.sm['modelV2'] + desire_prediction = model_v2.meta.desirePrediction + if len(desire_prediction) and ldw_allowed: right_lane_visible = self.sm['lateralPlan'].rProb > 0.5 left_lane_visible = self.sm['lateralPlan'].lProb > 0.5 - l_lane_change_prob = meta.desirePrediction[Desire.laneChangeLeft - 1] - r_lane_change_prob = meta.desirePrediction[Desire.laneChangeRight - 1] - l_lane_close = left_lane_visible and (self.sm['modelV2'].laneLines[1].y[0] > -(1.08 + CAMERA_OFFSET)) - r_lane_close = right_lane_visible and (self.sm['modelV2'].laneLines[2].y[0] < (1.08 - CAMERA_OFFSET)) + l_lane_change_prob = desire_prediction[Desire.laneChangeLeft - 1] + r_lane_change_prob = desire_prediction[Desire.laneChangeRight - 1] - CC.hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close) - CC.hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) + lane_lines = model_v2.laneLines + l_lane_close = left_lane_visible and (lane_lines[1].y[0] > -(1.08 + CAMERA_OFFSET)) + r_lane_close = right_lane_visible and (lane_lines[2].y[0] < (1.08 - CAMERA_OFFSET)) - if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart: + hudControl.leftLaneDepart = bool(l_lane_change_prob > LANE_DEPARTURE_THRESHOLD and l_lane_close) + hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) + + if hudControl.rightLaneDepart or hudControl.leftLaneDepart: self.events.add(EventName.ldw) clear_event = ET.WARNING if ET.WARNING not in self.current_alert_types else None - alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric]) + 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 + hudControl.visualAlert = self.AM.visual_alert if not self.read_only and self.initialized: # send car controls over can - can_sends = self.CI.apply(CC) + self.last_actuators, can_sends = self.CI.apply(CC) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) + CC.actuatorsOutput = self.last_actuators force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) # Curvature & Steering angle params = self.sm['liveParameters'] - steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetAverageDeg) - curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo) + + steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetDeg) + curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, params.roll) # controlsState dat = messaging.new_message('controlsState') @@ -659,18 +668,20 @@ class Controls: controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.can_error_counter + controlsState.canErrorCounter = self.can_rcv_error_counter + lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: controlsState.lateralControlState.debugState = lac_log elif self.CP.steerControlType == car.CarParams.SteerControlType.angle: controlsState.lateralControlState.angleState = lac_log - elif self.CP.lateralTuning.which() == 'pid': + elif lat_tuning == 'pid': controlsState.lateralControlState.pidState = lac_log - elif self.CP.lateralTuning.which() == 'lqr': + elif lat_tuning == 'lqr': controlsState.lateralControlState.lqrState = lac_log - elif self.CP.lateralTuning.which() == 'indi': + elif lat_tuning == 'indi': controlsState.lateralControlState.indiState = lac_log + self.pm.send('controlsState', dat) # carState diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 6254786b83..f68cd91814 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -1,5 +1,5 @@ from enum import IntEnum -from typing import Dict, Union, Callable, Any +from typing import Dict, Union, Callable, List, Optional from cereal import log, car import cereal.messaging as messaging @@ -42,33 +42,33 @@ EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} class Events: def __init__(self): - self.events = [] - self.static_events = [] + self.events: List[int] = [] + self.static_events: List[int] = [] self.events_prev = dict.fromkeys(EVENTS.keys(), 0) @property - def names(self): + def names(self) -> List[int]: return self.events - def __len__(self): + def __len__(self) -> int: return len(self.events) - def add(self, event_name, static=False): + def add(self, event_name: int, static: bool=False) -> None: if static: self.static_events.append(event_name) self.events.append(event_name) - def clear(self): + def clear(self) -> None: self.events_prev = {k: (v + 1 if k in self.events else 0) for k, v in self.events_prev.items()} self.events = self.static_events.copy() - def any(self, event_type): + def any(self, event_type: str) -> bool: for e in self.events: if event_type in EVENTS.get(e, {}).keys(): return True return False - 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 +129,7 @@ class Alert: 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 +139,14 @@ class Alert: class NoEntryAlert(Alert): - def __init__(self, alert_text_2, visual_alert=VisualAlert.none): + def __init__(self, alert_text_2: str, visual_alert: car.CarControl.HUDControl.VisualAlert=VisualAlert.none): super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal, AlertSize.mid, Priority.LOW, visual_alert, AudibleAlert.refuse, 3.) class SoftDisableAlert(Alert): - def __init__(self, alert_text_2): + def __init__(self, alert_text_2: str): super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, AlertStatus.userPrompt, AlertSize.full, Priority.MID, VisualAlert.steerRequired, @@ -155,13 +155,13 @@ class SoftDisableAlert(Alert): # 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, @@ -198,11 +198,31 @@ def get_display_speed(speed_ms: float, metric: bool) -> str: # ********** alert callback functions ********** -def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: + +AlertCallbackType = Callable[[car.CarParams, messaging.SubMaster, bool, int], Alert] + + +def soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return SoftDisableAlert(alert_text_2) + return func + + +def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: + def func(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: + if soft_disable_time < int(0.5 / DT_CTRL): + return ImmediateDisableAlert(alert_text_2) + return UserSoftDisableAlert(alert_text_2) + return func + + +def below_engage_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") -def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( f"Steer Unavailable Below {get_display_speed(CP.minSteerSpeed, metric)}", "", @@ -210,7 +230,7 @@ def below_steer_speed_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: Priority.MID, VisualAlert.steerRequired, AudibleAlert.prompt, 0.4) -def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: return Alert( "Calibration in Progress: %d%%" % sm['liveCalibration'].calPerc, f"Drive Above {get_display_speed(MIN_SPEED_FILTER, metric)}", @@ -218,7 +238,7 @@ def calibration_incomplete_alert(CP: car.CarParams, sm: messaging.SubMaster, met Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .2) -def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: gps_integrated = sm['peripheralState'].pandaType in [log.PandaState.PandaType.uno, log.PandaState.PandaType.dos] return Alert( "Poor GPS reception", @@ -227,25 +247,28 @@ def no_gps_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Al Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) -def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def wrong_car_mode_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: text = "Cruise Mode Disabled" if CP.carName == "honda": text = "Main Switch Off" return NoEntryAlert(text) -def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool) -> Alert: +def joystick_alert(CP: car.CarParams, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: axes = sm['testJoystick'].axes gb, steer = list(axes)[:2] if len(axes) else (0., 0.) vals = f"Gas: {round(gb * 100.)}%, Steer: {round(steer * 100.)}%" return NormalPermanentAlert("Joystick Mode", vals) -EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, bool], Alert]]]] = { + +EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { # ********** events with no alerts ********** EventName.stockFcw: {}, + EventName.lkasDisabled: {}, + # ********** events only containing alerts displayed in all states ********** EventName.joystickDebug: { @@ -358,7 +381,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # bad alignment or bad sensor data. If this happens consistently consider creating an issue on GitHub EventName.vehicleModelInvalid: { ET.NO_ENTRY: NoEntryAlert("Vehicle Parameter Identification Failed"), - ET.SOFT_DISABLE: SoftDisableAlert("Vehicle Parameter Identification Failed"), + ET.SOFT_DISABLE: soft_disable_alert("Vehicle Parameter Identification Failed"), }, EventName.steerTempUnavailableSilent: { @@ -544,7 +567,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.steerTempUnavailable: { - ET.SOFT_DISABLE: SoftDisableAlert("Steering Temporarily Unavailable"), + ET.SOFT_DISABLE: soft_disable_alert("Steering Temporarily Unavailable"), ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable"), }, @@ -581,12 +604,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo EventName.overheat: { ET.PERMANENT: NormalPermanentAlert("System Overheated"), - ET.SOFT_DISABLE: SoftDisableAlert("System Overheated"), + ET.SOFT_DISABLE: soft_disable_alert("System Overheated"), ET.NO_ENTRY: NoEntryAlert("System Overheated"), }, EventName.wrongGear: { - ET.SOFT_DISABLE: UserSoftDisableAlert("Gear not D"), + ET.SOFT_DISABLE: user_soft_disable_alert("Gear not D"), ET.NO_ENTRY: NoEntryAlert("Gear not D"), }, @@ -597,33 +620,33 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # See https://comma.ai/setup for more information EventName.calibrationInvalid: { ET.PERMANENT: NormalPermanentAlert("Calibration Invalid", "Remount Device and Recalibrate"), - ET.SOFT_DISABLE: SoftDisableAlert("Calibration Invalid: Remount Device & Recalibrate"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration Invalid: Remount Device & Recalibrate"), ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Remount Device & Recalibrate"), }, EventName.calibrationIncomplete: { ET.PERMANENT: calibration_incomplete_alert, - ET.SOFT_DISABLE: SoftDisableAlert("Calibration in Progress"), + ET.SOFT_DISABLE: soft_disable_alert("Calibration in Progress"), ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"), }, EventName.doorOpen: { - ET.SOFT_DISABLE: UserSoftDisableAlert("Door Open"), + ET.SOFT_DISABLE: user_soft_disable_alert("Door Open"), ET.NO_ENTRY: NoEntryAlert("Door Open"), }, EventName.seatbeltNotLatched: { - ET.SOFT_DISABLE: UserSoftDisableAlert("Seatbelt Unlatched"), + ET.SOFT_DISABLE: user_soft_disable_alert("Seatbelt Unlatched"), ET.NO_ENTRY: NoEntryAlert("Seatbelt Unlatched"), }, EventName.espDisabled: { - ET.SOFT_DISABLE: SoftDisableAlert("ESP Off"), + ET.SOFT_DISABLE: soft_disable_alert("ESP Off"), ET.NO_ENTRY: NoEntryAlert("ESP Off"), }, EventName.lowBattery: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Battery"), + ET.SOFT_DISABLE: soft_disable_alert("Low Battery"), ET.NO_ENTRY: NoEntryAlert("Low Battery"), }, @@ -632,7 +655,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is thrown. This can mean a service crashed, did not broadcast a message for # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { - ET.SOFT_DISABLE: SoftDisableAlert("Communication Issue between Processes"), + ET.SOFT_DISABLE: soft_disable_alert("Communication Issue between Processes"), ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes"), }, @@ -642,7 +665,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo }, EventName.radarFault: { - ET.SOFT_DISABLE: SoftDisableAlert("Radar Error: Restart the Car"), + ET.SOFT_DISABLE: soft_disable_alert("Radar Error: Restart the Car"), ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"), }, @@ -650,7 +673,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # is not processing frames fast enough they have to be dropped. This alert is # thrown when over 20% of frames are dropped. EventName.modeldLagging: { - ET.SOFT_DISABLE: SoftDisableAlert("Driving model lagging"), + ET.SOFT_DISABLE: soft_disable_alert("Driving model lagging"), ET.NO_ENTRY: NoEntryAlert("Driving model lagging"), }, @@ -660,25 +683,25 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # usually means the model has trouble understanding the scene. This is used # as a heuristic to warn the driver. EventName.posenetInvalid: { - ET.SOFT_DISABLE: SoftDisableAlert("Model Output Uncertain"), + ET.SOFT_DISABLE: soft_disable_alert("Model Output Uncertain"), ET.NO_ENTRY: NoEntryAlert("Model Output Uncertain"), }, # When the localizer detects an acceleration of more than 40 m/s^2 (~4G) we # alert the driver the device might have fallen from the windshield. EventName.deviceFalling: { - ET.SOFT_DISABLE: SoftDisableAlert("Device Fell Off Mount"), + ET.SOFT_DISABLE: soft_disable_alert("Device Fell Off Mount"), ET.NO_ENTRY: NoEntryAlert("Device Fell Off Mount"), }, EventName.lowMemory: { - ET.SOFT_DISABLE: SoftDisableAlert("Low Memory: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("Low Memory: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("Low Memory", "Reboot your Device"), ET.NO_ENTRY: NoEntryAlert("Low Memory: Reboot Your Device"), }, EventName.highCpuUsage: { - #ET.SOFT_DISABLE: SoftDisableAlert("System Malfunction: Reboot Your Device"), + #ET.SOFT_DISABLE: soft_disable_alert("System Malfunction: Reboot Your Device"), #ET.PERMANENT: NormalPermanentAlert("System Malfunction", "Reboot your Device"), ET.NO_ENTRY: NoEntryAlert("System Malfunction: Reboot Your Device"), }, @@ -714,7 +737,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, Callable[[Any, messaging.SubMaster, boo # Sometimes the USB stack on the device can get into a bad state # causing the connection to the panda to be lost EventName.usbError: { - ET.SOFT_DISABLE: SoftDisableAlert("USB Error: Reboot Your Device"), + ET.SOFT_DISABLE: soft_disable_alert("USB Error: Reboot Your Device"), ET.PERMANENT: NormalPermanentAlert("USB Error: Reboot Your Device", ""), ET.NO_ENTRY: NoEntryAlert("USB Error: Reboot Your Device"), }, diff --git a/selfdrive/controls/lib/lane_planner.py b/selfdrive/controls/lib/lane_planner.py index 88a1f6a672..4b5b8a6bfb 100644 --- a/selfdrive/controls/lib/lane_planner.py +++ b/selfdrive/controls/lib/lane_planner.py @@ -44,21 +44,23 @@ class LanePlanner: self.path_offset = -PATH_OFFSET if wide_camera else PATH_OFFSET def parse_model(self, md): - if len(md.laneLines) == 4 and len(md.laneLines[0].t) == TRAJECTORY_SIZE: - self.ll_t = (np.array(md.laneLines[1].t) + np.array(md.laneLines[2].t))/2 + lane_lines = md.laneLines + if len(lane_lines) == 4 and len(lane_lines[0].t) == TRAJECTORY_SIZE: + self.ll_t = (np.array(lane_lines[1].t) + np.array(lane_lines[2].t))/2 # left and right ll x is the same - self.ll_x = md.laneLines[1].x + 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 diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 8fcb9ae7bf..dc184cf58b 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -10,7 +10,7 @@ class LatControlAngle(): def reset(self): pass - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): angle_log = log.ControlsState.LateralAngleState.new_message() if CS.vEgo < 0.3 or not active: @@ -18,11 +18,11 @@ class LatControlAngle(): angle_steers_des = float(CS.steeringAngleDeg) else: angle_log.active = True - angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + angle_steers_des = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) angle_steers_des += params.angleOffsetDeg angle_log.saturated = False angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) - angle_log.steeringAngleDesiredDeg = angle_steers_des + angle_log.steeringAngleDesiredDeg = angle_steers_des return 0, float(angle_steers_des), angle_log diff --git a/selfdrive/controls/lib/latcontrol_indi.py b/selfdrive/controls/lib/latcontrol_indi.py index 50a8e22b3c..75b27ac8c1 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -82,7 +82,7 @@ class LatControlINDI(): return self.sat_count > self.sat_limit - def update(self, active, CS, CP, VM, params, curvature, curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, curvature, curvature_rate): self.speed = CS.vEgo # Update Kalman filter y = np.array([[math.radians(CS.steeringAngleDeg)], [math.radians(CS.steeringRateDeg)]]) @@ -93,11 +93,11 @@ class LatControlINDI(): indi_log.steeringRateDeg = math.degrees(self.x[1]) indi_log.steeringAccelDeg = math.degrees(self.x[2]) - steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo) + steers_des = VM.get_steer_from_curvature(-curvature, CS.vEgo, params.roll) steers_des += math.radians(params.angleOffsetDeg) indi_log.steeringAngleDesiredDeg = math.degrees(steers_des) - rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo) + rate_des = VM.get_steer_from_curvature(-curvature_rate, CS.vEgo, 0) indi_log.steeringRateDesiredDeg = math.degrees(rate_des) if CS.vEgo < 0.3 or not active: diff --git a/selfdrive/controls/lib/latcontrol_lqr.py b/selfdrive/controls/lib/latcontrol_lqr.py index 16fffac279..5777cde8e8 100644 --- a/selfdrive/controls/lib/latcontrol_lqr.py +++ b/selfdrive/controls/lib/latcontrol_lqr.py @@ -44,7 +44,7 @@ class LatControlLQR(): return self.sat_count > self.sat_limit - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): lqr_log = log.ControlsState.LateralLQRState.new_message() steers_max = get_steer_max(CP, CS.vEgo) @@ -53,7 +53,7 @@ class LatControlLQR(): # Subtract offset. Zero angle should correspond to zero torque steering_angle_no_offset = CS.steeringAngleDeg - params.angleOffsetAverageDeg - desired_angle = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + desired_angle = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) instant_offset = params.angleOffsetDeg - params.angleOffsetAverageDeg desired_angle += instant_offset # Only add offset that originates from vehicle model errors diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index c7730d011c..bcb8ccba56 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -16,15 +16,15 @@ class LatControlPID(): def reset(self): self.pid.reset() - def update(self, active, CS, CP, VM, params, desired_curvature, desired_curvature_rate): + def update(self, active, CS, CP, VM, params, last_actuators, desired_curvature, desired_curvature_rate): pid_log = log.ControlsState.LateralPIDState.new_message() pid_log.steeringAngleDeg = float(CS.steeringAngleDeg) pid_log.steeringRateDeg = float(CS.steeringRateDeg) - angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo)) + angle_steers_des_no_offset = math.degrees(VM.get_steer_from_curvature(-desired_curvature, CS.vEgo, params.roll)) angle_steers_des = angle_steers_des_no_offset + params.angleOffsetDeg - pid_log.steeringAngleDesiredDeg = angle_steers_des + pid_log.steeringAngleDesiredDeg = angle_steers_des pid_log.angleError = angle_steers_des - CS.steeringAngleDeg if CS.vEgo < 0.3 or not active: output_steer = 0.0 diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index 148e4e123d..f402e6e15e 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -48,12 +48,13 @@ lenv.Clean(generated_files, Dir(gen)) lenv.Command(generated_files, ["lat_mpc.py"], - f"cd {Dir('.').abspath} && python lat_mpc.py") + f"cd {Dir('.').abspath} && python3 lat_mpc.py") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CCFLAGS"].append("-Wno-unused") -lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") +if arch != "Darwin": + lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_lat", build_files, LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 0aa2359ae7..dbe29a12bb 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -204,20 +204,22 @@ class LateralPlanner: plan_solution_valid = self.solution_invalid_cnt < 2 plan_send = messaging.new_message('lateralPlan') plan_send.valid = sm.all_alive_and_valid(service_list=['carState', 'controlsState', 'modelV2']) - plan_send.lateralPlan.laneWidth = float(self.LP.lane_width) - plan_send.lateralPlan.dPathPoints = [float(x) for x in self.y_pts] - plan_send.lateralPlan.psis = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 2]] - plan_send.lateralPlan.curvatures = [float(x) for x in self.lat_mpc.x_sol[0:CONTROL_N, 3]] - plan_send.lateralPlan.curvatureRates = [float(x) for x in self.lat_mpc.u_sol[0:CONTROL_N - 1]] + [0.0] - plan_send.lateralPlan.lProb = float(self.LP.lll_prob) - plan_send.lateralPlan.rProb = float(self.LP.rll_prob) - plan_send.lateralPlan.dProb = float(self.LP.d_prob) - - plan_send.lateralPlan.mpcSolutionValid = bool(plan_solution_valid) - - plan_send.lateralPlan.desire = self.desire - plan_send.lateralPlan.useLaneLines = self.use_lanelines - plan_send.lateralPlan.laneChangeState = self.lane_change_state - plan_send.lateralPlan.laneChangeDirection = self.lane_change_direction + + lateralPlan = plan_send.lateralPlan + lateralPlan.laneWidth = float(self.LP.lane_width) + lateralPlan.dPathPoints = [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 beacc518d0..b16fbbeaab 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -7,21 +7,17 @@ from selfdrive.modeld.constants import T_IDXS LongCtrlState = car.CarControl.Actuators.LongControlState -STOPPING_TARGET_SPEED_OFFSET = 0.01 - # As per ISO 15622:2018 for all speeds ACCEL_MIN_ISO = -3.5 # m/s^2 ACCEL_MAX_ISO = 2.0 # m/s^2 -def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, v_pid, - output_accel, brake_pressed, cruise_standstill, min_speed_can): +def long_control_state_trans(CP, active, long_control_state, v_ego, v_target_future, + output_accel, brake_pressed, cruise_standstill): """Update longitudinal control state machine""" - stopping_target_speed = min_speed_can + STOPPING_TARGET_SPEED_OFFSET stopping_condition = (v_ego < 2.0 and cruise_standstill) or \ (v_ego < CP.vEgoStopping and - ((v_pid < stopping_target_speed and v_target_future < stopping_target_speed) or - brake_pressed)) + (v_target_future < CP.vEgoStopping or brake_pressed)) starting_condition = v_target_future > CP.vEgoStarting and not cruise_standstill @@ -69,15 +65,16 @@ class LongControl(): """Update longitudinal control. This updates the state machine and runs a PID loop""" # Interp control trajectory # TODO estimate car specific lag, use .15s for now - if len(long_plan.speeds) == CONTROL_N: - v_target_lower = interp(CP.longitudinalActuatorDelayLowerBound, T_IDXS[:CONTROL_N], long_plan.speeds) - a_target_lower = 2 * (v_target_lower - long_plan.speeds[0])/CP.longitudinalActuatorDelayLowerBound - long_plan.accels[0] + speeds = long_plan.speeds + if len(speeds) == CONTROL_N: + v_target_lower = interp(CP.longitudinalActuatorDelayLowerBound, T_IDXS[:CONTROL_N], speeds) + a_target_lower = 2 * (v_target_lower - speeds[0])/CP.longitudinalActuatorDelayLowerBound - long_plan.accels[0] - v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], long_plan.speeds) - a_target_upper = 2 * (v_target_upper - long_plan.speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] + v_target_upper = interp(CP.longitudinalActuatorDelayUpperBound, T_IDXS[:CONTROL_N], speeds) + a_target_upper = 2 * (v_target_upper - speeds[0])/CP.longitudinalActuatorDelayUpperBound - long_plan.accels[0] a_target = min(a_target_lower, a_target_upper) - v_target_future = long_plan.speeds[-1] + v_target_future = speeds[-1] else: v_target_future = 0.0 a_target = 0.0 @@ -91,8 +88,8 @@ class LongControl(): # Update state machine output_accel = self.last_output_accel self.long_control_state = long_control_state_trans(CP, active, self.long_control_state, CS.vEgo, - v_target_future, self.v_pid, output_accel, - CS.brakePressed, CS.cruiseState.standstill, CP.minSpeedCan) + v_target_future, output_accel, + CS.brakePressed, CS.cruiseState.standstill) if self.long_control_state == LongCtrlState.off or CS.gasPressed: self.reset(CS.vEgo) @@ -100,7 +97,7 @@ class LongControl(): # tracking objects and driving elif self.long_control_state == LongCtrlState.pid: - self.v_pid = long_plan.speeds[0] + self.v_pid = speeds[0] # Toyota starts braking more when it thinks you want to stop # Freeze the integrator so we don't accelerate to compensate, and don't allow positive acceleration @@ -119,7 +116,6 @@ class LongControl(): if not CS.standstill or output_accel > CP.stopAccel: output_accel -= CP.stoppingDecelRate * DT_CTRL output_accel = clip(output_accel, accel_limits[0], accel_limits[1]) - self.reset(CS.vEgo) # Intention is to move again, release brake fast before handing control to PID diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 4abe90f8f8..4c43985d1f 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -58,12 +58,13 @@ lenv.Clean(generated_files, Dir(gen)) lenv.Command(generated_files, ["long_mpc.py"], - f"cd {Dir('.').abspath} && python long_mpc.py") + f"cd {Dir('.').abspath} && python3 long_mpc.py") lenv["CFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CXXFLAGS"].append("-DACADOS_WITH_QPOASES") lenv["CCFLAGS"].append("-Wno-unused") -lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") +if arch != "Darwin": + lenv["LINKFLAGS"].append("-Wl,--disable-new-dtags") lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_long", build_files, LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index e0e0208d67..c07110eefa 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -40,7 +40,7 @@ CRASH_DISTANCE = .5 LIMIT_COST = 1e6 -# Less timestamps doesn't hurt performance and leads to +# Fewer timestamps don't hurt performance and lead to # much better convergence of the MPC with low iterations N = 12 MAX_T = 10.0 @@ -84,9 +84,9 @@ def gen_long_model(): model.xdot = vertcat(x_ego_dot, v_ego_dot, a_ego_dot) # live parameters - x_obstacle = SX.sym('x_obstacle') a_min = SX.sym('a_min') a_max = SX.sym('a_max') + x_obstacle = SX.sym('x_obstacle') prev_a = SX.sym('prev_a') model.p = vertcat(a_min, a_max, x_obstacle, prev_a) @@ -143,8 +143,8 @@ def gen_long_mpc_solver(): # Constraints on speed, acceleration and desired distance to # the obstacle, which is treated as a slack constraint so it - # behaves like an assymetrical cost. - constraints = vertcat((v_ego), + # behaves like an asymmetrical cost. + constraints = vertcat(v_ego, (a_ego - a_min), (a_max - a_ego), ((x_obstacle - x_ego) - (3/4) * (desired_dist_comfort)) / (v_ego + 10.)) @@ -169,7 +169,7 @@ def gen_long_mpc_solver(): ocp.constraints.idxsh = np.arange(CONSTR_DIM) # The HPIPM solver can give decent solutions even when it is stopped early - # Which is critical for our purpose where the compute time is strictly bounded + # Which is critical for our purpose where compute time is strictly bounded # We use HPIPM in the SPEED_ABS mode, which ensures fastest runtime. This # does not cause issues since the problem is well bounded. ocp.solver_options.qp_solver = 'PARTIAL_CONDENSING_HPIPM' @@ -190,21 +190,18 @@ def gen_long_mpc_solver(): return ocp -class LongitudinalMpc(): +class LongitudinalMpc: def __init__(self, e2e=False): self.e2e = e2e self.reset() - self.accel_limit_arr = np.zeros((N+1, 2)) - self.accel_limit_arr[:,0] = -1.2 - self.accel_limit_arr[:,1] = 1.2 self.source = SOURCES[2] def reset(self): self.solver = AcadosOcpSolverFast('long', N, EXPORT_DIR) - self.v_solution = [0.0 for i in range(N+1)] - self.a_solution = [0.0 for i in range(N+1)] + self.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 @@ class LongitudinalMpc(): def set_weights(self): if self.e2e: self.set_weights_for_xva_policy() + self.params[:,0] = -10. + self.params[:,1] = 10. + self.params[:,2] = 1e5 else: self.set_weights_for_lead_policy() @@ -264,7 +264,8 @@ class LongitudinalMpc(): self.x0[1] = v self.x0[2] = a - def extrapolate_lead(self, x_lead, v_lead, a_lead, a_lead_tau): + @staticmethod + def extrapolate_lead(x_lead, v_lead, a_lead, a_lead_tau): a_lead_traj = a_lead * np.exp(-a_lead_tau * (T_IDXS**2)/2.) v_lead_traj = np.clip(v_lead + np.cumsum(T_DIFFS * a_lead_traj), 0.0, 1e8) x_lead_traj = x_lead + np.cumsum(T_DIFFS * v_lead_traj) @@ -347,15 +348,9 @@ class LongitudinalMpc(): for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) - self.accel_limit_arr[:,0] = -10. - self.accel_limit_arr[:,1] = 10. - x_obstacle = 1e5*np.ones((N+1)) - self.params = np.concatenate([self.accel_limit_arr, - x_obstacle[:,None], - self.prev_a[:,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]) @@ -377,8 +372,7 @@ class LongitudinalMpc(): if self.solution_status != 0: if t > self.last_cloudlog_t + 5.0: self.last_cloudlog_t = t - cloudlog.warning("Long mpc reset, solution_status: %s" % ( - self.solution_status)) + cloudlog.warning(f"Long mpc reset, solution_status: {self.solution_status}") self.reset() diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 41bae4c475..f6b949316a 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -4,6 +4,7 @@ import numpy as np from common.numpy_fast import interp import cereal.messaging as messaging +from common.filter_simple import FirstOrderFilter from common.realtime import DT_MDL from selfdrive.modeld.constants import T_IDXS from selfdrive.config import Conversions as CV @@ -48,9 +49,8 @@ class Planner: self.fcw = False - self.v_desired = init_v self.a_desired = init_a - self.alpha = np.exp(-DT_MDL / 2.0) + self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) @@ -69,14 +69,13 @@ class Planner: prev_accel_constraint = True if long_control_state == LongCtrlState.off or sm['carState'].gasPressed: - self.v_desired = v_ego + self.v_desired_filter.x = v_ego self.a_desired = a_ego # Smoothly changing between accel trajectory is only relevant when OP is driving prev_accel_constraint = False # Prevent divergence, smooth in current v_ego - self.v_desired = self.alpha * self.v_desired + (1 - self.alpha) * v_ego - self.v_desired = max(0.0, self.v_desired) + self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) accel_limits = [A_CRUISE_MIN, get_max_accel(v_ego)] accel_limits_turns = limit_accel_in_turns(v_ego, sm['carState'].steeringAngleDeg, accel_limits, self.CP) @@ -88,7 +87,7 @@ class Planner: accel_limits_turns[0] = min(accel_limits_turns[0], self.a_desired + 0.05) accel_limits_turns[1] = max(accel_limits_turns[1], self.a_desired - 0.05) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) - self.mpc.set_cur_state(self.v_desired, self.a_desired) + self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) self.mpc.update(sm['carState'], sm['radarState'], v_cruise, prev_accel_constraint=prev_accel_constraint) self.v_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.v_solution) self.a_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC, self.mpc.a_solution) @@ -102,7 +101,7 @@ class Planner: # Interpolate 0.05 seconds and save as starting point for next iteration a_prev = self.a_desired self.a_desired = float(interp(DT_MDL, T_IDXS[:CONTROL_N], self.a_desired_trajectory)) - self.v_desired = self.v_desired + DT_MDL * (self.a_desired + a_prev) / 2.0 + self.v_desired_filter.x = self.v_desired_filter.x + DT_MDL * (self.a_desired + a_prev) / 2.0 def publish(self, sm, pm): plan_send = messaging.new_message('longitudinalPlan') diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py index 01498fa138..4f87fdf09b 100644 --- a/selfdrive/controls/lib/radar_helpers.py +++ b/selfdrive/controls/lib/radar_helpers.py @@ -146,7 +146,7 @@ class Cluster(): } def __str__(self): - ret = "x: %4.1f y: %4.1f v: %4.1f a: %4.1f" % (self.dRel, self.yRel, self.vRel, self.aLeadK) + ret = f"x: {self.dRel:4.1f} y: {self.yRel:4.1f} v: {self.vRel:4.1f} a: {self.aLeadK:4.1f}" return ret def potential_low_speed_lead(self, v_ego): diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py new file mode 100755 index 0000000000..f2636027e8 --- /dev/null +++ b/selfdrive/controls/lib/tests/test_vehicle_model.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +import math +import unittest + +import numpy as np +from control import StateSpace + +from selfdrive.car.honda.interface import CarInterface +from selfdrive.car.honda.values import CAR +from selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, create_dyn_state_matrices + + +class TestVehicleModel(unittest.TestCase): + def setUp(self): + CP = CarInterface.get_params(CAR.CIVIC) + self.VM = VehicleModel(CP) + + def test_round_trip_yaw_rate(self): + # TODO: fix VM to work at zero speed + for u in np.linspace(1, 30, num=10): + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + yr = self.VM.yaw_rate(sa, u, roll) + new_sa = self.VM.get_steer_from_yaw_rate(yr, u, roll) + + self.assertAlmostEqual(sa, new_sa) + + def test_dyn_ss_sol_against_yaw_rate(self): + """Verify that the yaw_rate helper function matches the results + from the state space model.""" + + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for u in np.linspace(1, 30, num=10): + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + + # Compute yaw rate based on state space model + _, yr1 = dyn_ss_sol(sa, u, roll, self.VM) + + # Compute yaw rate using direct computations + yr2 = self.VM.yaw_rate(sa, u, roll) + self.assertAlmostEqual(float(yr1), yr2) + + def test_syn_ss_sol_simulate(self): + """Verifies that dyn_ss_sol mathes a simulation""" + + for roll in np.linspace(math.radians(-20), math.radians(20), num=11): + for u in np.linspace(1, 30, num=10): + A, B = create_dyn_state_matrices(u, self.VM) + + # Convert to discrete time system + ss = StateSpace(A, B, np.eye(2), np.zeros((2, 2))) + ss = ss.sample(0.01) + + for sa in np.linspace(math.radians(-20), math.radians(20), num=11): + inp = np.array([[sa], [roll]]) + + # Simulate for 1 second + x1 = np.zeros((2, 1)) + for _ in range(100): + x1 = ss.A @ x1 + ss.B @ inp + + # Compute steady state solution directly + x2 = dyn_ss_sol(sa, u, roll, self.VM) + + np.testing.assert_almost_equal(x1, x2, decimal=3) + + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/lib/vehicle_model.py b/selfdrive/controls/lib/vehicle_model.py index a0b1dddfd7..3f180d3252 100755 --- a/selfdrive/controls/lib/vehicle_model.py +++ b/selfdrive/controls/lib/vehicle_model.py @@ -5,7 +5,7 @@ Dynamic bicycle model from "The Science of Vehicle Dynamics (2014), M. Guiggiani The state is x = [v, r]^T with v lateral speed [m/s], and r rotational speed [rad/s] -The input u is the steering angle [rad] +The input u is the steering angle [rad], and roll [rad] The system is defined by x_dot = A*x + B*u @@ -19,6 +19,9 @@ from numpy.linalg import solve from cereal import car +ACCELERATION_DUE_TO_GRAVITY = 9.8 + + class VehicleModel: def __init__(self, CP: car.CarParams): """ @@ -43,7 +46,7 @@ class VehicleModel: self.cR = stiffness_factor * self.cR_orig self.sR = steer_ratio - def steady_state_sol(self, sa: float, u: float) -> np.ndarray: + def steady_state_sol(self, sa: float, u: float, roll: float) -> np.ndarray: """Returns the steady state solution. If the speed is too low we can't use the dynamic model (tire slip is undefined), @@ -52,26 +55,28 @@ class VehicleModel: Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: 2x1 matrix with steady state solution (lateral speed, rotational speed) """ if u > 0.1: - return dyn_ss_sol(sa, u, self) + return dyn_ss_sol(sa, u, roll, self) else: return kin_ss_sol(sa, u, self) - def calc_curvature(self, sa: float, u: float) -> float: + def calc_curvature(self, sa: float, u: float, roll: float) -> float: """Returns the curvature. Multiplied by the speed this will give the yaw rate. Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: Curvature factor [1/m] """ - return self.curvature_factor(u) * sa / self.sR + return (self.curvature_factor(u) * sa / self.sR) + self.roll_compensation(roll, u) def curvature_factor(self, u: float) -> float: """Returns the curvature factor. @@ -86,43 +91,63 @@ class VehicleModel: sf = calc_slip_factor(self) return (1. - self.chi) / (1. - sf * u**2) / self.l - def get_steer_from_curvature(self, curv: float, u: float) -> float: + def get_steer_from_curvature(self, curv: float, u: float, roll: float) -> float: """Calculates the required steering wheel angle for a given curvature Args: curv: Desired curvature [1/m] u: Speed [m/s] + roll: Road Roll [rad] Returns: Steering wheel angle [rad] """ - return curv * self.sR * 1.0 / self.curvature_factor(u) + return (curv - self.roll_compensation(roll, u)) * self.sR * 1.0 / self.curvature_factor(u) + + def roll_compensation(self, roll: float, u: float) -> float: + """Calculates the roll-compensation to curvature + + Args: + roll: Road Roll [rad] + u: Speed [m/s] - def get_steer_from_yaw_rate(self, yaw_rate: float, u: float) -> float: + Returns: + Roll compensation curvature [rad] + """ + sf = calc_slip_factor(self) + + if abs(sf) < 1e-6: + return 0 + else: + return (ACCELERATION_DUE_TO_GRAVITY * roll) / ((1 / sf) - u**2) + + def get_steer_from_yaw_rate(self, yaw_rate: float, u: float, roll: float) -> float: """Calculates the required steering wheel angle for a given yaw_rate Args: yaw_rate: Desired yaw rate [rad/s] u: Speed [m/s] + roll: Road Roll [rad] Returns: Steering wheel angle [rad] """ curv = yaw_rate / u - return self.get_steer_from_curvature(curv, u) + return self.get_steer_from_curvature(curv, u, roll) - def yaw_rate(self, sa: float, u: float) -> float: + def yaw_rate(self, sa: float, u: float, roll: float) -> float: """Calculate yaw rate Args: sa: Steering wheel angle [rad] u: Speed [m/s] + roll: Road Roll [rad] Returns: Yaw rate [rad/s] """ - return self.calc_curvature(sa, u) * u + return self.calc_curvature(sa, u, roll) * u def kin_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: @@ -152,7 +177,7 @@ def create_dyn_state_matrices(u: float, VM: VehicleModel) -> Tuple[np.ndarray, n VM: Vehicle model Returns: - A tuple with the 2x2 A matrix, and 2x1 B matrix + A tuple with the 2x2 A matrix, and 2x2 B matrix Parameters in the vehicle model: cF: Tire stiffness Front [N/rad] @@ -165,30 +190,38 @@ def create_dyn_state_matrices(u: float, VM: VehicleModel) -> Tuple[np.ndarray, n chi: Steer ratio rear [-] """ A = np.zeros((2, 2)) - B = np.zeros((2, 1)) + B = np.zeros((2, 2)) A[0, 0] = - (VM.cF + VM.cR) / (VM.m * u) A[0, 1] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.m * u) - u A[1, 0] = - (VM.cF * VM.aF - VM.cR * VM.aR) / (VM.j * u) A[1, 1] = - (VM.cF * VM.aF**2 + VM.cR * VM.aR**2) / (VM.j * u) + + # Steering input B[0, 0] = (VM.cF + VM.chi * VM.cR) / VM.m / VM.sR B[1, 0] = (VM.cF * VM.aF - VM.chi * VM.cR * VM.aR) / VM.j / VM.sR + + # Roll input + B[0, 1] = -ACCELERATION_DUE_TO_GRAVITY + return A, B -def dyn_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: +def dyn_ss_sol(sa: float, u: float, roll: float, VM: VehicleModel) -> np.ndarray: """Calculate the steady state solution when x_dot = 0, Ax + Bu = 0 => x = -A^{-1} B u Args: sa: Steering angle [rad] u: Speed [m/s] + roll: Road Roll [rad] VM: Vehicle model Returns: 2x1 matrix with steady state solution """ A, B = create_dyn_state_matrices(u, VM) - return -solve(A, B) * sa + inp = np.array([[sa], [roll]]) + return -solve(A, B) @ inp def calc_slip_factor(VM): diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 88f39ce431..d4b0733692 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -135,7 +135,7 @@ class RadarD(): self.tracks[ids].update(rpt[0], rpt[1], rpt[2], v_lead, rpt[3]) idens = list(sorted(self.tracks.keys())) - track_pts = list([self.tracks[iden].get_key_for_cluster() for iden in idens]) + track_pts = [self.tracks[iden].get_key_for_cluster() for iden in idens] # If we have multiple points, cluster them if len(track_pts) > 1: @@ -172,9 +172,10 @@ class RadarD(): radarState.carStateMonoTime = sm.logMonoTime['carState'] if enable_lead: - if len(sm['modelV2'].leadsV3) > 1: - radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, sm['modelV2'].leadsV3[0], low_speed_override=True) - radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, sm['modelV2'].leadsV3[1], low_speed_override=False) + leads_v3 = sm['modelV2'].leadsV3 + if len(leads_v3) > 1: + radarState.leadOne = get_lead(self.v_ego, self.ready, clusters, leads_v3[0], low_speed_override=True) + radarState.leadTwo = get_lead(self.v_ego, self.ready, clusters, leads_v3[1], low_speed_override=False) return dat @@ -189,7 +190,7 @@ def radard_thread(sm=None, pm=None, can_sock=None): # import the radar from the fingerprint cloudlog.info("radard is importing %s", CP.carName) - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % CP.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{CP.carName}.radar_interface').RadarInterface # *** setup messaging if can_sock is None: diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py index 920c126c44..98767d6e2f 100755 --- a/selfdrive/controls/tests/test_alerts.py +++ b/selfdrive/controls/tests/test_alerts.py @@ -70,7 +70,7 @@ class TestAlerts(unittest.TestCase): font = fonts[alert.alert_size][i] w, _ = draw.textsize(txt, font) - msg = "type: %s msg: %s" % (alert.alert_type, txt) + msg = f"type: {alert.alert_type} msg: {txt}" self.assertLessEqual(w, max_text_width, msg=msg) def test_alert_sanity_check(self): diff --git a/selfdrive/debug/can_printer.py b/selfdrive/debug/can_printer.py index cb6ff29006..b4436bd2be 100755 --- a/selfdrive/debug/can_printer.py +++ b/selfdrive/debug/can_printer.py @@ -22,7 +22,7 @@ def can_printer(bus, max_msg, addr): if sec_since_boot() - lp > 0.1: dd = chr(27) + "[2J" - dd += "%5.2f\n" % (sec_since_boot() - start) + dd += f"{sec_since_boot() - start:5.2f}\n" for addr in sorted(msgs.keys()): a = msgs[addr][-1].decode('ascii', 'backslashreplace') x = binascii.hexlify(msgs[addr][-1]).decode('ascii') diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index 300b3ea1fb..424ad67b6d 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -42,6 +42,6 @@ if __name__ == "__main__": for name in socket_names: dts = np.diff(rcv_times[name]) mean = np.mean(dts) - print("%s: Freq %.2f Hz, Min %.2f%%, Max %.2f%%, valid " % (name, 1.0 / mean, np.min(dts) / mean * 100, np.max(dts) / mean * 100), all(valids[name])) + print(f"{name}: Freq {1.0 / mean:.2f} Hz, Min {np.min(dts) / mean * 100:.2f}%, Max {np.max(dts) / mean * 100:.2f}%, valid ", all(valids[name])) prev_print = t diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index aafc696b46..76e809d2c4 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -110,14 +110,14 @@ if __name__ == "__main__": stat['avg'][name] = (stat['avg'][name] * (i - c) + avg * c) / (i) stat['cpu_samples'][name] = [] - msg = 'avg: {1:.2%}, min: {2:.2%}, max: {3:.2%} {0}'.format(os.path.basename(k), stat['avg']['total'], stat['min']['total'], stat['max']['total']) + msg = f"avg: {stat['avg']['total']:.2%}, min: {stat['min']['total']:.2%}, max: {stat['max']['total']:.2%} {os.path.basename(k)}" if args.detailed_times: for stat_type in ['avg', 'min', 'max']: - msg += '\n {}: {}'.format(stat_type, [name + ':' + str(round(stat[stat_type][name]*100, 2)) for name in cpu_time_names]) + msg += f"\n {stat_type}: {[(name + ':' + str(round(stat[stat_type][name] * 100, 2))) for name in cpu_time_names]}" l.append((os.path.basename(k), stat['avg']['total'], msg)) l.sort(key=lambda x: -x[1]) for x in l: print(x[2]) - print('avg sum: {0:.2%} over {1} samples {2} seconds\n'.format( + print('avg sum: {:.2%} over {} samples {} seconds\n'.format( sum(stat['avg']['total'] for k, stat in stats.items()), i, i * SLEEP_INTERVAL )) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index ca238b4293..f28d5373f4 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -52,7 +52,7 @@ def cycle_alerts(duration=200, is_metric=False): events.clear() events.add(alert) - a = events.create_alerts([et, ], [CP, sm, is_metric]) + a = events.create_alerts([et, ], [CP, sm, is_metric, 0]) AM.add_many(frame, a) AM.process_alerts(frame) print(AM.alert) diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py index 047c874fb7..f208920f12 100755 --- a/selfdrive/debug/dump.py +++ b/selfdrive/debug/dump.py @@ -54,13 +54,13 @@ if __name__ == "__main__": elif args.dump_json: print(json.dumps(evt.to_dict())) elif values: - print("logMonotime = {}".format(evt.logMonoTime)) + print(f"logMonotime = {evt.logMonoTime}") for value in values: if hasattr(evt, value[0]): item = evt for key in value: item = getattr(item, key) - print("{} = {}".format(".".join(value), item)) + print(f"{'.'.join(value)} = {item}") print("") else: try: diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 098e39fbe8..326e68f8e7 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -41,5 +41,5 @@ if __name__ == "__main__": sys.exit(1) route = Route(sys.argv[1]) - lr = MultiLogIterator(route.log_paths()[:5], wraparound=False) + lr = MultiLogIterator(route.log_paths()[:5]) get_fingerprint(lr) diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index 6c38957be3..e678db4f17 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -27,5 +27,5 @@ while True: fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items())) - print("number of messages {0}:".format(len(msgs))) - print("fingerprint {0}".format(fingerprint)) + print(f"number of messages {len(msgs)}:") + print(f"fingerprint {fingerprint}") diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index 204d2480c3..e566884a90 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -38,6 +38,11 @@ SUPPORTED_FW_VERSIONS = { "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 ": { + "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", diff --git a/selfdrive/debug/internal/power_monitor.py b/selfdrive/debug/internal/power_monitor.py index 3377088f8b..c995ef6ff2 100755 --- a/selfdrive/debug/internal/power_monitor.py +++ b/selfdrive/debug/internal/power_monitor.py @@ -45,7 +45,7 @@ if __name__ == '__main__': capacity_average = average(capacity_average, capacity) bat_temp_average = average(bat_temp_average, bat_temp) - print("%.2f volts %12.2f ma %12.2f mW %8.2f%% battery %8.1f degC" % (voltage, current, power, capacity, bat_temp)) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {capacity:8.2f}% battery {bat_temp:8.1f} degC") time.sleep(0.1) finally: stop_time = datetime.now() @@ -55,8 +55,8 @@ if __name__ == '__main__': power = power_average[0] capacity = capacity_average[0] bat_temp = bat_temp_average[0] - print("%.2f volts %12.2f ma %12.2f mW %8.2f%% battery %8.1f degC" % (voltage, current, power, capacity, bat_temp)) - print(" {:.2f} Seconds {} samples".format((stop_time-start_time).total_seconds(), voltage_average[1])) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {capacity:8.2f}% battery {bat_temp:8.1f} degC") + print(f" {(stop_time - start_time).total_seconds():.2f} Seconds {voltage_average[1]} samples") print("----------------------------------------------------------------") # reenable charging diff --git a/selfdrive/debug/internal/rt/atrace.sh b/selfdrive/debug/internal/rt/atrace.sh deleted file mode 100755 index 826178c329..0000000000 --- a/selfdrive/debug/internal/rt/atrace.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/bash - -echo 96000 > /d/tracing/buffer_size_kb -atrace -t 10 sched workq -b 96000 > /tmp/trace.txt diff --git a/selfdrive/debug/internal/rt/record_trace.sh b/selfdrive/debug/internal/rt/record_trace.sh deleted file mode 100755 index 00593bfca4..0000000000 --- a/selfdrive/debug/internal/rt/record_trace.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/bash - -cd /d/tracing - -# setup tracer -echo "function" > current_tracer - -echo "start tracing" -echo 1 > tracing_on - -# do stuff -sleep 2 -#/data/openpilot/scripts/restart_modem.sh -#sleep 3 -#/data/openpilot/scripts/restart_modem.sh -sleep 5 - -# disable tracing -echo "done tracing" -echo 0 > tracing_on - -# copy -echo "copy traces" -cp trace /tmp/trace.txt -cp per_cpu/cpu3/trace /tmp/trace_cpu3.txt diff --git a/selfdrive/debug/internal/rt/waste3.c b/selfdrive/debug/internal/rt/waste3.c deleted file mode 100644 index c536ca9c6d..0000000000 --- a/selfdrive/debug/internal/rt/waste3.c +++ /dev/null @@ -1,63 +0,0 @@ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include "../../../common/util.h" -#include "../../../common/timing.h" - -#define CORES 3 -double ttime[CORES]; -double oout[CORES]; - -void waste(int core) { - prctl(PR_SET_NAME, (unsigned long)"waste", 0, 0, 0); - - cpu_set_t my_set; - CPU_ZERO(&my_set); - CPU_SET(core, &my_set); - int ret = sched_setaffinity(0, sizeof(cpu_set_t), &my_set); - printf("set affinity to %d: %d\n", core, ret); - - //struct sched_param sa; - //memset(&sa, 0, sizeof(sa)); - //sa.sched_priority = 51; - //sched_setscheduler(syscall(SYS_gettid), SCHED_FIFO, &sa); - - float32x4_t *tmp = (float32x4_t *)malloc(0x1000008*sizeof(float32x4_t)); - float32x4_t out; - - uint64_t i = 0; - double sec = seconds_since_boot(); - while(1) { - int j; - for (j = 0; j < 0x1000000; j++) { - out = vmlaq_f32(out, tmp[j], tmp[j+1]); - } - if (i == 0x8) { - double nsec = seconds_since_boot(); - ttime[core] = nsec-sec; - oout[core] = out[0] + out[1] + out[2] + out[3]; - i = 0; - sec = nsec; - } - i++; - } -} - -int main() { - pthread_t waster[CORES]; - for (int i = 0 ; i < CORES; i++) { - pthread_create(&waster[i], NULL, waste, (void*)i); - } - while (1) { - for (int i = 0 ; i < CORES; i++) { - printf("%.2f ", ttime[i]); - } - printf("\n"); - sleep(1); - } -} - diff --git a/selfdrive/debug/internal/sensor_test_bootloop.py b/selfdrive/debug/internal/sensor_test_bootloop.py index 9e89add6bb..36eb112e44 100755 --- a/selfdrive/debug/internal/sensor_test_bootloop.py +++ b/selfdrive/debug/internal/sensor_test_bootloop.py @@ -17,7 +17,7 @@ except PermissionError: print("WARNING: failed to make /dev/shm") try: - with open('/tmp/sensor-test-results.json', 'r') as infile: + with open('/tmp/sensor-test-results.json') as infile: data = json.load(infile) except Exception: data = {'sensor-pass': 0, 'sensor-fail': 0} diff --git a/selfdrive/debug/internal/sounds/set_volume.sh b/selfdrive/debug/internal/sounds/set_volume.sh deleted file mode 100755 index b25a421822..0000000000 --- a/selfdrive/debug/internal/sounds/set_volume.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/bash -while true -do - service call audio 3 i32 3 i32 $1 i32 1 - sleep 1 -done diff --git a/selfdrive/debug/internal/sounds/test_sound_stability.py b/selfdrive/debug/internal/sounds/test_sound_stability.py deleted file mode 100755 index ba6c59220b..0000000000 --- a/selfdrive/debug/internal/sounds/test_sound_stability.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -import os -import subprocess -import time -import datetime -import random - -from common.basedir import BASEDIR -import cereal.messaging as messaging - -if __name__ == "__main__": - - sound_dir = os.path.join(BASEDIR, "selfdrive/assets/sounds") - sound_files = [f for f in os.listdir(sound_dir) if f.endswith(".wav")] - play_sound = os.path.join(BASEDIR, "selfdrive/ui/test/play_sound") - - print("disabling charging") - os.system('echo "0" > /sys/class/power_supply/battery/charging_enabled') - - os.environ["LD_LIBRARY_PATH"] = "" - - sm = messaging.SubMaster(["deviceState"]) - - FNULL = open(os.devnull, "w") - start_time = time.time() - while True: - volume = 15 - - n = random.randint(5, 10) - procs = [] - for _ in range(n): - sound = random.choice(sound_files) - p = subprocess.Popen([play_sound, os.path.join(sound_dir, sound), str(volume)], stdout=FNULL, stderr=FNULL) - procs.append(p) - time.sleep(random.uniform(0, 0.75)) - - time.sleep(random.randint(0, 5)) - for p in procs: - p.terminate() - - sm.update(0) - s = time.time() - start_time - hhmmss = str(datetime.timedelta(seconds=s)).split(".")[0] - print("test duration:", hhmmss) - print("\tbattery percent", sm["deviceState"].batteryPercent) diff --git a/selfdrive/debug/internal/sounds/test_sounds.py b/selfdrive/debug/internal/sounds/test_sounds.py deleted file mode 100755 index 3ed3a51589..0000000000 --- a/selfdrive/debug/internal/sounds/test_sounds.py +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env python3 - -import os -import subprocess -import time - -from common.basedir import BASEDIR - -if __name__ == "__main__": - - sound_dir = os.path.join(BASEDIR, "selfdrive/assets/sounds") - sound_files = [f for f in os.listdir(sound_dir) if f.endswith(".wav")] - - play_sound = os.path.join(BASEDIR, "selfdrive/ui/test/play_sound") - - os.environ["LD_LIBRARY_PATH"] = "" - - while True: - for volume in range(10, 16): - for sound in sound_files: - p = subprocess.Popen([play_sound, os.path.join(sound_dir, sound), str(volume)]) - time.sleep(1) - p.terminate() diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index 5589b0d6dd..e46c0b0a1f 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -71,7 +71,7 @@ if __name__ == "__main__": total_times = total_times_new[:] busy_times = busy_times_new[:] - print("CPU %.2f%% - RAM: %.2f%% - Temp %.2fC" % (100. * mean(cores), last_mem, last_temp)) + print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C") if args.cpu and prev_proclog is not None: procs = {} diff --git a/selfdrive/debug/profiling/palanteer/.gitignore b/selfdrive/debug/profiling/palanteer/.gitignore new file mode 100644 index 0000000000..158e7b0759 --- /dev/null +++ b/selfdrive/debug/profiling/palanteer/.gitignore @@ -0,0 +1,2 @@ +palanteer/ +viewer diff --git a/selfdrive/debug/profiling/palanteer/setup.sh b/selfdrive/debug/profiling/palanteer/setup.sh new file mode 100755 index 0000000000..e912a9367f --- /dev/null +++ b/selfdrive/debug/profiling/palanteer/setup.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d palanteer ]; then + git clone https://github.com/dfeneyrou/palanteer + pip install wheel + sudo apt install libunwind-dev libdw-dev +fi + +cd palanteer +git pull + +mkdir -p build +cd build +cmake .. -DCMAKE_BUILD_TYPE=Release +make -j$(nproc) + +pip install --force-reinstall python/dist/palanteer*.whl + +cp bin/palanteer $DIR/viewer diff --git a/selfdrive/debug/run_process_on_route.py b/selfdrive/debug/run_process_on_route.py index bcb741f54e..a6e67d2b24 100755 --- a/selfdrive/debug/run_process_on_route.py +++ b/selfdrive/debug/run_process_on_route.py @@ -17,13 +17,13 @@ if __name__ == "__main__": cfg = [c for c in CONFIGS if c.proc_name == args.process][0] route = Route(args.route) - lr = MultiLogIterator(route.log_paths(), wraparound=False) + lr = MultiLogIterator(route.log_paths()) inputs = list(lr) outputs = replay_process(cfg, inputs) # Remove message generated by the process under test and merge in the new messages - produces = set(o.which() for o in outputs) + produces = {o.which() for o in outputs} inputs = [i for i in inputs if i.which() not in produces] outputs = sorted(inputs + outputs, key=lambda x: x.logMonoTime) diff --git a/selfdrive/debug/toyota_eps_factor.py b/selfdrive/debug/toyota_eps_factor.py index e63122b6a2..0a459bb719 100755 --- a/selfdrive/debug/toyota_eps_factor.py +++ b/selfdrive/debug/toyota_eps_factor.py @@ -59,6 +59,6 @@ def get_eps_factor(lr, plot=False): if __name__ == "__main__": r = Route(sys.argv[1]) - lr = MultiLogIterator(r.log_paths(), wraparound=False) + lr = MultiLogIterator(r.log_paths()) n = get_eps_factor(lr, plot="--plot" in sys.argv) print("EPS torque factor: ", n) diff --git a/selfdrive/hardware/base.py b/selfdrive/hardware/base.py index 06c86f0c26..77ddcbc2d4 100644 --- a/selfdrive/hardware/base.py +++ b/selfdrive/hardware/base.py @@ -1,11 +1,12 @@ -from abc import abstractmethod +from abc import abstractmethod, ABC from collections import namedtuple +from typing import Dict ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic']) -class HardwareBase: +class HardwareBase(ABC): @staticmethod - def get_cmdline(): + def get_cmdline() -> Dict[str, str]: with open('/proc/cmdline') as f: cmdline = f.read() return {kv[0]: kv[1] for kv in [s.split('=') for s in cmdline.split(' ')] if len(kv) == 2} diff --git a/selfdrive/hardware/eon/androidd.py b/selfdrive/hardware/eon/androidd.py index 6de00d8e71..b836eb0129 100755 --- a/selfdrive/hardware/eon/androidd.py +++ b/selfdrive/hardware/eon/androidd.py @@ -52,25 +52,26 @@ def main(): cloudlog.event("android service pid changed", proc=p, cur=cur[p], prev=procs[p]) procs.update(cur) - # check modem state - state = get_modem_state() - if state != modem_state and not modem_killed: - cloudlog.event("modem state changed", state=state) - modem_state = state + if os.path.exists(MODEM_PATH): + # check modem state + state = get_modem_state() + if state != modem_state and not modem_killed: + cloudlog.event("modem state changed", state=state) + modem_state = state - # check modem crashes - cnt = get_modem_crash_count() - if cnt is not None: - if cnt > crash_count: - cloudlog.event("modem crash", count=cnt) - crash_count = cnt + # check modem crashes + cnt = get_modem_crash_count() + if cnt is not None: + if cnt > crash_count: + cloudlog.event("modem crash", count=cnt) + crash_count = cnt - # handle excessive modem crashes - if crash_count > MAX_MODEM_CRASHES and not modem_killed: - cloudlog.event("killing modem") - with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: - f.write("put") - modem_killed = True + # handle excessive modem crashes + if crash_count > MAX_MODEM_CRASHES and not modem_killed: + cloudlog.event("killing modem") + with open("/sys/kernel/debug/msm_subsys/modem", "w") as f: + f.write("put") + modem_killed = True time.sleep(1) diff --git a/selfdrive/hardware/tici/power_monitor.py b/selfdrive/hardware/tici/power_monitor.py index 65f58876de..d7f113cf2c 100755 --- a/selfdrive/hardware/tici/power_monitor.py +++ b/selfdrive/hardware/tici/power_monitor.py @@ -40,7 +40,7 @@ if __name__ == '__main__': power_average = average(power_average, power) power_total_average = average(power_total_average, power_total) - print("%12.2f mW %12.2f mW %12.2f mW" % (power, power_total, power_total-power)) + print(f"{power:12.2f} mW {power_total:12.2f} mW {power_total - power:12.2f} mW") time.sleep(0.25) finally: stop_time = time.monotonic() @@ -48,6 +48,6 @@ if __name__ == '__main__': voltage = voltage_average[0] current = current_average[0] power = power_average[0] - print("%.2f volts %12.2f ma %12.2f mW %12.2f mW" % (voltage, current, power, power_total)) - print(" {:.2f} Seconds {} samples".format(stop_time - start_time, voltage_average[1])) + print(f"{voltage:.2f} volts {current:12.2f} ma {power:12.2f} mW {power_total:12.2f} mW") + print(f" {stop_time - start_time:.2f} Seconds {voltage_average[1]} samples") print("----------------------------------------------------------------") diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 604f4f68d9..61bc56b80e 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -8,6 +8,7 @@ and the image input into the neural network is not corrected for roll. import os import copy +from typing import NoReturn import numpy as np import cereal.messaging as messaging from cereal import log @@ -99,10 +100,16 @@ class Calibrator(): self.old_rpy = smooth_from self.old_rpy_weight = 1.0 + def get_valid_idxs(self, ): + # exclude current block_idx from validity window + before_current = list(range(self.block_idx)) + after_current = list(range(min(self.valid_blocks, self.block_idx + 1), self.valid_blocks)) + return before_current + after_current + def update_status(self): - if self.valid_blocks > 0: - max_rpy_calib = np.array(np.max(self.rpys[:self.valid_blocks], axis=0)) - min_rpy_calib = np.array(np.min(self.rpys[:self.valid_blocks], axis=0)) + if len(self.get_valid_idxs()) > 0: + max_rpy_calib = np.array(np.max(self.rpys[self.get_valid_idxs()], axis=0)) + min_rpy_calib = np.array(np.min(self.rpys[self.get_valid_idxs()], axis=0)) self.calib_spread = np.abs(max_rpy_calib - min_rpy_calib) else: self.calib_spread = np.zeros(3) @@ -157,8 +164,8 @@ class Calibrator(): self.block_idx += 1 self.valid_blocks = max(self.block_idx, self.valid_blocks) self.block_idx = self.block_idx % INPUTS_WANTED - if self.valid_blocks > 0: - self.rpy = np.mean(self.rpys[:self.valid_blocks], axis=0) + if len(self.get_valid_idxs()) > 0: + self.rpy = np.mean(self.rpys[self.get_valid_idxs()], axis=0) self.update_status() @@ -177,11 +184,11 @@ class Calibrator(): 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']) @@ -209,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 7926846895..0350625a91 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -531,7 +531,7 @@ int Localizer::locationd_thread() { } int main() { - set_realtime_priority(5); + util::set_realtime_priority(5); Localizer localizer; return localizer.locationd_thread(); diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index dac95dcd7b..031c56ceb9 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -5,6 +5,7 @@ from typing import Any, Dict import numpy as np +from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY from selfdrive.locationd.models.constants import ObservationKind from selfdrive.swaglog import cloudlog @@ -37,6 +38,7 @@ class States(): VELOCITY = _slice(2) # (x, y) [m/s] YAW_RATE = _slice(1) # [rad/s] STEER_ANGLE = _slice(1) # [rad] + ROAD_ROLL = _slice(1) # [rad] class CarKalman(KalmanFilter): @@ -51,6 +53,7 @@ class CarKalman(KalmanFilter): 10.0, 0.0, 0.0, 0.0, + 0.0 ]) # process noise @@ -63,12 +66,14 @@ class CarKalman(KalmanFilter): .1**2, .01**2, math.radians(0.1)**2, math.radians(0.1)**2, + math.radians(1)**2, ]) P_initial = Q.copy() obs_noise: Dict[int, Any] = { ObservationKind.STEER_ANGLE: np.atleast_2d(math.radians(0.01)**2), ObservationKind.ANGLE_OFFSET_FAST: np.atleast_2d(math.radians(10.0)**2), + ObservationKind.ROAD_ROLL: np.atleast_2d(math.radians(1.0)**2), ObservationKind.STEER_RATIO: np.atleast_2d(5.0**2), ObservationKind.STIFFNESS: np.atleast_2d(5.0**2), ObservationKind.ROAD_FRAME_X_SPEED: np.atleast_2d(0.1**2), @@ -87,7 +92,7 @@ class CarKalman(KalmanFilter): def generate_code(generated_dir): dim_state = CarKalman.initial_x.shape[0] name = CarKalman.name - + # vehicle models comes from The Science of Vehicle Dynamics: Handling, Braking, and Ride of Road and Race Cars # Model used is in 6.15 with formula from 6.198 @@ -106,6 +111,7 @@ class CarKalman(KalmanFilter): cF, cR = x * cF_orig, x * cR_orig angle_offset = state[States.ANGLE_OFFSET, :][0, 0] angle_offset_fast = state[States.ANGLE_OFFSET_FAST, :][0, 0] + theta = state[States.ROAD_ROLL, :][0, 0] sa = state[States.STEER_ANGLE, :][0, 0] sR = state[States.STEER_RATIO, :][0, 0] @@ -122,8 +128,12 @@ class CarKalman(KalmanFilter): B[0, 0] = cF / m / sR B[1, 0] = (cF * aF) / j / sR + C = sp.Matrix(np.zeros((2, 1))) + C[0, 0] = ACCELERATION_DUE_TO_GRAVITY + C[1, 0] = 0 + x = sp.Matrix([v, r]) # lateral velocity, yaw rate - x_dot = A * x + B * (sa - angle_offset - angle_offset_fast) + x_dot = A * x + B * (sa - angle_offset - angle_offset_fast) - C * theta dt = sp.Symbol('dt') state_dot = sp.Matrix(np.zeros((dim_state, 1))) @@ -145,11 +155,12 @@ class CarKalman(KalmanFilter): [sp.Matrix([angle_offset_fast]), ObservationKind.ANGLE_OFFSET_FAST, None], [sp.Matrix([sR]), ObservationKind.STEER_RATIO, None], [sp.Matrix([x]), ObservationKind.STIFFNESS, None], + [sp.Matrix([theta]), ObservationKind.ROAD_ROLL, None], ] gen_code(generated_dir, name, f_sym, dt, state_sym, obs_eqs, dim_state, dim_state, global_vars=global_vars) - def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0): # pylint: disable=super-init-not-called + def __init__(self, generated_dir, steer_ratio=15, stiffness_factor=1, angle_offset=0, P_initial=None): # pylint: disable=super-init-not-called dim_state = self.initial_x.shape[0] dim_state_err = self.P_initial.shape[0] x_init = self.initial_x @@ -157,6 +168,8 @@ class CarKalman(KalmanFilter): x_init[States.STIFFNESS] = stiffness_factor x_init[States.ANGLE_OFFSET] = angle_offset + if P_initial is not None: + self.P_initial = P_initial # init filter self.filter = EKF_sym(generated_dir, self.name, self.Q, self.initial_x, self.P_initial, dim_state, dim_state_err, global_vars=self.global_vars, logger=cloudlog) diff --git a/selfdrive/locationd/models/constants.py b/selfdrive/locationd/models/constants.py index 7450f76be7..f22f503ee0 100644 --- a/selfdrive/locationd/models/constants.py +++ b/selfdrive/locationd/models/constants.py @@ -38,6 +38,7 @@ class ObservationKind: STIFFNESS = 28 # [-] STEER_RATIO = 29 # [-] ROAD_FRAME_X_SPEED = 30 # (x) [m/s] + ROAD_ROLL = 31 # [rad] names = [ 'Unknown', @@ -69,6 +70,8 @@ class ObservationKind: 'Fast Angle Offset', 'Stiffness', 'Steer Ratio', + 'Road Frame x speed', + 'Road Roll', ] @classmethod diff --git a/selfdrive/locationd/models/live_kf.py b/selfdrive/locationd/models/live_kf.py index fa52945932..023479d10e 100755 --- a/selfdrive/locationd/models/live_kf.py +++ b/selfdrive/locationd/models/live_kf.py @@ -158,7 +158,7 @@ class LiveKalman(): delta_x = sp.MatrixSymbol('delta_x', dim_state_err, 1) err_function_sym = sp.Matrix(np.zeros((dim_state, 1))) - delta_quat = sp.Matrix(np.ones((4))) + delta_quat = sp.Matrix(np.ones(4)) delta_quat[1:, :] = sp.Matrix(0.5 * delta_x[States.ECEF_ORIENTATION_ERR, :]) err_function_sym[States.ECEF_POS, :] = sp.Matrix(nom_x[States.ECEF_POS, :] + delta_x[States.ECEF_POS_ERR, :]) err_function_sym[States.ECEF_ORIENTATION, 0] = quat_matrix_r(nom_x[States.ECEF_ORIENTATION, 0]) * delta_quat diff --git a/selfdrive/locationd/models/loc_kf.py b/selfdrive/locationd/models/loc_kf.py index adb9098d4d..199cc7e205 100755 --- a/selfdrive/locationd/models/loc_kf.py +++ b/selfdrive/locationd/models/loc_kf.py @@ -232,7 +232,7 @@ class LocKalman(): err_function_sym = sp.Matrix(np.zeros((dim_state, 1))) for q_idx, q_err_idx in zip(q_idxs, q_err_idxs): - delta_quat = sp.Matrix(np.ones((4))) + delta_quat = sp.Matrix(np.ones(4)) delta_quat[1:, :] = sp.Matrix(0.5 * delta_x[q_err_idx[0]: q_err_idx[1], :]) err_function_sym[q_idx[0]:q_idx[1], 0] = quat_matrix_r(nom_x[q_idx[0]:q_idx[1], 0]) * delta_quat for p_idx, p_err_idx in zip(p_idxs, p_err_idxs): diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 914e0d0223..1542f29d34 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -16,10 +16,12 @@ from selfdrive.swaglog import cloudlog MAX_ANGLE_OFFSET_DELTA = 20 * DT_MDL # Max 20 deg/s +ROLL_MAX_DELTA = np.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits +ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) class ParamsLearner: - def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset): - self.kf = CarKalman(GENERATED_DIR, steer_ratio, stiffness_factor, angle_offset) + def __init__(self, CP, steer_ratio, stiffness_factor, angle_offset, P_initial=None): + self.kf = CarKalman(GENERATED_DIR, steer_ratio, stiffness_factor, angle_offset, P_initial) self.kf.filter.set_global("mass", CP.mass) self.kf.filter.set_global("rotational_inertia", CP.rotationalInertia) @@ -30,9 +32,10 @@ class ParamsLearner: self.active = False - self.speed = 0 + self.speed = 0.0 + self.roll = 0.0 self.steering_pressed = False - self.steering_angle = 0 + self.steering_angle = 0.0 self.valid = True @@ -41,16 +44,34 @@ class ParamsLearner: yaw_rate = msg.angularVelocityCalibrated.value[2] yaw_rate_std = msg.angularVelocityCalibrated.std[2] + localizer_roll = msg.orientationNED.value[0] + roll_valid = msg.orientationNED.valid and ROLL_MIN < localizer_roll < ROLL_MAX + if roll_valid: + roll = localizer_roll + roll_std = np.radians(1.0) + else: + # This is done to bound the road roll estimate when localizer values are invalid + roll = 0.0 + roll_std = np.radians(10.0) + self.roll = clip(roll, self.roll - ROLL_MAX_DELTA, self.roll + ROLL_MAX_DELTA) + yaw_rate_valid = msg.angularVelocityCalibrated.valid yaw_rate_valid = yaw_rate_valid and 0 < yaw_rate_std < 10 # rad/s yaw_rate_valid = yaw_rate_valid and abs(yaw_rate) < 1 # rad/s if self.active: - if msg.inputsOK and msg.posenetOK and yaw_rate_valid: + if msg.inputsOK and msg.posenetOK: + + if yaw_rate_valid: + self.kf.predict_and_observe(t, + ObservationKind.ROAD_FRAME_YAW_RATE, + np.array([[-yaw_rate]]), + np.array([np.atleast_2d(yaw_rate_std**2)])) + self.kf.predict_and_observe(t, - ObservationKind.ROAD_FRAME_YAW_RATE, - np.array([[-yaw_rate]]), - np.array([np.atleast_2d(yaw_rate_std**2)])) + ObservationKind.ROAD_ROLL, + np.array([[self.roll]]), + np.array([np.atleast_2d(roll_std**2)])) self.kf.predict_and_observe(t, ObservationKind.ANGLE_OFFSET_FAST, np.array([[0]])) elif which == 'carState': @@ -152,6 +173,7 @@ def main(sm=None, pm=None): msg.liveParameters.sensorValid = True msg.liveParameters.steerRatio = float(x[States.STEER_RATIO]) msg.liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) + msg.liveParameters.roll = float(x[States.ROAD_ROLL]) msg.liveParameters.angleOffsetAverageDeg = angle_offset_average msg.liveParameters.angleOffsetDeg = angle_offset msg.liveParameters.valid = all(( diff --git a/selfdrive/locationd/test/ublox.py b/selfdrive/locationd/test/ublox.py index 04422a81cd..9cffbeac40 100644 --- a/selfdrive/locationd/test/ublox.py +++ b/selfdrive/locationd/test/ublox.py @@ -338,7 +338,7 @@ class UBloxDescriptor: ret += '%s, ' % v[a] ret = ret[:-2] + '], ' elif isinstance(v, str): - ret += '%s="%s", ' % (f, v.rstrip(' \0')) + ret += '{}="{}", '.format(f, v.rstrip(' \0')) else: ret += '%s=%s, ' % (f, v) for r in msg._recs: @@ -774,10 +774,9 @@ class UBlox: def read(self, n): '''read some bytes''' if self.use_sendrecv: - import socket try: return self.dev.recv(n) - except socket.error: + except OSError: return '' return self.dev.read(n) diff --git a/selfdrive/locationd/ubloxd.cc b/selfdrive/locationd/ubloxd.cc index bcf33b3f73..ae07284c8e 100644 --- a/selfdrive/locationd/ubloxd.cc +++ b/selfdrive/locationd/ubloxd.cc @@ -17,14 +17,14 @@ int main() { PubMaster pm({"ubloxGnss", "gpsLocationExternal"}); - Context * context = Context::create(); - SubSocket * subscriber = SubSocket::create(context, "ubloxRaw"); + std::unique_ptr context(Context::create()); + std::unique_ptr subscriber(SubSocket::create(context.get(), "ubloxRaw")); assert(subscriber != NULL); subscriber->setTimeout(100); while (!do_exit) { - Message * msg = subscriber->receive(); + std::unique_ptr msg(subscriber->receive()); if (!msg) { if (errno == EINTR) { do_exit = true; @@ -32,7 +32,7 @@ int main() { continue; } - capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg)); + capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); auto ubloxRaw = event.getUbloxRaw(); @@ -58,11 +58,7 @@ int main() { } bytes_consumed += bytes_consumed_this_time; } - delete msg; } - delete subscriber; - delete context; - return 0; } diff --git a/selfdrive/logcatd/logcatd_android.cc b/selfdrive/logcatd/logcatd_android.cc index 1a982a0feb..4452e2f093 100644 --- a/selfdrive/logcatd/logcatd_android.cc +++ b/selfdrive/logcatd/logcatd_android.cc @@ -14,8 +14,10 @@ int main() { ExitHandler do_exit; PubMaster pm({"androidLog"}); - log_time last_log_time = {}; - logger_list *logger_list = android_logger_list_alloc(ANDROID_LOG_RDONLY | ANDROID_LOG_NONBLOCK, 0, 0); + struct timespec cur_time; + clock_gettime(CLOCK_REALTIME, &cur_time); + log_time last_log_time(cur_time); + logger_list *logger_list = nullptr; while (!do_exit) { // setup android logging diff --git a/selfdrive/logcatd/logcatd_systemd.cc b/selfdrive/logcatd/logcatd_systemd.cc index 7c7800dfea..dabb11adcb 100644 --- a/selfdrive/logcatd/logcatd_systemd.cc +++ b/selfdrive/logcatd/logcatd_systemd.cc @@ -24,6 +24,10 @@ int main(int argc, char *argv[]) { err = sd_journal_seek_tail(journal); assert(err >= 0); + // workaround for bug https://github.com/systemd/systemd/issues/9934 + // call sd_journal_previous_skip after sd_journal_seek_tail (like journalctl -f does) to makes things work. + sd_journal_previous_skip(journal, 1); + while (!do_exit) { err = sd_journal_next(journal); assert(err >= 0); diff --git a/selfdrive/loggerd/bootlog.cc b/selfdrive/loggerd/bootlog.cc index 5209958377..478eb16852 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/selfdrive/loggerd/bootlog.cc @@ -11,6 +11,8 @@ static kj::Array build_boot_log() { if (Hardware::TICI()) { bootlog_commands.push_back("journalctl"); bootlog_commands.push_back("sudo nvme smart-log --output-format=json /dev/nvme0"); + } else if (Hardware::EON()) { + bootlog_commands.push_back("logcat -d"); } MessageBuilder msg; diff --git a/selfdrive/loggerd/deleter.py b/selfdrive/loggerd/deleter.py index 16083ec921..e7006a08cf 100644 --- a/selfdrive/loggerd/deleter.py +++ b/selfdrive/loggerd/deleter.py @@ -27,11 +27,11 @@ def deleter_thread(exit_event): continue try: - cloudlog.info("deleting %s" % delete_path) + cloudlog.info(f"deleting {delete_path}") shutil.rmtree(delete_path) break except OSError: - cloudlog.exception("issue deleting %s" % delete_path) + cloudlog.exception(f"issue deleting {delete_path}") exit_event.wait(.1) else: exit_event.wait(30) diff --git a/selfdrive/loggerd/loggerd.cc b/selfdrive/loggerd/loggerd.cc index 40d0a15efd..37f03ef4e5 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/selfdrive/loggerd/loggerd.cc @@ -38,7 +38,7 @@ bool trigger_rotate_if_needed(LoggerdState *s, int cur_seg, uint32_t frame_id) { } void encoder_thread(LoggerdState *s, const LogCameraInfo &cam_info) { - set_thread_name(cam_info.filename); + util::set_thread_name(cam_info.filename); int cur_seg = -1; int encode_idx = 0; @@ -185,15 +185,14 @@ void loggerd_thread() { } QlogState; std::unordered_map qlog_states; - LoggerdState s; - s.ctx = Context::create(); - Poller * poller = Poller::create(); + std::unique_ptr ctx(Context::create()); + std::unique_ptr poller(Poller::create()); // subscribe to all socks for (const auto& it : services) { if (!it.should_log) continue; - SubSocket * sock = SubSocket::create(s.ctx, it.name); + SubSocket * sock = SubSocket::create(ctx.get(), it.name); assert(sock != NULL); poller->registerSocket(sock); qlog_states[sock] = { @@ -203,6 +202,7 @@ void loggerd_thread() { }; } + LoggerdState s; // init logger logger_init(&s.logger, "rlog", true); logger_rotate(&s); @@ -266,6 +266,4 @@ void loggerd_thread() { // messaging cleanup for (auto &[sock, qs] : qlog_states) delete sock; - delete poller; - delete s.ctx; } diff --git a/selfdrive/loggerd/loggerd.h b/selfdrive/loggerd/loggerd.h index 0101a91a5e..bdf5ef8f9d 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/selfdrive/loggerd/loggerd.h @@ -108,7 +108,6 @@ const LogCameraInfo qcam_info = { }; struct LoggerdState { - Context *ctx; LoggerState logger = {}; char segment_path[4096]; std::mutex rotate_lock; diff --git a/selfdrive/loggerd/main.cc b/selfdrive/loggerd/main.cc index 04da0f67e8..7069aa7068 100644 --- a/selfdrive/loggerd/main.cc +++ b/selfdrive/loggerd/main.cc @@ -7,10 +7,10 @@ int main(int argc, char** argv) { setpriority(PRIO_PROCESS, 0, -20); } else if (Hardware::TICI()) { int ret; - ret = set_core_affinity({0, 1, 2, 3}); + ret = util::set_core_affinity({0, 1, 2, 3}); assert(ret == 0); // TODO: why does this impact camerad timings? - //ret = set_realtime_priority(1); + //ret = util::set_realtime_priority(1); //assert(ret == 0); } diff --git a/selfdrive/loggerd/raw_logger.cc b/selfdrive/loggerd/raw_logger.cc index c490a0813d..b0f9f7a9c8 100644 --- a/selfdrive/loggerd/raw_logger.cc +++ b/selfdrive/loggerd/raw_logger.cc @@ -127,7 +127,7 @@ int RawLogger::encode_frame(const uint8_t *y_ptr, const uint8_t *u_ptr, const ui frame->data[0] = (uint8_t*)y_ptr; frame->data[1] = (uint8_t*)u_ptr; frame->data[2] = (uint8_t*)v_ptr; - frame->pts = ts; + frame->pts = counter; int ret = counter; diff --git a/selfdrive/loggerd/tests/test_deleter.py b/selfdrive/loggerd/tests/test_deleter.py index 89904e694a..e0a063bd43 100644 --- a/selfdrive/loggerd/tests/test_deleter.py +++ b/selfdrive/loggerd/tests/test_deleter.py @@ -18,7 +18,7 @@ class TestDeleter(UploaderTestCase): def setUp(self): self.f_type = "fcamera.hevc" - super(TestDeleter, self).setUp() + super().setUp() self.fake_stats = Stats(f_bavail=0, f_blocks=10, f_frsize=4096) deleter.os.statvfs = self.fake_statvfs deleter.ROOT = self.root diff --git a/selfdrive/loggerd/tests/test_uploader.py b/selfdrive/loggerd/tests/test_uploader.py index 8562f5f21d..54c65db73d 100755 --- a/selfdrive/loggerd/tests/test_uploader.py +++ b/selfdrive/loggerd/tests/test_uploader.py @@ -39,7 +39,7 @@ cloudlog.addHandler(log_handler) class TestUploader(UploaderTestCase): def setUp(self): - super(TestUploader, self).setUp() + super().setUp() log_handler.reset() def start_thread(self): diff --git a/selfdrive/loggerd/tools/mark_unuploaded.py b/selfdrive/loggerd/tools/mark_unuploaded.py index e219c3a53a..0d3f765af4 100755 --- a/selfdrive/loggerd/tools/mark_unuploaded.py +++ b/selfdrive/loggerd/tools/mark_unuploaded.py @@ -4,5 +4,5 @@ from common.xattr import removexattr from selfdrive.loggerd.uploader import UPLOAD_ATTR_NAME for fn in sys.argv[1:]: - print("unmarking %s" % fn) + print(f"unmarking {fn}") removexattr(fn, UPLOAD_ATTR_NAME) diff --git a/selfdrive/loggerd/uploader.py b/selfdrive/loggerd/uploader.py index 10bf218b03..c75b34e9cb 100644 --- a/selfdrive/loggerd/uploader.py +++ b/selfdrive/loggerd/uploader.py @@ -140,7 +140,7 @@ class Uploader(): cloudlog.debug("upload_url v1.4 %s %s", url, str(headers)) if fake_upload: - cloudlog.debug("*** WARNING, THIS IS A FAKE UPLOAD TO %s ***" % url) + cloudlog.debug(f"*** WARNING, THIS IS A FAKE UPLOAD TO {url} ***") class FakeResponse(): def __init__(self): diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index 078f18fd29..4b97855f34 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -12,7 +12,7 @@ from common.spinner import Spinner from common.text_window import TextWindow from selfdrive.hardware import TICI from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import get_dirty +from selfdrive.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if TICI else "/tmp/scons_cache") @@ -22,7 +22,7 @@ MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) -def build(spinner, dirty=False): +def build(spinner: Spinner, dirty: bool = False) -> None: env = os.environ.copy() env['SCONS_PROGRESS'] = "1" nproc = os.cpu_count() @@ -30,6 +30,7 @@ def build(spinner, dirty=False): for retry in [True, False]: scons = subprocess.Popen(["scons", j_flag, "--cache-populate"], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) + assert scons.stderr is not None compile_output = [] @@ -98,4 +99,4 @@ def build(spinner, dirty=False): if __name__ == "__main__" and not PREBUILT: spinner = Spinner() spinner.update_progress(0, 100) - build(spinner, get_dirty()) + build(spinner, is_dirty()) diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index fdda0deb9a..b07362dd4d 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -5,7 +5,7 @@ import errno import signal -def unblock_stdout(): +def unblock_stdout() -> None: # get a non-blocking stdout child_pid, child_pty = os.forkpty() if child_pid != 0: # parent @@ -29,7 +29,7 @@ def unblock_stdout(): try: sys.stdout.write(dat.decode('utf8')) - except (OSError, IOError, UnicodeDecodeError): + except (OSError, UnicodeDecodeError): pass # os.wait() returns a tuple with the pid and a 16 bit value diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 386876b692..76106e473f 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -5,6 +5,7 @@ import signal import subprocess import sys import traceback +from typing import List, Tuple, Union import cereal.messaging as messaging import selfdrive.crash as crash @@ -18,14 +19,14 @@ from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from selfdrive.swaglog import cloudlog, add_file_handler -from selfdrive.version import get_dirty, get_commit, get_version, get_origin, get_short_branch, \ - terms_version, training_version, get_comma_remote +from selfdrive.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ + terms_version, training_version, is_comma_remote sys.path.append(os.path.join(BASEDIR, "pyextra")) -def manager_init(): +def manager_init() -> None: # update system time from panda set_time(cloudlog) @@ -35,7 +36,7 @@ def manager_init(): params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) - default_params = [ + default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), ("HasAcceptedTerms", "0"), ("OpenpilotEnabledToggle", "1"), @@ -56,7 +57,7 @@ def manager_init(): # is this dashcam? if os.getenv("PASSIVE") is not None: - params.put_bool("Passive", bool(int(os.getenv("PASSIVE")))) + params.put_bool("Passive", bool(int(os.getenv("PASSIVE", "0")))) if params.get("Passive") is None: raise Exception("Passive must be set to continue") @@ -86,25 +87,25 @@ def manager_init(): raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog - if not get_dirty(): + if not is_dirty(): os.environ['CLEAN'] = '1' - cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=get_dirty(), + cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty(), device=HARDWARE.get_device_type()) - if get_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): + if is_comma_remote() and not (os.getenv("NOLOG") or os.getenv("NOCRASH") or PC): crash.init() crash.bind_user(id=dongle_id) - crash.bind_extra(dirty=get_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), + crash.bind_extra(dirty=is_dirty(), origin=get_origin(), branch=get_short_branch(), commit=get_commit(), device=HARDWARE.get_device_type()) -def manager_prepare(): +def manager_prepare() -> None: for p in managed_processes.values(): p.prepare() -def manager_cleanup(): +def manager_cleanup() -> None: # send signals to kill all procs for p in managed_processes.values(): p.stop(block=False) @@ -116,7 +117,8 @@ def manager_cleanup(): cloudlog.info("everything is dead") -def manager_thread(): +def manager_thread() -> None: + cloudlog.bind(daemon="manager") cloudlog.info("manager start") cloudlog.info({"environ": os.environ}) @@ -127,8 +129,7 @@ def manager_thread(): ignore += ["manage_athenad", "uploader"] if os.getenv("NOBOARD") is not None: ignore.append("pandad") - if os.getenv("BLOCK") is not None: - ignore += os.getenv("BLOCK").split(",") + ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] ensure_running(managed_processes.values(), started=False, not_run=ignore) @@ -175,7 +176,7 @@ def manager_thread(): break -def main(): +def main() -> None: prepare_only = os.getenv("PREPAREONLY") is not None manager_init() diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index d9a1619411..a423b1ff84 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -4,6 +4,7 @@ import signal import struct import time import subprocess +from typing import Optional, List, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process @@ -22,7 +23,7 @@ WATCHDOG_FN = "/dev/shm/wd_" ENABLE_WATCHDOG = os.getenv("NO_WATCHDOG") is None -def launcher(proc): +def launcher(proc: str, name: str) -> None: try: # import the process mod = importlib.import_module(proc) @@ -33,10 +34,13 @@ def launcher(proc): # create new context since we forked messaging.context = messaging.Context() + # add daemon name to cloudlog ctx + cloudlog.bind(daemon=name) + # exec the process - mod.main() + getattr(mod, 'main')() except KeyboardInterrupt: - cloudlog.warning("child %s got SIGINT" % proc) + cloudlog.warning(f"child {proc} got SIGINT") except Exception: # can't install the crash handler because sys.excepthook doesn't play nice # with threads, so catch it here. @@ -44,13 +48,13 @@ def launcher(proc): raise -def nativelauncher(pargs, cwd): +def nativelauncher(pargs: List[str], cwd: str) -> None: # exec the process os.chdir(cwd) os.execvp(pargs[0], pargs) -def join_process(process, timeout): +def join_process(process: Process, timeout: float) -> None: # Process().join(timeout) will hang due to a python 3 bug: https://bugs.python.org/issue28382 # We have to poll the exitcode instead t = time.monotonic() @@ -62,7 +66,8 @@ class ManagerProcess(ABC): unkillable = False daemon = False sigkill = False - proc = None + persistent = False + proc: Optional[Process] = None enabled = True name = "" @@ -72,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 @@ -103,9 +109,9 @@ class ManagerProcess(ABC): else: self.watchdog_seen = True - def stop(self, retry=True, block=True): + def stop(self, retry: bool=True, block: bool=True) -> Optional[int]: if self.proc is None: - return + return None if self.proc.exitcode is None: if not self.shutting_down: @@ -115,7 +121,7 @@ class ManagerProcess(ABC): self.shutting_down = True if not block: - return + return None join_process(self.proc, 5) @@ -145,7 +151,7 @@ class ManagerProcess(ABC): return ret - def signal(self, sig): + def signal(self, sig: int) -> None: if self.proc is None: return @@ -153,6 +159,10 @@ class ManagerProcess(ABC): if self.proc.exitcode is not None and self.proc.pid is not None: return + # Can't signal if we don't have a pid + if self.proc.pid is None: + return + cloudlog.info(f"sending signal {sig} to {self.name}") os.kill(self.proc.pid, sig) @@ -179,10 +189,10 @@ class NativeProcess(ManagerProcess): self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt - def prepare(self): + def prepare(self) -> None: pass - def start(self): + def start(self) -> None: # In case we only tried a non blocking stop we need to stop it before restarting if self.shutting_down: self.stop() @@ -191,7 +201,7 @@ class NativeProcess(ManagerProcess): return cwd = os.path.join(BASEDIR, self.cwd) - cloudlog.info("starting process %s" % self.name) + cloudlog.info(f"starting process {self.name}") self.proc = Process(name=self.name, target=nativelauncher, args=(self.cmdline, cwd)) self.proc.start() self.watchdog_seen = False @@ -209,12 +219,12 @@ class PythonProcess(ManagerProcess): self.sigkill = sigkill self.watchdog_max_dt = watchdog_max_dt - def prepare(self): + def prepare(self) -> None: if self.enabled: - cloudlog.info("preimporting %s" % self.module) + cloudlog.info(f"preimporting {self.module}") importlib.import_module(self.module) - def start(self): + def start(self) -> None: # In case we only tried a non blocking stop we need to stop it before restarting if self.shutting_down: self.stop() @@ -222,8 +232,8 @@ class PythonProcess(ManagerProcess): if self.proc is not None: return - cloudlog.info("starting python %s" % self.module) - self.proc = Process(name=self.name, target=launcher, args=(self.module,)) + cloudlog.info(f"starting python {self.module}") + self.proc = Process(name=self.name, target=launcher, args=(self.module, self.name)) self.proc.start() self.watchdog_seen = False self.shutting_down = False @@ -239,10 +249,10 @@ class DaemonProcess(ManagerProcess): self.enabled = enabled self.persistent = True - def prepare(self): + def prepare(self) -> None: pass - def start(self): + def start(self) -> None: params = Params() pid = params.get(self.param_name, encoding='utf-8') @@ -257,20 +267,20 @@ class DaemonProcess(ManagerProcess): # process is dead pass - cloudlog.info("starting daemon %s" % self.name) + cloudlog.info(f"starting daemon {self.name}") proc = subprocess.Popen(['python', '-m', self.module], # pylint: disable=subprocess-popen-preexec-fn - stdin=open('/dev/null', 'r'), + stdin=open('/dev/null'), stdout=open('/dev/null', 'w'), stderr=open('/dev/null', 'w'), preexec_fn=os.setpgrp) params.put(self.param_name, str(proc.pid)) - def stop(self, retry=True, block=True): + def stop(self, retry=True, block=True) -> None: pass -def ensure_running(procs, started, driverview=False, not_run=None): +def ensure_running(procs: ValuesView[ManagerProcess], started: bool, driverview: bool=False, not_run: Optional[List[str]]=None) -> None: if not_run is None: not_run = [] @@ -281,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/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index 9013e63617..0facf3fa0b 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -14,64 +14,46 @@ #include "selfdrive/modeld/models/driving.h" ExitHandler do_exit; -// globals -bool live_calib_seen; -mat3 cur_transform; -std::mutex transform_lock; - -void calibration_thread(bool wide_camera) { - set_thread_name("calibration"); - set_realtime_priority(50); - - SubMaster sm({"liveCalibration"}); +mat3 update_calibration(cereal::LiveCalibrationData::Reader live_calib, bool wide_camera) { /* import numpy as np from common.transformations.model import medmodel_frame_from_road_frame medmodel_frame_from_ground = medmodel_frame_from_road_frame[:, (0, 1, 3)] ground_from_medmodel_frame = np.linalg.inv(medmodel_frame_from_ground) */ - Eigen::Matrix ground_from_medmodel_frame; - ground_from_medmodel_frame << + static const auto ground_from_medmodel_frame = (Eigen::Matrix() << 0.00000000e+00, 0.00000000e+00, 1.00000000e+00, -1.09890110e-03, 0.00000000e+00, 2.81318681e-01, - -1.84808520e-20, 9.00738606e-04,-4.28751576e-02; + -1.84808520e-20, 9.00738606e-04, -4.28751576e-02).finished(); - Eigen::Matrix cam_intrinsics = Eigen::Matrix(wide_camera ? ecam_intrinsic_matrix.v : fcam_intrinsic_matrix.v); - const mat3 yuv_transform = get_model_yuv_transform(); + static const auto cam_intrinsics = Eigen::Matrix(wide_camera ? ecam_intrinsic_matrix.v : fcam_intrinsic_matrix.v); + static const mat3 yuv_transform = get_model_yuv_transform(); - while (!do_exit) { - sm.update(100); - if(sm.updated("liveCalibration")) { - auto extrinsic_matrix = sm["liveCalibration"].getLiveCalibration().getExtrinsicMatrix(); - Eigen::Matrix extrinsic_matrix_eigen; - for (int i = 0; i < 4*3; i++) { - extrinsic_matrix_eigen(i / 4, i % 4) = extrinsic_matrix[i]; - } - - auto camera_frame_from_road_frame = cam_intrinsics * extrinsic_matrix_eigen; - Eigen::Matrix camera_frame_from_ground; - camera_frame_from_ground.col(0) = camera_frame_from_road_frame.col(0); - camera_frame_from_ground.col(1) = camera_frame_from_road_frame.col(1); - camera_frame_from_ground.col(2) = camera_frame_from_road_frame.col(3); - - auto warp_matrix = camera_frame_from_ground * ground_from_medmodel_frame; - mat3 transform = {}; - for (int i=0; i<3*3; i++) { - transform.v[i] = warp_matrix(i / 3, i % 3); - } - mat3 model_transform = matmul3(yuv_transform, transform); - std::lock_guard lk(transform_lock); - cur_transform = model_transform; - live_calib_seen = true; - } + auto extrinsic_matrix = live_calib.getExtrinsicMatrix(); + Eigen::Matrix extrinsic_matrix_eigen; + for (int i = 0; i < 4*3; i++) { + extrinsic_matrix_eigen(i / 4, i % 4) = extrinsic_matrix[i]; } + + auto camera_frame_from_road_frame = cam_intrinsics * extrinsic_matrix_eigen; + Eigen::Matrix camera_frame_from_ground; + camera_frame_from_ground.col(0) = camera_frame_from_road_frame.col(0); + camera_frame_from_ground.col(1) = camera_frame_from_road_frame.col(1); + camera_frame_from_ground.col(2) = camera_frame_from_road_frame.col(3); + + auto warp_matrix = camera_frame_from_ground * ground_from_medmodel_frame; + mat3 transform = {}; + for (int i=0; i<3*3; i++) { + transform.v[i] = warp_matrix(i / 3, i % 3); + } + return matmul3(yuv_transform, transform); } -void run_model(ModelState &model, VisionIpcClient &vipc_client) { +void run_model(ModelState &model, VisionIpcClient &vipc_client, bool wide_camera) { // messaging PubMaster pm({"modelV2", "cameraOdometry"}); - SubMaster sm({"lateralPlan", "roadCameraState"}); + SubMaster sm({"lateralPlan", "roadCameraState", "liveCalibration"}); // setup filter to track dropped frames FirstOrderFilter frame_dropped_filter(0., 10., 1. / MODEL_FREQ); @@ -80,70 +62,66 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client) { double last = 0; uint32_t run_count = 0; + mat3 model_transform = {}; + bool live_calib_seen = false; + while (!do_exit) { VisionIpcBufExtra extra = {}; VisionBuf *buf = vipc_client.recv(&extra); if (buf == nullptr) continue; - transform_lock.lock(); - mat3 model_transform = cur_transform; - const bool run_model_this_iter = live_calib_seen; - transform_lock.unlock(); - // TODO: path planner timeout? sm.update(0); int desire = ((int)sm["lateralPlan"].getLateralPlan().getDesire()); frame_id = sm["roadCameraState"].getRoadCameraState().getFrameId(); + if (sm.updated("liveCalibration")) { + model_transform = update_calibration(sm["liveCalibration"].getLiveCalibration(), wide_camera); + live_calib_seen = true; + } + + float vec_desire[DESIRE_LEN] = {0}; + if (desire >= 0 && desire < DESIRE_LEN) { + vec_desire[desire] = 1.0; + } - if (run_model_this_iter) { - run_count++; - - float vec_desire[DESIRE_LEN] = {0}; - if (desire >= 0 && desire < DESIRE_LEN) { - vec_desire[desire] = 1.0; - } - - double mt1 = millis_since_boot(); - ModelDataRaw model_buf = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, - model_transform, vec_desire); - double mt2 = millis_since_boot(); - float model_execution_time = (mt2 - mt1) / 1000.0; - - // tracked dropped frames - uint32_t vipc_dropped_frames = extra.frame_id - last_vipc_frame_id - 1; - float frames_dropped = frame_dropped_filter.update((float)std::min(vipc_dropped_frames, 10U)); - if (run_count < 10) { // let frame drops warm up - frame_dropped_filter.reset(0); - frames_dropped = 0.; - } - - float frame_drop_ratio = frames_dropped / (1 + frames_dropped); - - model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, model_buf, extra.timestamp_eof, model_execution_time, - kj::ArrayPtr(model.output.data(), model.output.size())); - posenet_publish(pm, extra.frame_id, vipc_dropped_frames, model_buf, extra.timestamp_eof); - - //printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); - last = mt1; - last_vipc_frame_id = extra.frame_id; + double mt1 = millis_since_boot(); + ModelOutput *model_output = model_eval_frame(&model, buf->buf_cl, buf->width, buf->height, + model_transform, vec_desire); + double mt2 = millis_since_boot(); + float model_execution_time = (mt2 - mt1) / 1000.0; + + // tracked dropped frames + uint32_t vipc_dropped_frames = extra.frame_id - last_vipc_frame_id - 1; + float frames_dropped = frame_dropped_filter.update((float)std::min(vipc_dropped_frames, 10U)); + if (run_count < 10) { // let frame drops warm up + frame_dropped_filter.reset(0); + frames_dropped = 0.; } + run_count++; + + float frame_drop_ratio = frames_dropped / (1 + frames_dropped); + + model_publish(pm, extra.frame_id, frame_id, frame_drop_ratio, *model_output, extra.timestamp_eof, model_execution_time, + kj::ArrayPtr(model.output.data(), model.output.size()), live_calib_seen); + posenet_publish(pm, extra.frame_id, vipc_dropped_frames, *model_output, extra.timestamp_eof, live_calib_seen); + + //printf("model process: %.2fms, from last %.2fms, vipc_frame_id %u, frame_id, %u, frame_drop %.3f\n", mt2 - mt1, mt1 - last, extra.frame_id, frame_id, frame_drop_ratio); + last = mt1; + last_vipc_frame_id = extra.frame_id; } } int main(int argc, char **argv) { if (!Hardware::PC()) { int ret; - ret = set_realtime_priority(54); + ret = util::set_realtime_priority(54); assert(ret == 0); - set_core_affinity({Hardware::EON() ? 2 : 7}); + util::set_core_affinity({Hardware::EON() ? 2 : 7}); assert(ret == 0); } bool wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; - // start calibration thread - std::thread thread = std::thread(calibration_thread, wide_camera); - // cl init cl_device_id device_id = cl_get_device_id(CL_DEVICE_TYPE_DEFAULT); cl_context context = CL_CHECK_ERR(clCreateContext(NULL, 1, &device_id, NULL, NULL, &err)); @@ -163,12 +141,10 @@ int main(int argc, char **argv) { if (vipc_client.connected) { const VisionBuf *b = &vipc_client.buffers[0]; LOGW("connected with buffer size: %d (%d x %d)", b->len, b->width, b->height); - run_model(model, vipc_client); + run_model(model, vipc_client, wide_camera); } model_free(&model); - LOG("joining calibration thread"); - thread.join(); CL_CHECK(clReleaseContext(context)); return 0; } diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index d7904489b1..19af75eef4 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -16,10 +16,6 @@ #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" -constexpr int MODEL_WIDTH = 512; -constexpr int MODEL_HEIGHT = 256; -constexpr int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; - const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); @@ -27,14 +23,17 @@ float softplus(float input); float sigmoid(float input); class ModelFrame { - public: +public: ModelFrame(cl_device_id device_id, cl_context context); ~ModelFrame(); float* prepare(cl_mem yuv_cl, int width, int height, const mat3& transform, cl_mem *output); + const int MODEL_WIDTH = 512; + const int MODEL_HEIGHT = 256; + const int MODEL_FRAME_SIZE = MODEL_WIDTH * MODEL_HEIGHT * 3 / 2; const int buf_size = MODEL_FRAME_SIZE * 2; - private: +private: Transform transform; LoadYUVState loadyuv; cl_command_queue q; diff --git a/selfdrive/modeld/models/dmonitoring.cc b/selfdrive/modeld/models/dmonitoring.cc index de07ffd38b..2acdf7d2b7 100644 --- a/selfdrive/modeld/models/dmonitoring.cc +++ b/selfdrive/modeld/models/dmonitoring.cc @@ -10,9 +10,8 @@ #include "selfdrive/modeld/models/dmonitoring.h" -#define MODEL_WIDTH 320 -#define MODEL_HEIGHT 640 -#define FULL_W 852 // should get these numbers from camerad +constexpr int MODEL_WIDTH = 320; +constexpr int MODEL_HEIGHT = 640; template static inline T *get_buffer(std::vector &buf, const size_t size) { diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index a2ff9e811f..69aeb91be1 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -16,8 +16,8 @@ constexpr float FCW_THRESHOLD_5MS2_HIGH = 0.15; constexpr float FCW_THRESHOLD_5MS2_LOW = 0.05; constexpr float FCW_THRESHOLD_3MS2 = 0.7; -float prev_brake_5ms2_probs[5] = {0,0,0,0,0}; -float prev_brake_3ms2_probs[3] = {0,0,0}; +std::array prev_brake_5ms2_probs = {0,0,0,0,0}; +std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV @@ -52,7 +52,7 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #endif } -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput* model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in) { #ifdef DESIRE if (desire_in != NULL) { @@ -69,35 +69,18 @@ ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int heigh } #endif - //for (int i = 0; i < NET_OUTPUT_SIZE; i++) { printf("%f ", s->output[i]); } printf("\n"); - // if getInputBuf is not NULL, net_input_buf will be auto net_input_buf = s->frame->prepare(yuv_cl, width, height, transform, static_cast(s->m->getInputBuf())); s->m->execute(net_input_buf, s->frame->buf_size); - // net outputs - ModelDataRaw net_outputs { - .plans = (ModelDataRawPlans*)&s->output[PLAN_IDX], - .lane_lines = (ModelDataRawLaneLines*)&s->output[LL_IDX], - .road_edges = (ModelDataRawRoadEdges*)&s->output[RE_IDX], - .leads = (ModelDataRawLeads*)&s->output[LEAD_IDX], - .meta = &s->output[DESIRE_STATE_IDX], - .pose = (ModelDataRawPose*)&s->output[POSE_IDX], - }; - return net_outputs; + return (ModelOutput*)&s->output; } void model_free(ModelState* s) { delete s->frame; } -void fill_sigmoid(const float *input, float *output, int len, int stride) { - for (int i=0; i lead_t = {0.0, 2.0, 4.0, 6.0, 8.0, 10.0}; const auto &best_prediction = leads.get_best_prediction(t_idx); lead.setProb(sigmoid(leads.prob[t_idx])); @@ -125,56 +108,54 @@ void fill_lead(cereal::ModelDataV2::LeadDataV3::Builder lead, const ModelDataRaw lead.setAStd(to_kj_array_ptr(lead_a_std)); } -void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const float *meta_data) { - float desire_state_softmax[DESIRE_LEN]; - float desire_pred_softmax[4*DESIRE_LEN]; - softmax(&meta_data[0], desire_state_softmax, DESIRE_LEN); - for (int i=0; i<4; i++) { - softmax(&meta_data[DESIRE_LEN + OTHER_META_SIZE + i*DESIRE_LEN], - &desire_pred_softmax[i*DESIRE_LEN], DESIRE_LEN); +void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMeta &meta_data) { + std::array desire_state_softmax; + softmax(meta_data.desire_state_prob.array.data(), desire_state_softmax.data(), DESIRE_LEN); + + std::array desire_pred_softmax; + for (int i=0; i lat_long_t = {2,4,6,8,10}; + std::array gas_disengage_sigmoid, brake_disengage_sigmoid, steer_override_sigmoid, + brake_3ms2_sigmoid, brake_4ms2_sigmoid, brake_5ms2_sigmoid; + for (int i=0; i threshold; } - for (int i=0; i<3; i++) { + for (int i=0; i FCW_THRESHOLD_3MS2; } auto disengage = meta.initDisengagePredictions(); - disengage.setT({2,4,6,8,10}); - disengage.setGasDisengageProbs(gas_disengage_sigmoid); - disengage.setBrakeDisengageProbs(brake_disengage_sigmoid); - disengage.setSteerOverrideProbs(steer_override_sigmoid); - disengage.setBrake3MetersPerSecondSquaredProbs(brake_3ms2_sigmoid); - disengage.setBrake4MetersPerSecondSquaredProbs(brake_4ms2_sigmoid); - disengage.setBrake5MetersPerSecondSquaredProbs(brake_5ms2_sigmoid); - - meta.setEngagedProb(sigmoid(meta_data[DESIRE_LEN])); - meta.setDesirePrediction(desire_pred_softmax); - meta.setDesireState(desire_state_softmax); + disengage.setT(to_kj_array_ptr(lat_long_t)); + disengage.setGasDisengageProbs(to_kj_array_ptr(gas_disengage_sigmoid)); + disengage.setBrakeDisengageProbs(to_kj_array_ptr(brake_disengage_sigmoid)); + disengage.setSteerOverrideProbs(to_kj_array_ptr(steer_override_sigmoid)); + disengage.setBrake3MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_3ms2_sigmoid)); + disengage.setBrake4MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_4ms2_sigmoid)); + disengage.setBrake5MetersPerSecondSquaredProbs(to_kj_array_ptr(brake_5ms2_sigmoid)); + + meta.setEngagedProb(sigmoid(meta_data.engaged_prob)); + meta.setDesirePrediction(to_kj_array_ptr(desire_pred_softmax)); + meta.setDesireState(to_kj_array_ptr(desire_state_softmax)); meta.setHardBrakePredicted(above_fcw_threshold); } @@ -197,7 +178,7 @@ void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array pos_x, pos_y, pos_z; std::array pos_x_std, pos_y_std, pos_z_std; std::array vel_x, vel_y, vel_z; @@ -229,7 +210,7 @@ void fill_plan(cereal::ModelDataV2::Builder &framed, const ModelDataRawPlanPredi } void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawLaneLines &lanes) { + const ModelOutputLaneLines &lanes) { std::array left_far_y, left_far_z; std::array left_near_y, left_near_z; std::array right_near_y, right_near_z; @@ -267,7 +248,7 @@ void fill_lane_lines(cereal::ModelDataV2::Builder &framed, const std::array &plan_t, - const ModelDataRawRoadEdges &edges) { + const ModelOutputRoadEdges &edges) { std::array left_y, left_z; std::array right_y, right_z; for (int j=0; jget_best_prediction(); +void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_outputs) { + const auto &best_plan = net_outputs.plans.get_best_prediction(); std::array plan_t; std::fill_n(plan_t.data(), plan_t.size(), NAN); plan_t[0] = 0.0; @@ -311,8 +292,8 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou } fill_plan(framed, best_plan); - fill_lane_lines(framed, plan_t, *net_outputs.lane_lines); - fill_road_edges(framed, plan_t, *net_outputs.road_edges); + fill_lane_lines(framed, plan_t, net_outputs.lane_lines); + fill_road_edges(framed, plan_t, net_outputs.road_edges); // meta fill_meta(framed.initMeta(), net_outputs.meta); @@ -321,16 +302,16 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelDataRaw &net_ou auto leads = framed.initLeadsV3(LEAD_MHP_SELECTION); std::array t_offsets = {0.0, 2.0, 4.0}; for (int i=0; i raw_pred) { + const ModelOutput &net_outputs, uint64_t timestamp_eof, + float model_execution_time, kj::ArrayPtr raw_pred, const bool valid) { const uint32_t frame_age = (frame_id > vipc_frame_id) ? (frame_id - vipc_frame_id) : 0; MessageBuilder msg; - auto framed = msg.initEvent().initModelV2(); + auto framed = msg.initEvent(valid).initModelV2(); framed.setFrameId(vipc_frame_id); framed.setFrameAge(frame_age); framed.setFrameDropPerc(frame_drop * 100); @@ -344,14 +325,14 @@ void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, flo } void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof) { + const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid) { MessageBuilder msg; - auto v_mean = net_outputs.pose->velocity_mean; - auto r_mean = net_outputs.pose->rotation_mean; - auto v_std = net_outputs.pose->velocity_std; - auto r_std = net_outputs.pose->rotation_std; + const auto &v_mean = net_outputs.pose.velocity_mean; + const auto &r_mean = net_outputs.pose.rotation_mean; + const auto &v_std = net_outputs.pose.velocity_std; + const auto &r_std = net_outputs.pose.rotation_std; - auto posenetd = msg.initEvent(vipc_dropped_frames < 1).initCameraOdometry(); + auto posenetd = msg.initEvent(valid && (vipc_dropped_frames < 1)).initCameraOdometry(); posenetd.setTrans({v_mean.x, v_mean.y, v_mean.z}); posenetd.setRot({r_mean.x, r_mean.y, r_mean.z}); posenetd.setTransStd({exp(v_std.x), exp(v_std.y), exp(v_std.z)}); diff --git a/selfdrive/modeld/models/driving.h b/selfdrive/modeld/models/driving.h index c6d3e00acc..14645dcfaa 100644 --- a/selfdrive/modeld/models/driving.h +++ b/selfdrive/modeld/models/driving.h @@ -16,78 +16,54 @@ #include "selfdrive/modeld/runners/run.h" constexpr int DESIRE_LEN = 8; +constexpr int DESIRE_PRED_LEN = 4; constexpr int TRAFFIC_CONVENTION_LEN = 2; constexpr int MODEL_FREQ = 20; -constexpr int DESIRE_PRED_SIZE = 32; -constexpr int OTHER_META_SIZE = 48; -constexpr int NUM_META_INTERVALS = 5; +constexpr int DISENGAGE_LEN = 5; +constexpr int BLINKER_LEN = 6; constexpr int META_STRIDE = 7; constexpr int PLAN_MHP_N = 5; -constexpr int PLAN_MHP_VALS = 15*33; -constexpr int PLAN_MHP_SELECTION = 1; -constexpr int PLAN_MHP_GROUP_SIZE = (2*PLAN_MHP_VALS + PLAN_MHP_SELECTION); constexpr int LEAD_MHP_N = 2; constexpr int LEAD_TRAJ_LEN = 6; constexpr int LEAD_PRED_DIM = 4; -constexpr int LEAD_MHP_VALS = LEAD_PRED_DIM*LEAD_TRAJ_LEN; constexpr int LEAD_MHP_SELECTION = 3; -constexpr int LEAD_MHP_GROUP_SIZE = (2*LEAD_MHP_VALS + LEAD_MHP_SELECTION); - -constexpr int POSE_SIZE = 12; - -constexpr int PLAN_IDX = 0; -constexpr int LL_IDX = PLAN_IDX + PLAN_MHP_N*PLAN_MHP_GROUP_SIZE; -constexpr int LL_PROB_IDX = LL_IDX + 4*2*2*33; -constexpr int RE_IDX = LL_PROB_IDX + 8; -constexpr int LEAD_IDX = RE_IDX + 2*2*2*33; -constexpr int LEAD_PROB_IDX = LEAD_IDX + LEAD_MHP_N*(LEAD_MHP_GROUP_SIZE); -constexpr int DESIRE_STATE_IDX = LEAD_PROB_IDX + 3; -constexpr int META_IDX = DESIRE_STATE_IDX + DESIRE_LEN; -constexpr int POSE_IDX = META_IDX + OTHER_META_SIZE + DESIRE_PRED_SIZE; -constexpr int OUTPUT_SIZE = POSE_IDX + POSE_SIZE; -#ifdef TEMPORAL - constexpr int TEMPORAL_SIZE = 512; -#else - constexpr int TEMPORAL_SIZE = 0; -#endif -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; -struct ModelDataRawXYZ { +struct ModelOutputXYZ { float x; float y; float z; }; -static_assert(sizeof(ModelDataRawXYZ) == sizeof(float)*3); +static_assert(sizeof(ModelOutputXYZ) == sizeof(float)*3); -struct ModelDataRawYZ { +struct ModelOutputYZ { float y; float z; }; -static_assert(sizeof(ModelDataRawYZ) == sizeof(float)*2); +static_assert(sizeof(ModelOutputYZ) == sizeof(float)*2); -struct ModelDataRawPlanElement { - ModelDataRawXYZ position; - ModelDataRawXYZ velocity; - ModelDataRawXYZ acceleration; - ModelDataRawXYZ rotation; - ModelDataRawXYZ rotation_rate; +struct ModelOutputPlanElement { + ModelOutputXYZ position; + ModelOutputXYZ velocity; + ModelOutputXYZ acceleration; + ModelOutputXYZ rotation; + ModelOutputXYZ rotation_rate; }; -static_assert(sizeof(ModelDataRawPlanElement) == sizeof(ModelDataRawXYZ)*5); +static_assert(sizeof(ModelOutputPlanElement) == sizeof(ModelOutputXYZ)*5); -struct ModelDataRawPlanPrediction { - std::array mean; - std::array std; +struct ModelOutputPlanPrediction { + std::array mean; + std::array std; float prob; }; -static_assert(sizeof(ModelDataRawPlanPrediction) == (sizeof(ModelDataRawPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); +static_assert(sizeof(ModelOutputPlanPrediction) == (sizeof(ModelOutputPlanElement)*TRAJECTORY_SIZE*2) + sizeof(float)); -struct ModelDataRawPlans { - std::array prediction; +struct ModelOutputPlans { + std::array prediction; - constexpr const ModelDataRawPlanPrediction &get_best_prediction() const { + constexpr const ModelOutputPlanPrediction &get_best_prediction() const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob > prediction[max_idx].prob) { @@ -97,69 +73,69 @@ struct ModelDataRawPlans { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawPlans) == sizeof(ModelDataRawPlanPrediction)*PLAN_MHP_N); +static_assert(sizeof(ModelOutputPlans) == sizeof(ModelOutputPlanPrediction)*PLAN_MHP_N); -struct ModelDataRawLinesXY { - std::array left_far; - std::array left_near; - std::array right_near; - std::array right_far; +struct ModelOutputLinesXY { + std::array left_far; + std::array left_near; + std::array right_near; + std::array right_far; }; -static_assert(sizeof(ModelDataRawLinesXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*4); +static_assert(sizeof(ModelOutputLinesXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*4); -struct ModelDataRawLineProbVal { +struct ModelOutputLineProbVal { float val_deprecated; float val; }; -static_assert(sizeof(ModelDataRawLineProbVal) == sizeof(float)*2); +static_assert(sizeof(ModelOutputLineProbVal) == sizeof(float)*2); -struct ModelDataRawLinesProb { - ModelDataRawLineProbVal left_far; - ModelDataRawLineProbVal left_near; - ModelDataRawLineProbVal right_near; - ModelDataRawLineProbVal right_far; +struct ModelOutputLinesProb { + ModelOutputLineProbVal left_far; + ModelOutputLineProbVal left_near; + ModelOutputLineProbVal right_near; + ModelOutputLineProbVal right_far; }; -static_assert(sizeof(ModelDataRawLinesProb) == sizeof(ModelDataRawLineProbVal)*4); +static_assert(sizeof(ModelOutputLinesProb) == sizeof(ModelOutputLineProbVal)*4); -struct ModelDataRawLaneLines { - ModelDataRawLinesXY mean; - ModelDataRawLinesXY std; - ModelDataRawLinesProb prob; +struct ModelOutputLaneLines { + ModelOutputLinesXY mean; + ModelOutputLinesXY std; + ModelOutputLinesProb prob; }; -static_assert(sizeof(ModelDataRawLaneLines) == (sizeof(ModelDataRawLinesXY)*2) + sizeof(ModelDataRawLinesProb)); +static_assert(sizeof(ModelOutputLaneLines) == (sizeof(ModelOutputLinesXY)*2) + sizeof(ModelOutputLinesProb)); -struct ModelDataRawEdgessXY { - std::array left; - std::array right; +struct ModelOutputEdgessXY { + std::array left; + std::array right; }; -static_assert(sizeof(ModelDataRawEdgessXY) == sizeof(ModelDataRawYZ)*TRAJECTORY_SIZE*2); +static_assert(sizeof(ModelOutputEdgessXY) == sizeof(ModelOutputYZ)*TRAJECTORY_SIZE*2); -struct ModelDataRawRoadEdges { - ModelDataRawEdgessXY mean; - ModelDataRawEdgessXY std; +struct ModelOutputRoadEdges { + ModelOutputEdgessXY mean; + ModelOutputEdgessXY std; }; -static_assert(sizeof(ModelDataRawRoadEdges) == (sizeof(ModelDataRawEdgessXY)*2)); +static_assert(sizeof(ModelOutputRoadEdges) == (sizeof(ModelOutputEdgessXY)*2)); -struct ModelDataRawLeadElement { +struct ModelOutputLeadElement { float x; float y; float velocity; float acceleration; }; -static_assert(sizeof(ModelDataRawLeadElement) == sizeof(float)*4); +static_assert(sizeof(ModelOutputLeadElement) == sizeof(float)*4); -struct ModelDataRawLeadPrediction { - std::array mean; - std::array std; +struct ModelOutputLeadPrediction { + std::array mean; + std::array std; std::array prob; }; -static_assert(sizeof(ModelDataRawLeadPrediction) == (sizeof(ModelDataRawLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); +static_assert(sizeof(ModelOutputLeadPrediction) == (sizeof(ModelOutputLeadElement)*LEAD_TRAJ_LEN*2) + (sizeof(float)*LEAD_MHP_SELECTION)); -struct ModelDataRawLeads { - std::array prediction; +struct ModelOutputLeads { + std::array prediction; std::array prob; - constexpr const ModelDataRawLeadPrediction &get_best_prediction(int t_idx) const { + constexpr const ModelOutputLeadPrediction &get_best_prediction(int t_idx) const { int max_idx = 0; for (int i = 1; i < prediction.size(); i++) { if (prediction[i].prob[t_idx] > prediction[max_idx].prob[t_idx]) { @@ -169,26 +145,77 @@ struct ModelDataRawLeads { return prediction[max_idx]; } }; -static_assert(sizeof(ModelDataRawLeads) == (sizeof(ModelDataRawLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); - -struct ModelDataRawPose { - ModelDataRawXYZ velocity_mean; - ModelDataRawXYZ rotation_mean; - ModelDataRawXYZ velocity_std; - ModelDataRawXYZ rotation_std; -}; -static_assert(sizeof(ModelDataRawPose) == sizeof(ModelDataRawXYZ)*4); - -struct ModelDataRaw { - const ModelDataRawPlans *const plans; - const ModelDataRawLaneLines *const lane_lines; - const ModelDataRawRoadEdges *const road_edges; - const ModelDataRawLeads *const leads; - const float *const desire_state; - const float *const meta; - const float *const desire_pred; - const ModelDataRawPose *const pose; -}; +static_assert(sizeof(ModelOutputLeads) == (sizeof(ModelOutputLeadPrediction)*LEAD_MHP_N) + (sizeof(float)*LEAD_MHP_SELECTION)); + +struct ModelOutputPose { + ModelOutputXYZ velocity_mean; + ModelOutputXYZ rotation_mean; + ModelOutputXYZ velocity_std; + ModelOutputXYZ rotation_std; +}; +static_assert(sizeof(ModelOutputPose) == sizeof(ModelOutputXYZ)*4); + +struct ModelOutputDisengageProb { + float gas_disengage; + float brake_disengage; + float steer_override; + float brake_3ms2; + float brake_4ms2; + float brake_5ms2; + float gas_pressed; +}; +static_assert(sizeof(ModelOutputDisengageProb) == sizeof(float)*7); + +struct ModelOutputBlinkerProb { + float left; + float right; +}; +static_assert(sizeof(ModelOutputBlinkerProb) == sizeof(float)*2); + +struct ModelOutputDesireProb { + union { + struct { + float none; + float turn_left; + float turn_right; + float lane_change_left; + float lane_change_right; + float keep_left; + float keep_right; + float null; + }; + struct { + std::array array; + }; + }; +}; +static_assert(sizeof(ModelOutputDesireProb) == sizeof(float)*DESIRE_LEN); + +struct ModelOutputMeta { + ModelOutputDesireProb desire_state_prob; + float engaged_prob; + std::array disengage_prob; + std::array blinker_prob; + std::array desire_pred_prob; +}; +static_assert(sizeof(ModelOutputMeta) == sizeof(ModelOutputDesireProb) + sizeof(float) + (sizeof(ModelOutputDisengageProb)*DISENGAGE_LEN) + (sizeof(ModelOutputBlinkerProb)*BLINKER_LEN) + (sizeof(ModelOutputDesireProb)*DESIRE_PRED_LEN)); + +struct ModelOutput { + const ModelOutputPlans plans; + const ModelOutputLaneLines lane_lines; + const ModelOutputRoadEdges road_edges; + const ModelOutputLeads leads; + const ModelOutputMeta meta; + const ModelOutputPose pose; +}; + +constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); +#ifdef TEMPORAL + constexpr int TEMPORAL_SIZE = 512; +#else + constexpr int TEMPORAL_SIZE = 0; +#endif +constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + TEMPORAL_SIZE; // TODO: convert remaining arrays to std::array and update model runners struct ModelState { @@ -205,12 +232,11 @@ struct ModelState { }; void model_init(ModelState* s, cl_device_id device_id, cl_context context); -ModelDataRaw model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, +ModelOutput *model_eval_frame(ModelState* s, cl_mem yuv_cl, int width, int height, const mat3 &transform, float *desire_in); void model_free(ModelState* s); -void poly_fit(float *in_pts, float *in_stds, float *out); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t frame_id, float frame_drop, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof, - float model_execution_time, kj::ArrayPtr raw_pred); + const ModelOutput &net_outputs, uint64_t timestamp_eof, + float model_execution_time, kj::ArrayPtr raw_pred, const bool valid); void posenet_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_dropped_frames, - const ModelDataRaw &net_outputs, uint64_t timestamp_eof); + const ModelOutput &net_outputs, uint64_t timestamp_eof, const bool valid); diff --git a/selfdrive/modeld/visiontest.py b/selfdrive/modeld/visiontest.py index 8bf71d26d5..1e30c05449 100644 --- a/selfdrive/modeld/visiontest.py +++ b/selfdrive/modeld/visiontest.py @@ -62,7 +62,7 @@ class VisionTest(): disable_model = 0 temporal_model = 1 else: - raise ValueError("Bad model name: {}".format(model)) + raise ValueError(f"Bad model name: {model}") prevdir = os.getcwd() os.chdir(_visiond_dir) # tmp hack to find kernels diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index f11d72266c..1728cc9e79 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -43,7 +43,7 @@ def dmonitoringd_thread(sm=None, pm=None): v_cruise_last = v_cruise if sm.updated['modelV2']: - driver_status.set_policy(sm['modelV2']) + driver_status.set_policy(sm['modelV2'], sm['carState'].vEgo) # Get data from dmonitoringmodeld events = Events() diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index e4018d66f6..c470612dc0 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -32,15 +32,21 @@ class DRIVER_MONITOR_SETTINGS(): self._BLINK_THRESHOLD = 0.82 if TICI else 0.588 self._BLINK_THRESHOLD_SLACK = 0.9 if TICI else 0.77 self._BLINK_THRESHOLD_STRICT = self._BLINK_THRESHOLD - self._PITCH_WEIGHT = 1.35 # pitch matters a lot more - self._POSESTD_THRESHOLD = 0.38 if TICI else 0.3 - self._METRIC_THRESHOLD = 0.48 - self._METRIC_THRESHOLD_SLACK = 0.66 - self._METRIC_THRESHOLD_STRICT = self._METRIC_THRESHOLD - self._PITCH_NATURAL_OFFSET = 0.02 # people don't seem to look straight when they drive relaxed, rather a bit up - self._YAW_NATURAL_OFFSET = 0.08 # people don't seem to look straight when they drive relaxed, rather a bit to the right (center of car) + self._POSE_PITCH_THRESHOLD = 0.3237 + self._POSE_PITCH_THRESHOLD_SLACK = 0.3657 + self._POSE_PITCH_THRESHOLD_STRICT = self._POSE_PITCH_THRESHOLD + self._POSE_YAW_THRESHOLD = 0.3109 + self._POSE_YAW_THRESHOLD_SLACK = 0.4294 + self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD + self._PITCH_NATURAL_OFFSET = 0.057 # initial value before offset is learned + self._YAW_NATURAL_OFFSET = 0.11 # initial value before offset is learned + self._PITCH_MAX_OFFSET = 0.124 + self._PITCH_MIN_OFFSET = -0.0881 + self._YAW_MAX_OFFSET = 0.289 + self._YAW_MIN_OFFSET = -0.0246 + self._POSESTD_THRESHOLD = 0.38 if TICI else 0.3 self._HI_STD_FALLBACK_TIME = int(10 / self._DT_DMON) # fall back to wheel touch if model is uncertain for 10s self._DISTRACTED_FILTER_TS = 0.25 # 0.6Hz @@ -93,7 +99,8 @@ class DriverPose(): self.pitch_offseter = RunningStatFilter(max_trackable=max_trackable) self.yaw_offseter = RunningStatFilter(max_trackable=max_trackable) self.low_std = True - self.cfactor = 1. + self.cfactor_pitch = 1. + self.cfactor_yaw = 1. class DriverBlink(): def __init__(self): @@ -164,30 +171,38 @@ class DriverStatus(): pitch_error = pose.pitch - self.settings._PITCH_NATURAL_OFFSET yaw_error = pose.yaw - self.settings._YAW_NATURAL_OFFSET else: - pitch_error = pose.pitch - self.pose.pitch_offseter.filtered_stat.mean() - yaw_error = pose.yaw - self.pose.yaw_offseter.filtered_stat.mean() + pitch_error = pose.pitch - min(max(self.pose.pitch_offseter.filtered_stat.mean(), + self.settings._PITCH_MIN_OFFSET), self.settings._PITCH_MAX_OFFSET) + yaw_error = pose.yaw - min(max(self.pose.yaw_offseter.filtered_stat.mean(), + self.settings._YAW_MIN_OFFSET), self.settings._YAW_MAX_OFFSET) pitch_error = 0 if pitch_error > 0 else abs(pitch_error) # no positive pitch limit yaw_error = abs(yaw_error) - if pitch_error*self.settings._PITCH_WEIGHT > self.settings._METRIC_THRESHOLD*pose.cfactor or \ - yaw_error > self.settings._METRIC_THRESHOLD*pose.cfactor: + if pitch_error > self.settings._POSE_PITCH_THRESHOLD*pose.cfactor_pitch or \ + yaw_error > self.settings._POSE_YAW_THRESHOLD*pose.cfactor_yaw: return DistractedType.BAD_POSE elif (blink.left_blink + blink.right_blink)*0.5 > self.settings._BLINK_THRESHOLD*blink.cfactor: return DistractedType.BAD_BLINK else: return DistractedType.NOT_DISTRACTED - def set_policy(self, model_data): - ep = min(model_data.meta.engagedProb, 0.8) / 0.8 - self.pose.cfactor = interp(ep, [0, 0.5, 1], - [self.settings._METRIC_THRESHOLD_STRICT, - self.settings. _METRIC_THRESHOLD, - self.settings._METRIC_THRESHOLD_SLACK]) / self.settings._METRIC_THRESHOLD + def set_policy(self, model_data, car_speed): + ep = min(model_data.meta.engagedProb, 0.8) / 0.8 # engaged prob + bp = model_data.meta.disengagePredictions.brakeDisengageProbs[0] # brake disengage prob in next 2s + # TODO: retune adaptive blink self.blink.cfactor = interp(ep, [0, 0.5, 1], [self.settings._BLINK_THRESHOLD_STRICT, self.settings._BLINK_THRESHOLD, self.settings._BLINK_THRESHOLD_SLACK]) / self.settings._BLINK_THRESHOLD + k1 = max(-0.00156*((car_speed-16)**2)+0.6, 0.2) + bp_normal = max(min(bp / k1, 0.5),0) + self.pose.cfactor_pitch = interp(bp_normal, [0, 0.5], + [self.settings._POSE_PITCH_THRESHOLD_SLACK, + self.settings._POSE_PITCH_THRESHOLD_STRICT]) / self.settings._POSE_PITCH_THRESHOLD + self.pose.cfactor_yaw = interp(bp_normal, [0, 0.5], + [self.settings._POSE_YAW_THRESHOLD_SLACK, + self.settings._POSE_YAW_THRESHOLD_STRICT]) / self.settings._POSE_YAW_THRESHOLD def get_pose(self, driver_state, cal_rpy, car_speed, op_engaged): if not all(len(x) > 0 for x in [driver_state.faceOrientation, driver_state.facePosition, diff --git a/selfdrive/pandad.py b/selfdrive/pandad.py index b725ca9dda..e3ebdad637 100755 --- a/selfdrive/pandad.py +++ b/selfdrive/pandad.py @@ -4,7 +4,7 @@ import os import usb1 import time import subprocess -from typing import List +from typing import NoReturn from functools import cmp_to_key from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU @@ -30,12 +30,7 @@ def flash_panda(panda_serial : str) -> Panda: panda_version = "bootstub" if panda.bootstub else panda.get_version() panda_signature = b"" if panda.bootstub else panda.get_signature() - cloudlog.warning("Panda %s connected, version: %s, signature %s, expected %s" % ( - panda_serial, - panda_version, - panda_signature.hex()[:16], - fw_signature.hex()[:16], - )) + cloudlog.warning(f"Panda {panda_serial} connected, version: {panda_version}, signature {panda_signature.hex()[:16]}, expected {fw_signature.hex()[:16]}") if panda.bootstub or panda_signature != fw_signature: cloudlog.info("Panda firmware out of date, update required") @@ -72,11 +67,11 @@ def panda_sort_cmp(a : Panda, b : Panda): # sort by hardware type if a_type != b_type: return a_type < b_type - + # last resort: sort by serial number return a.get_usb_serial() < b.get_usb_serial() -def main() -> None: +def main() -> NoReturn: while True: try: # Flash all Pandas in DFU mode diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index cefc2166ec..beb544b1b1 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -97,7 +97,7 @@ class Plant(): 'carState': car_state.carState, 'controlsState': control.controlsState} self.planner.update(sm) - self.speed = self.planner.v_desired + self.speed = self.planner.v_desired_filter.x self.acceleration = self.planner.a_desired fcw = self.planner.fcw self.distance_lead = self.distance_lead + v_lead * self.ts diff --git a/selfdrive/test/openpilotci.py b/selfdrive/test/openpilotci.py index 9cc0e37c4b..5bf43fb10a 100755 --- a/selfdrive/test/openpilotci.py +++ b/selfdrive/test/openpilotci.py @@ -9,7 +9,7 @@ TOKEN_PATH = "/data/azure_token" def get_url(route_name, segment_num, log_type="rlog"): ext = "hevc" if log_type in ["fcamera", "dcamera"] else "bz2" - return BASE_URL + "%s/%s/%s.%s" % (route_name.replace("|", "/"), segment_num, log_type, ext) + return BASE_URL + f"{route_name.replace('|', '/')}/{segment_num}/{log_type}.{ext}" def upload_file(path, name): from azure.storage.blob import BlockBlobService # pylint: disable=import-error diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index eb058b354c..72f5e51bb2 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -58,7 +58,7 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non if ignore_msgs is None: ignore_msgs = [] - log1, log2 = [list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)] + log1, log2 = (list(filter(lambda m: m.which() not in ignore_msgs, log)) for log in (log1, log2)) if len(log1) != len(log2): cnt1 = Counter(m.which() for m in log1) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index 32a5717852..6eb37dfa29 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -2,16 +2,16 @@ import os import sys import time -from typing import Any - +from collections import defaultdict from tqdm import tqdm +from typing import Any import cereal.messaging as messaging from cereal.visionipc.visionipc_pyx import VisionIpcServer, VisionStreamType # pylint: disable=no-name-in-module, import-error from common.spinner import Spinner from common.timeout import Timeout from common.transformations.camera import get_view_frame_from_road_frame, eon_f_frame_size, tici_f_frame_size, \ - eon_d_frame_size, tici_d_frame_size + eon_d_frame_size, tici_d_frame_size from selfdrive.hardware import PC, TICI from selfdrive.manager.process_config import managed_processes from selfdrive.test.openpilotci import BASE_URL, get_url @@ -25,13 +25,14 @@ if TICI: TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" else: TEST_ROUTE = "303055c0002aefd1|2021-11-22--18-36-32" +SEGMENT = 0 -CACHE_DIR = os.getenv("CACHE_DIR", None) +SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) -packet_from_camera = {"roadCameraState":"modelV2", "driverCameraState":"driverState"} def get_log_fn(ref_commit): - return "%s_%s_%s.bz2" % (TEST_ROUTE, "model_tici" if TICI else "model", ref_commit) + return f"{TEST_ROUTE}_{'model_tici' if TICI else 'model'}_{ref_commit}.bz2" + def replace_calib(msg, calib): msg = msg.as_builder() @@ -39,36 +40,8 @@ def replace_calib(msg, calib): msg.liveCalibration.extrinsicMatrix = get_view_frame_from_road_frame(*calib, 1.22).flatten().tolist() return msg -def process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire): - if msg.which() == "roadCameraState" and last_desire is not None: - dat = messaging.new_message('lateralPlan') - dat.lateralPlan.desire = last_desire - pm.send('lateralPlan', dat) - - f = msg.as_builder() - pm.send(msg.which(), f) - - img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="yuv420p")[0] - if msg.which == "roadCameraState": - vipc_server.send(VisionStreamType.VISION_STREAM_ROAD, img.flatten().tobytes(), f.roadCameraState.frameId, - f.roadCameraState.timestampSof, f.roadCameraState.timestampEof) - else: - vipc_server.send(VisionStreamType.VISION_STREAM_DRIVER, img.flatten().tobytes(), f.driverCameraState.frameId, - f.driverCameraState.timestampSof, f.driverCameraState.timestampEof) - with Timeout(seconds=15): - log_msgs.append(messaging.recv_one(sm.sock[packet_from_camera[msg.which()]])) - - frame_idxs[msg.which()] += 1 - if frame_idxs[msg.which()] >= frs[msg.which()].frame_count: - return None - update_spinner(spinner, frame_idxs['roadCameraState'], frs['roadCameraState'].frame_count, - frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count) - return 0 - -def update_spinner(s, fidx, fcnt, didx, dcnt): - s.update("replaying models: road %d/%d, driver %d/%d" % (fidx, fcnt, didx, dcnt)) - -def model_replay(lr_list, frs): + +def model_replay(lr, frs): spinner = Spinner() spinner.update("starting model replay") @@ -77,93 +50,110 @@ def model_replay(lr_list, frs): vipc_server.create_buffers(VisionStreamType.VISION_STREAM_DRIVER, 40, False, *(tici_d_frame_size if TICI else eon_d_frame_size)) vipc_server.start_listener() - pm = messaging.PubMaster(['roadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) sm = messaging.SubMaster(['modelV2', 'driverState']) + pm = messaging.PubMaster(['roadCameraState', 'driverCameraState', 'liveCalibration', 'lateralPlan']) try: managed_processes['modeld'].start() managed_processes['dmonitoringmodeld'].start() - time.sleep(5) + time.sleep(2) sm.update(1000) - last_desire = None log_msgs = [] - frame_idxs = dict.fromkeys(['roadCameraState','driverCameraState'], 0) - - cal = [msg for msg in lr if msg.which() == "liveCalibration"] - for msg in cal[:5]: - pm.send(msg.which(), replace_calib(msg, None)) - - for msg in tqdm(lr_list): - if msg.which() == "liveCalibration": - last_calib = list(msg.liveCalibration.rpyCalib) - pm.send(msg.which(), replace_calib(msg, last_calib)) - elif msg.which() == "lateralPlan": - last_desire = msg.lateralPlan.desire - elif msg.which() in ["roadCameraState", "driverCameraState"]: - ret = process_frame(msg, pm, sm, log_msgs, vipc_server, spinner, frs, frame_idxs, last_desire) - if ret is None: + last_desire = None + frame_idxs = defaultdict(lambda: 0) + + # init modeld with valid calibration + cal_msgs = [msg for msg in lr if msg.which() == "liveCalibration"] + for _ in range(5): + pm.send(cal_msgs[0].which(), cal_msgs[0].as_builder()) + time.sleep(0.1) + + for msg in tqdm(lr): + if SEND_EXTRA_INPUTS: + if msg.which() == "liveCalibration": + last_calib = list(msg.liveCalibration.rpyCalib) + pm.send(msg.which(), replace_calib(msg, last_calib)) + elif msg.which() == "lateralPlan": + last_desire = msg.lateralPlan.desire + dat = messaging.new_message('lateralPlan') + dat.lateralPlan.desire = last_desire + pm.send('lateralPlan', dat) + + if msg.which() in ["roadCameraState", "driverCameraState"]: + camera_state = getattr(msg, msg.which()) + stream = VisionStreamType.VISION_STREAM_ROAD if msg.which() == "roadCameraState" else VisionStreamType.VISION_STREAM_DRIVER + img = frs[msg.which()].get(frame_idxs[msg.which()], pix_fmt="yuv420p")[0] + + # send camera state and frame + pm.send(msg.which(), msg.as_builder()) + vipc_server.send(stream, img.flatten().tobytes(), camera_state.frameId, + camera_state.timestampSof, camera_state.timestampEof) + + # wait for a response + with Timeout(seconds=15): + packet_from_camera = {"roadCameraState": "modelV2", "driverCameraState": "driverState"} + log_msgs.append(messaging.recv_one(sm.sock[packet_from_camera[msg.which()]])) + + frame_idxs[msg.which()] += 1 + if frame_idxs[msg.which()] >= frs[msg.which()].frame_count: break - except KeyboardInterrupt: - pass + spinner.update("replaying models: road %d/%d, driver %d/%d" % (frame_idxs['roadCameraState'], + frs['roadCameraState'].frame_count, frame_idxs['driverCameraState'], frs['driverCameraState'].frame_count)) + finally: spinner.close() managed_processes['modeld'].stop() managed_processes['dmonitoringmodeld'].stop() + return log_msgs + if __name__ == "__main__": update = "--update" in sys.argv - - if TICI: - os.system('sudo mount -o remount,size=200M /tmp') # c3 hevcs are 75M each - replay_dir = os.path.dirname(os.path.abspath(__file__)) ref_commit_fn = os.path.join(replay_dir, "model_replay_ref_commit") - segnum = 0 - frs = {} - if CACHE_DIR: - lr = LogReader(os.path.join(CACHE_DIR, '%s--%d--rlog.bz2' % (TEST_ROUTE.replace('|', '_'), segnum))) - frs['roadCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--fcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) - frs['driverCameraState'] = FrameReader(os.path.join(CACHE_DIR, '%s--%d--dcamera.hevc' % (TEST_ROUTE.replace('|', '_'), segnum))) - else: - lr = LogReader(get_url(TEST_ROUTE, segnum)) - frs['roadCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="fcamera")) - frs['driverCameraState'] = FrameReader(get_url(TEST_ROUTE, segnum, log_type="dcamera")) + # load logs + lr = list(LogReader(get_url(TEST_ROUTE, SEGMENT))) + frs = { + 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="fcamera")), + 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="dcamera")), + } - lr_list = list(lr) - log_msgs = model_replay(lr_list, frs) + # run replay + log_msgs = model_replay(lr, frs) + # get diff failed = False if not update: - ref_commit = open(ref_commit_fn).read().strip() + with open(ref_commit_fn) as f: + ref_commit = f.read().strip() log_fn = get_log_fn(ref_commit) cmp_log = LogReader(BASE_URL + log_fn) - ignore = ['logMonoTime', 'valid', - 'modelV2.frameDropPerc', - 'modelV2.modelExecutionTime', - 'driverState.modelExecutionTime', - 'driverState.dspExecutionTime'] + ignore = [ + 'logMonoTime', + 'modelV2.frameDropPerc', + 'modelV2.modelExecutionTime', + 'driverState.modelExecutionTime', + 'driverState.dspExecutionTime' + ] tolerance = None if not PC else 1e-3 results: Any = {TEST_ROUTE: {}} results[TEST_ROUTE]["models"] = compare_logs(cmp_log, log_msgs, tolerance=tolerance, ignore_fields=ignore) diff1, diff2, failed = format_diff(results, ref_commit) print(diff2) - print('-------------') - print('-------------') - print('-------------') - print('-------------') - print('-------------') + print('-------------\n'*5) print(diff1) with open("model_diff.txt", "w") as f: f.write(diff2) + # upload new refs if update or failed: from selfdrive.test.openpilotci import upload_file diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 012c34c80c..c343275b6b 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 ccdbc2754f..8a13e4b18e 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -89,7 +89,7 @@ class DumbSocket: class FakeSubMaster(messaging.SubMaster): def __init__(self, services): - super(FakeSubMaster, self).__init__(services, addr=None) + super().__init__(services, addr=None) self.sock = {s: DumbSocket(s) for s in services} self.update_called = threading.Event() self.update_ready = threading.Event() @@ -111,7 +111,7 @@ class FakeSubMaster(messaging.SubMaster): def update_msgs(self, cur_time, msgs): wait_for_event(self.update_called) self.update_called.clear() - super(FakeSubMaster, self).update_msgs(cur_time, msgs) + super().update_msgs(cur_time, msgs) self.update_ready.set() def wait_for_update(self): diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index c162de2bb2..9ccca04cce 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -0ae46ae318a63476d8905aa0c32b0e587177868a \ No newline at end of file +67534ae58a87b6993a0a9653d255e629785a07c3 \ No newline at end of file diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 8fd1b0b421..6c33b46224 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -74,7 +74,7 @@ def test_process(cfg, lr, cmp_log_fn, ignore_fields=None, ignore_msgs=None): break else: segment = cmp_log_fn.split("/")[-1].split("_")[0] - raise Exception("Route never enabled: %s" % segment) + raise Exception(f"Route never enabled: {segment}") try: return compare_logs(cmp_log_msgs, log_msgs, ignore_fields+cfg.ignore, ignore_msgs, cfg.tolerance) @@ -83,30 +83,30 @@ def test_process(cfg, lr, cmp_log_fn, ignore_fields=None, ignore_msgs=None): def format_diff(results, ref_commit): diff1, diff2 = "", "" - diff2 += "***** tested against commit %s *****\n" % ref_commit + diff2 += f"***** tested against commit {ref_commit} *****\n" failed = False for segment, result in list(results.items()): - diff1 += "***** results for segment %s *****\n" % segment - diff2 += "***** differences for segment %s *****\n" % segment + diff1 += f"***** results for segment {segment} *****\n" + diff2 += f"***** differences for segment {segment} *****\n" for proc, diff in list(result.items()): - diff1 += "\t%s\n" % proc - diff2 += "*** process: %s ***\n" % proc + diff1 += f"\t{proc}\n" + diff2 += f"*** process: {proc} ***\n" if isinstance(diff, str): - diff1 += "\t\t%s\n" % diff + diff1 += f"\t\t{diff}\n" failed = True elif len(diff): cnt = {} for d in diff: - diff2 += "\t%s\n" % str(d) + diff2 += f"\t{str(d)}\n" k = str(d[1]) cnt[k] = 1 if k not in cnt else cnt[k] + 1 for k, v in sorted(cnt.items()): - diff1 += "\t\t%s: %s\n" % (k, v) + diff1 += f"\t\t{k}: {v}\n" failed = True return diff1, diff2, failed @@ -139,13 +139,13 @@ if __name__ == "__main__": print("couldn't find reference commit") sys.exit(1) - print("***** testing against commit %s *****" % ref_commit) + print(f"***** testing against commit {ref_commit} *****") # check to make sure all car brands are tested if FULL_TEST: - tested_cars = set(c.lower() for c, _ in segments) + tested_cars = {c.lower() for c, _ in segments} untested = (set(interface_names) - set(excluded_interfaces)) - tested_cars - assert len(untested) == 0, "Cars missing routes: %s" % (str(untested)) + assert len(untested) == 0, f"Cars missing routes: {str(untested)}" results: Any = {} for car_brand, segment in segments: @@ -153,7 +153,7 @@ if __name__ == "__main__": (not cars_whitelisted and car_brand.upper() in args.blacklist_cars): continue - print("***** testing route segment %s *****\n" % segment) + print(f"***** testing route segment {segment} *****\n") results[segment] = {} @@ -165,7 +165,7 @@ if __name__ == "__main__": (not procs_whitelisted and cfg.proc_name in args.blacklist_procs): continue - cmp_log_fn = os.path.join(process_replay_dir, "%s_%s_%s.bz2" % (segment, cfg.proc_name, ref_commit)) + cmp_log_fn = os.path.join(process_replay_dir, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") results[segment][cfg.proc_name] = test_process(cfg, lr, cmp_log_fn, args.ignore_fields, args.ignore_msgs) diff1, diff2, failed = format_diff(results, ref_commit) diff --git a/selfdrive/test/process_replay/update_refs.py b/selfdrive/test/process_replay/update_refs.py index 96feb0d534..0a3d95e714 100755 --- a/selfdrive/test/process_replay/update_refs.py +++ b/selfdrive/test/process_replay/update_refs.py @@ -28,7 +28,7 @@ if __name__ == "__main__": for cfg in CONFIGS: log_msgs = replay_process(cfg, lr) - log_fn = os.path.join(process_replay_dir, "%s_%s_%s.bz2" % (segment, cfg.proc_name, ref_commit)) + log_fn = os.path.join(process_replay_dir, f"{segment}_{cfg.proc_name}_{ref_commit}.bz2") save_log(log_fn, log_msgs) if not no_upload: diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 7a01e72122..5f73176fab 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -5,7 +5,6 @@ import cProfile # pylint: disable=import-error import pprofile # pylint: disable=import-error import pyprof2calltree # pylint: disable=import-error -from cereal import car from common.params import Params from tools.lib.logreader import LogReader from selfdrive.test.profiling.lib import SubMaster, PubMaster, SubSocket, ReplayDone @@ -35,9 +34,7 @@ def get_inputs(msgs, process, fingerprint): if msg.which() == 'carParams': m = msg.as_builder() m.carParams.carFingerprint = fingerprint - - CP = car.CarParams.from_dict(m.carParams.to_dict()) - Params().put("CarParams", CP.to_bytes()) + Params().put("CarParams", m.carParams.copy().to_bytes()) break sm = SubMaster(msgs, trigger, sub_socks) diff --git a/selfdrive/test/test_fingerprints.py b/selfdrive/test/test_fingerprints.py index 07e4774827..4ea60a76a8 100755 --- a/selfdrive/test/test_fingerprints.py +++ b/selfdrive/test/test_fingerprints.py @@ -18,8 +18,8 @@ def _get_fingerprints(): for car_folder in [x[0] for x in os.walk(BASEDIR + '/selfdrive/car')]: car_name = car_folder.split('/')[-1] try: - fingerprints[car_name] = __import__('selfdrive.car.%s.values' % car_name, fromlist=['FINGERPRINTS']).FINGERPRINTS - except (ImportError, IOError, AttributeError): + fingerprints[car_name] = __import__(f'selfdrive.car.{car_name}.values', fromlist=['FINGERPRINTS']).FINGERPRINTS + except (ImportError, OSError, AttributeError): pass return fingerprints @@ -80,14 +80,14 @@ if __name__ == "__main__": for idx2, f2 in enumerate(fingerprints_flat): if idx1 < idx2 and not check_fingerprint_consistency(f1, f2): valid = False - print("Those two fingerprints are inconsistent {0} {1}".format(car_names[idx1], car_names[idx2])) + print(f"Those two fingerprints are inconsistent {car_names[idx1]} {car_names[idx2]}") print("") print(', '.join("%d: %d" % v for v in sorted(f1.items()))) print("") print(', '.join("%d: %d" % v for v in sorted(f2.items()))) print("") - print("Found {0} individual fingerprints".format(len(fingerprints_flat))) + print(f"Found {len(fingerprints_flat)} individual fingerprints") if not valid or len(fingerprints_flat) == 0: print("TEST FAILED") sys.exit(1) diff --git a/selfdrive/test/test_models.py b/selfdrive/test/test_models.py index 5c277bfcb1..5112d30919 100755 --- a/selfdrive/test/test_models.py +++ b/selfdrive/test/test_models.py @@ -134,7 +134,7 @@ class TestCarModel(unittest.TestCase): def test_radar_interface(self): os.environ['NO_RADAR_SLEEP'] = "1" - RadarInterface = importlib.import_module('selfdrive.car.%s.radar_interface' % self.CP.carName).RadarInterface + RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface RI = RadarInterface(self.CP) assert RI diff --git a/selfdrive/test/test_routes.py b/selfdrive/test/test_routes.py index a59690e94b..f064b89f51 100755 --- a/selfdrive/test/test_routes.py +++ b/selfdrive/test/test_routes.py @@ -74,6 +74,7 @@ routes = [ TestRoute("38bfd238edecbcd7|2018-08-29--22-02-15", HYUNDAI.SANTA_FE), TestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), TestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), + TestRoute("656ac0d830792fcc|2021-12-28--14-45-56", HYUNDAI.SANTA_FE_PHEV_2022), TestRoute("e0e98335f3ebc58f|2021-03-07--16-38-29", HYUNDAI.KIA_CEED), TestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA), TestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), @@ -171,6 +172,7 @@ routes = [ TestRoute("3c8f0c502e119c1c|2020-06-30--12-58-02", SUBARU.ASCENT), TestRoute("c321c6b697c5a5ff|2020-06-23--11-04-33", SUBARU.FORESTER), TestRoute("791340bc01ed993d|2019-03-10--16-28-08", SUBARU.IMPREZA), + TestRoute("8bf7e79a3ce64055|2021-05-24--09-36-27", SUBARU.IMPREZA_2020), # Dashcam TestRoute("95441c38ae8c130e|2020-06-08--12-10-17", SUBARU.FORESTER_PREGLOBAL), # Dashcam diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 3e5a0d2cf0..3a392b4b36 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -25,7 +25,7 @@ def upload_route(path): "azcopy", "copy", f"{path}/*", - "https://{}.blob.core.windows.net/{}/{}?{}".format(_DATA_ACCOUNT_CI, "openpilotci", destpath, DEST_KEY), + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{destpath}?{DEST_KEY}", "--recursive=false", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", @@ -46,8 +46,8 @@ def sync_to_ci_public(route): cmd = [ "azcopy", "copy", - "https://{}.blob.core.windows.net/{}/{}?{}".format(source_account, source_bucket, key_prefix, source_key), - "https://{}.blob.core.windows.net/{}/{}?{}".format(_DATA_ACCOUNT_CI, "openpilotci", dongle_id, DEST_KEY), + f"https://{source_account}.blob.core.windows.net/{source_bucket}/{key_prefix}?{source_key}", + f"https://{_DATA_ACCOUNT_CI}.blob.core.windows.net/openpilotci/{dongle_id}?{DEST_KEY}", "--recursive=true", "--overwrite=false", "--exclude-pattern=*/dcamera.hevc", diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 9c07363917..8e24eb3cf1 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -2,6 +2,7 @@ import random import threading import time from statistics import mean +from typing import Optional from cereal import log from common.params import Params, put_nonblocking @@ -135,7 +136,7 @@ class PowerMonitoring: except Exception: cloudlog.exception("Power monitoring calculation failed") - def _perform_integration(self, t, current_power): + def _perform_integration(self, t: float, current_power: float) -> None: with self.integration_lock: try: if self.last_measurement_time: @@ -150,14 +151,14 @@ class PowerMonitoring: cloudlog.exception("Integration failed") # Get the power usage - def get_power_used(self): + def get_power_used(self) -> int: return int(self.power_used_uWh) - def get_car_battery_capacity(self): + def get_car_battery_capacity(self) -> int: return int(self.car_battery_capacity_uWh) # See if we need to disable charging - def should_disable_charging(self, ignition, in_car, offroad_timestamp): + def should_disable_charging(self, ignition: bool, in_car: bool, offroad_timestamp: Optional[float]) -> bool: if offroad_timestamp is None: return False diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 1b467f9627..4ddf37f418 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -3,7 +3,7 @@ import datetime import os import time from pathlib import Path -from typing import Dict, Optional, Tuple +from typing import Dict, NoReturn, Optional, Tuple from collections import namedtuple, OrderedDict import psutil @@ -81,7 +81,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,7 +152,7 @@ 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']) @@ -163,11 +163,11 @@ def thermald_thread(): 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 @@ -424,7 +424,7 @@ def thermald_thread(): count += 1 -def main(): +def main() -> NoReturn: thermald_thread() diff --git a/selfdrive/timezoned.py b/selfdrive/timezoned.py index 74da5fe35c..6a7e9122c1 100755 --- a/selfdrive/timezoned.py +++ b/selfdrive/timezoned.py @@ -3,6 +3,7 @@ import json import os import time import subprocess +from typing import NoReturn import requests from timezonefinder import TimezoneFinder @@ -30,7 +31,7 @@ def set_timezone(valid_timezones, timezone): cloudlog.exception(f"Error setting timezone to {timezone}") -def main(): +def main() -> NoReturn: params = Params() tf = TimezoneFinder() diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index a301725bae..df96c222c0 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -7,6 +7,7 @@ import signal import subprocess import time import glob +from typing import NoReturn import sentry_sdk @@ -15,7 +16,7 @@ from common.file_helpers import mkdirs_exists_ok from selfdrive.hardware import TICI, HARDWARE from selfdrive.loggerd.config import ROOT from selfdrive.swaglog import cloudlog -from selfdrive.version import get_branch, get_commit, get_dirty, get_origin, get_version +from selfdrive.version import get_branch, get_commit, is_dirty, get_origin, get_version MAX_SIZE = 100000 * 10 # mal size is 40-100k, allow up to 1M if TICI: @@ -197,7 +198,7 @@ def report_tombstone_apport(fn): pass -def main(): +def main() -> NoReturn: clear_apport_folder() # Clear apport folder on start, otherwise duplicate crashes won't register initial_tombstones = set(get_tombstones()) @@ -207,7 +208,7 @@ def main(): dongle_id = Params().get("DongleId", encoding='utf-8') sentry_sdk.set_user({"id": dongle_id}) - sentry_sdk.set_tag("dirty", get_dirty()) + sentry_sdk.set_tag("dirty", is_dirty()) sentry_sdk.set_tag("origin", get_origin()) sentry_sdk.set_tag("branch", get_branch()) sentry_sdk.set_tag("commit", get_commit()) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 5d656f889e..5f8a96edf0 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -1,6 +1,7 @@ moc_* *.moc +_mui watch3 installer/installers/* replay/replay diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 52e4e0c42c..62f93c13a9 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -73,6 +73,9 @@ if GetOption('extras'): # buidl updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) + # build mui + qt_env.Program("_mui", ["mui.cc"], LIBS=qt_libs) + # build installers senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') @@ -117,8 +120,7 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): replay_lib = qt_env.Library("qt_replay", replay_lib_src, LIBS=base_libs) replay_libs = [replay_lib, 'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs qt_env.Program("replay/replay", ["replay/main.cc"], LIBS=replay_libs) - - qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11']) + qt_env.Program("watch3", ["watch3.cc"], LIBS=qt_libs + ['common', 'json11', 'zmq', 'visionipc', 'messaging']) if GetOption('test'): qt_env.Program('replay/tests/test_replay', ['replay/tests/test_runner.cc', 'replay/tests/test_replay.cc'], LIBS=[replay_libs]) diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc new file mode 100644 index 0000000000..55b9a47474 --- /dev/null +++ b/selfdrive/ui/mui.cc @@ -0,0 +1,138 @@ +#include +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/qt_window.h" + +class StatusBar : public QGraphicsRectItem { + private: + QLinearGradient linear_gradient; + QRadialGradient radial_gradient; + QTimer animation_timer; + const int animation_length = 10; + int animation_index = 0; + + public: + StatusBar(double x, double y, double width, double height) : QGraphicsRectItem {x, y, width, height} { + linear_gradient = QLinearGradient(0, 0, 0, height/2); + linear_gradient.setSpread(QGradient::ReflectSpread); + + radial_gradient = QRadialGradient(width/2, height/2, width/8); + QObject::connect(&animation_timer, &QTimer::timeout, [=]() { + animation_index++; + animation_index %= animation_length; + }); + animation_timer.start(50); + } + + void solidColor(QColor color) { + QColor dark_color = QColor(color); + dark_color.setAlphaF(0.5); + + linear_gradient.setColorAt(0, dark_color); + linear_gradient.setColorAt(1, color); + setBrush(QBrush(linear_gradient)); + } + + // these need to be called continuously for the animations to work. + // can probably clean that up with some more abstractions + void blinkingColor(QColor color) { + QColor dark_color = QColor(color); + dark_color.setAlphaF(0.1); + + int radius = (rect().width() / animation_length) * animation_index; + QPoint center = QPoint(rect().width()/2, rect().height()/2); + radial_gradient.setCenter(center); + radial_gradient.setFocalPoint(center); + radial_gradient.setRadius(radius); + + radial_gradient.setColorAt(1, dark_color); + radial_gradient.setColorAt(0, color); + setBrush(QBrush(radial_gradient)); + } + + void laneChange(cereal::LateralPlan::LaneChangeDirection direction) { + QColor dark_color = QColor(bg_colors[STATUS_ENGAGED]); + dark_color.setAlphaF(0.1); + + int x = (rect().width() / animation_length) * animation_index; + QPoint center = QPoint(((direction == cereal::LateralPlan::LaneChangeDirection::RIGHT) ? x : (rect().width() - x)), rect().height()/2); + radial_gradient.setCenter(center); + radial_gradient.setFocalPoint(center); + radial_gradient.setRadius(rect().width()/5); + + radial_gradient.setColorAt(1, dark_color); + radial_gradient.setColorAt(0, bg_colors[STATUS_ENGAGED]); + setBrush(QBrush(radial_gradient)); + } + + void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = nullptr) override { + painter->setPen(QPen()); + painter->setBrush(brush()); + + double rounding_radius = rect().height()/2; + painter->drawRoundedRect(rect(), rounding_radius, rounding_radius); + } +}; + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QWidget w; + setMainWindow(&w); + + w.setStyleSheet("background-color: black;"); + + // our beautiful UI + QVBoxLayout *layout = new QVBoxLayout(&w); + + QGraphicsScene *scene = new QGraphicsScene(); + StatusBar *status_bar = new StatusBar(0, 0, 1000, 50); + scene->addItem(status_bar); + + QGraphicsView *graphics_view = new QGraphicsView(scene); + layout->insertSpacing(0, 400); + layout->addWidget(graphics_view, 0, Qt::AlignCenter); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [=]() { + static SubMaster sm({"deviceState", "controlsState", "lateralPlan"}); + + bool onroad_prev = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + sm.update(0); + + bool onroad = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + + if (onroad) { + auto cs = sm["controlsState"].getControlsState(); + UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::USER_PROMPT) { + status = STATUS_WARNING; + } else if (cs.getAlertStatus() == cereal::ControlsState::AlertStatus::CRITICAL) { + status = STATUS_ALERT; + } + + auto lp = sm["lateralPlan"].getLateralPlan(); + if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::PRE_LANE_CHANGE || status == STATUS_ALERT) { + status_bar->blinkingColor(bg_colors[status]); + } else if (lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_STARTING || + lp.getLaneChangeState() == cereal::LateralPlan::LaneChangeState::LANE_CHANGE_FINISHING) { + status_bar->laneChange(lp.getLaneChangeDirection()); + } else { + status_bar->solidColor(bg_colors[status]); + } + } + + if ((onroad != onroad_prev) || sm.frame < 2) { + Hardware::set_brightness(50); + Hardware::set_display_power(onroad); + } + }); + timer.start(50); + + return a.exec(); +} diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 1e89264952..df4f474943 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -19,7 +19,6 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { sidebar = new Sidebar(this); main_layout->addWidget(sidebar); - QObject::connect(this, &HomeWindow::update, sidebar, &Sidebar::updateState); QObject::connect(sidebar, &Sidebar::openSettings, this, &HomeWindow::openSettings); slayout = new QStackedLayout(); @@ -31,15 +30,13 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { onroad = new OnroadWindow(this); slayout->addWidget(onroad); - QObject::connect(this, &HomeWindow::update, onroad, &OnroadWindow::updateStateSignal); - QObject::connect(this, &HomeWindow::offroadTransitionSignal, onroad, &OnroadWindow::offroadTransitionSignal); - driver_view = new DriverViewWindow(this); connect(driver_view, &DriverViewWindow::done, [=] { showDriverView(false); }); slayout->addWidget(driver_view); setAttribute(Qt::WA_NoSystemBackground); + QObject::connect(uiState(), &UIState::offroadTransition, this, &HomeWindow::offroadTransition); } void HomeWindow::showSidebar(bool show) { @@ -53,7 +50,6 @@ void HomeWindow::offroadTransition(bool offroad) { } else { slayout->setCurrentWidget(onroad); } - emit offroadTransitionSignal(offroad); } void HomeWindow::showDriverView(bool show) { diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 0e65ef05e9..732071c153 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -43,10 +43,6 @@ signals: void openSettings(); void closeSettings(); - // forwarded signals - void update(const UIState &s); - void offroadTransitionSignal(bool offroad); - public slots: void offroadTransition(bool offroad); void showDriverView(bool show); diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index e1bfe96c8e..5e5fdb40cf 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -106,7 +106,7 @@ void MapWindow::initLayers() { } void MapWindow::timerUpdate() { - if (!QUIState::ui_state.scene.started) { + if (!uiState()->scene.started) { return; } @@ -387,7 +387,7 @@ void MapInstructions::updateDistance(float d) { d = std::max(d, 0.0f); QString distance_str; - if (QUIState::ui_state.scene.is_metric) { + if (uiState()->scene.is_metric) { if (d > 500) { distance_str.setNum(d / 1000, 'f', 1); distance_str += " km"; @@ -498,10 +498,9 @@ void MapInstructions::updateInstructions(cereal::NavInstruction::Reader instruct fn += "turn_straight"; } - QPixmap pix(fn + ICON_SUFFIX); auto icon = new QLabel; int wh = active ? 125 : 75; - icon->setPixmap(pix.scaled(wh, wh, Qt::IgnoreAspectRatio, Qt::SmoothTransformation)); + icon->setPixmap(loadPixmap(fn + ICON_SUFFIX, {wh, wh}, Qt::IgnoreAspectRatio)); icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); lane_layout->addWidget(icon); } @@ -620,7 +619,7 @@ void MapETA::updateETA(float s, float s_typical, float d) { // Distance QString distance_str; float num = 0; - if (QUIState::ui_state.scene.is_metric) { + if (uiState()->scene.is_metric) { num = d / 1000.0; distance_unit->setText("km"); } else { diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 6f03e2ed4a..3ab5b999b2 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -26,7 +26,7 @@ void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { } DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverState"}), QWidget(parent) { - face_img = QImage("../assets/img_driver_face.png").scaled(FACE_IMG_SIZE, FACE_IMG_SIZE, Qt::KeepAspectRatio, Qt::SmoothTransformation); + face_img = loadPixmap("../assets/img_driver_face.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } void DriverViewScene::showEvent(QShowEvent* event) { @@ -97,5 +97,5 @@ void DriverViewScene::paintEvent(QPaintEvent* event) { const int img_x = is_rhd ? rect2.right() - FACE_IMG_SIZE - img_offset : rect2.left() + img_offset; const int img_y = rect2.bottom() - FACE_IMG_SIZE - img_offset; p.setOpacity(face_detected ? 1.0 : 0.3); - p.drawImage(img_x, img_y, face_img); + p.drawPixmap(img_x, img_y, face_img); } diff --git a/selfdrive/ui/qt/offroad/driverview.h b/selfdrive/ui/qt/offroad/driverview.h index 8eab76a3dd..4d4a4358ef 100644 --- a/selfdrive/ui/qt/offroad/driverview.h +++ b/selfdrive/ui/qt/offroad/driverview.h @@ -24,7 +24,7 @@ protected: private: Params params; SubMaster sm; - QImage face_img; + QPixmap face_img; bool is_rhd = false; bool frame_updated = false; }; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index dd519dd661..2d5651dcce 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -158,7 +158,7 @@ AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWid list->addItem(roamingToggle); // APN settings - ButtonControl *editApnButton = new ButtonControl("APN settings", "EDIT"); + ButtonControl *editApnButton = new ButtonControl("APN Setting", "EDIT"); connect(editApnButton, &ButtonControl::clicked, [=]() { const bool roamingEnabled = params.getBool("GsmRoaming"); const QString cur_apn = QString::fromStdString(params.get("GsmApn")); diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index e6593f7679..458464239e 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -16,6 +16,11 @@ TrainingGuide::TrainingGuide(QWidget *parent) : QFrame(parent) { } void TrainingGuide::mouseReleaseEvent(QMouseEvent *e) { + if (click_timer.elapsed() < 250) { + return; + } + click_timer.restart(); + if (boundingRect[currentIndex].contains(e->x(), e->y())) { if (currentIndex == 9) { const QRect yes = QRect(692, 842, 492, 148); @@ -40,6 +45,7 @@ void TrainingGuide::showEvent(QShowEvent *event) { currentIndex = 0; image.load(img_path + "step0.png"); + click_timer.start(); } void TrainingGuide::paintEvent(QPaintEvent *event) { diff --git a/selfdrive/ui/qt/offroad/onboarding.h b/selfdrive/ui/qt/offroad/onboarding.h index 7ae72649d1..9424c07d19 100644 --- a/selfdrive/ui/qt/offroad/onboarding.h +++ b/selfdrive/ui/qt/offroad/onboarding.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -74,6 +75,7 @@ private: QString img_path; QVector boundingRect; + QElapsedTimer click_timer; signals: void completedTraining(); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 72bfd2eb80..83802a1d90 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -38,7 +38,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { { "IsLdwEnabled", "Enable Lane Departure Warnings", - "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31mph (50kph).", + "Receive alerts to steer back into the lane when your vehicle drifts over a detected lane line without a turn signal activated while driving over 31 mph (50 km/h).", "../assets/offroad/icon_warning.png", }, { @@ -98,7 +98,7 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { bool locked = params.getBool((param + "Lock").toStdString()); toggle->setEnabled(!locked); if (!locked) { - connect(parent, &SettingsWindow::offroadTransition, toggle, &ParamControl::setEnabled); + connect(uiState(), &UIState::offroadTransition, toggle, &ParamControl::setEnabled); } addItem(toggle); } @@ -144,7 +144,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { addItem(regulatoryBtn); } - QObject::connect(parent, &SettingsWindow::offroadTransition, [=](bool offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { btn->setEnabled(offroad); } @@ -187,8 +187,8 @@ void DevicePanel::updateCalibDescription() { double pitch = calib.getRpyCalib()[1] * (180 / M_PI); double yaw = calib.getRpyCalib()[2] * (180 / M_PI); desc += QString(" Your device is pointed %1° %2 and %3° %4.") - .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "up" : "down", - QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "right" : "left"); + .arg(QString::number(std::abs(pitch), 'g', 1), pitch > 0 ? "down" : "up", + QString::number(std::abs(yaw), 'g', 1), yaw > 0 ? "left" : "right"); } } catch (kj::Exception) { qInfo() << "invalid CalibrationParams"; @@ -198,10 +198,10 @@ void DevicePanel::updateCalibDescription() { } void DevicePanel::reboot() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { if (ConfirmationDialog::confirm("Are you sure you want to reboot?", this)) { // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { Params().putBool("DoReboot", true); } } @@ -211,10 +211,10 @@ void DevicePanel::reboot() { } void DevicePanel::poweroff() { - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { if (ConfirmationDialog::confirm("Are you sure you want to power off?", this)) { // Check engaged again in case it changed while the dialog was open - if (QUIState::ui_state.status == UIStatus::STATUS_DISENGAGED) { + if (uiState()->status == UIStatus::STATUS_DISENGAGED) { Params().putBool("DoShutdown", true); } } @@ -247,7 +247,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { params.putBool("DoUninstall", true); } }); - connect(parent, SIGNAL(offroadTransition(bool)), uninstallBtn, SLOT(setEnabled(bool))); + connect(uiState(), &UIState::offroadTransition, uninstallBtn, &QPushButton::setEnabled); QWidget *widgets[] = {versionLbl, lastUpdateLbl, updateBtn, gitBranchLbl, gitCommitLbl, osVersionLbl, uninstallBtn}; for (QWidget* w : widgets) { @@ -286,15 +286,14 @@ void SoftwarePanel::updateLabels() { osVersionLbl->setText(QString::fromStdString(Hardware::get_os_version()).trimmed()); } -QWidget * network_panel(QWidget * parent) { -#ifdef QCOM - QWidget *w = new QWidget(parent); - QVBoxLayout *layout = new QVBoxLayout(w); +C2NetworkPanel::C2NetworkPanel(QWidget *parent) : QWidget(parent) { + QVBoxLayout *layout = new QVBoxLayout(this); layout->setContentsMargins(50, 0, 50, 0); ListWidget *list = new ListWidget(); list->setSpacing(30); // wifi + tethering buttons +#ifdef QCOM auto wifiBtn = new ButtonControl("Wi-Fi Settings", "OPEN"); QObject::connect(wifiBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_wifi(); }); list->addItem(wifiBtn); @@ -302,17 +301,42 @@ QWidget * network_panel(QWidget * parent) { auto tetheringBtn = new ButtonControl("Tethering Settings", "OPEN"); QObject::connect(tetheringBtn, &ButtonControl::clicked, [=]() { HardwareEon::launch_tethering(); }); list->addItem(tetheringBtn); +#endif + ipaddress = new LabelControl("IP Address", ""); + list->addItem(ipaddress); // SSH key management list->addItem(new SshToggle()); list->addItem(new SshControl()); - layout->addWidget(list); layout->addStretch(1); +} + +void C2NetworkPanel::showEvent(QShowEvent *event) { + ipaddress->setText(getIPAddress()); +} + +QString C2NetworkPanel::getIPAddress() { + std::string result = util::check_output("ifconfig wlan0"); + if (result.empty()) return ""; + + const std::string inetaddrr = "inet addr:"; + std::string::size_type begin = result.find(inetaddrr); + if (begin == std::string::npos) return ""; + + begin += inetaddrr.length(); + std::string::size_type end = result.find(' ', begin); + if (end == std::string::npos) return ""; + + return result.substr(begin, end - begin).c_str(); +} + +QWidget *network_panel(QWidget *parent) { +#ifdef QCOM + return new C2NetworkPanel(parent); #else - Networking *w = new Networking(parent); + return new Networking(parent); #endif - return w; } void SettingsWindow::showEvent(QShowEvent *event) { diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 7fc5a8581f..c3acf3d94a 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -24,7 +24,6 @@ protected: signals: void closeSettings(); - void offroadTransition(bool offroad); void reviewTrainingGuide(); void showDriverView(); @@ -77,3 +76,14 @@ private: Params params; QFileSystemWatcher *fs_watch; }; + +class C2NetworkPanel: public QWidget { + Q_OBJECT +public: + explicit C2NetworkPanel(QWidget* parent = nullptr); + +private: + void showEvent(QShowEvent *event) override; + QString getIPAddress(); + LabelControl *ipaddress; +}; diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 53cc963674..e394d1248a 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -41,8 +41,8 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { alerts->raise(); setAttribute(Qt::WA_OpaquePaintEvent); - QObject::connect(this, &OnroadWindow::updateStateSignal, this, &OnroadWindow::updateState); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, this, &OnroadWindow::offroadTransition); + QObject::connect(uiState(), &UIState::uiUpdate, this, &OnroadWindow::updateState); + QObject::connect(uiState(), &UIState::offroadTransition, this, &OnroadWindow::offroadTransition); } void OnroadWindow::updateState(const UIState &s) { @@ -51,6 +51,8 @@ void OnroadWindow::updateState(const UIState &s) { if (s.sm->updated("controlsState") || !alert.equal({})) { if (alert.type == "controlsUnresponsive") { bgColor = bg_colors[STATUS_ALERT]; + } else if (alert.type == "controlsUnresponsivePermanent") { + bgColor = bg_colors[STATUS_DISENGAGED]; } alerts->updateAlert(alert, bgColor); } @@ -76,10 +78,11 @@ void OnroadWindow::mousePressEvent(QMouseEvent* e) { void OnroadWindow::offroadTransition(bool offroad) { #ifdef ENABLE_MAPS if (!offroad) { - if (map == nullptr && (QUIState::ui_state.has_prime || !MAPBOX_TOKEN.isEmpty())) { + if (map == nullptr && (uiState()->has_prime || !MAPBOX_TOKEN.isEmpty())) { MapWindow * m = new MapWindow(get_mapbox_settings()); m->setFixedWidth(topWidget(this)->width() / 2); - QObject::connect(this, &OnroadWindow::offroadTransitionSignal, m, &MapWindow::offroadTransition); + m->offroadTransition(offroad); + QObject::connect(uiState(), &UIState::offroadTransition, m, &MapWindow::offroadTransition); split->addWidget(m, 0, Qt::AlignRight); map = m; } @@ -162,8 +165,8 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { // OnroadHud OnroadHud::OnroadHud(QWidget *parent) : QWidget(parent) { - engage_img = QPixmap("../assets/img_chffr_wheel.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - dm_img = QPixmap("../assets/img_driver_face.png").scaled(img_size, img_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); connect(this, &OnroadHud::valueChanged, [=] { update(); }); } @@ -274,7 +277,7 @@ void NvgWindow::initializeGL() { void NvgWindow::updateFrameMat(int w, int h) { CameraViewWidget::updateFrameMat(w, h); - UIState *s = &QUIState::ui_state; + UIState *s = uiState(); s->fb_w = w; s->fb_h = h; auto intrinsic_matrix = s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; @@ -348,8 +351,8 @@ void NvgWindow::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV void NvgWindow::paintGL() { CameraViewWidget::paintGL(); - UIState *s = &QUIState::ui_state; - if (s->scene.world_objects_visible) { + UIState *s = uiState(); + if (s->worldObjectsVisible()) { QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); @@ -379,6 +382,6 @@ void NvgWindow::paintGL() { void NvgWindow::showEvent(QShowEvent *event) { CameraViewWidget::showEvent(event); - ui_update_params(&QUIState::ui_state); + ui_update_params(uiState()); prev_draw_t = millis_since_boot(); } diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 37460c8ba6..05811c5060 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -97,10 +97,6 @@ private: QWidget *map = nullptr; QHBoxLayout* split; -signals: - void updateStateSignal(const UIState &s); - void offroadTransitionSignal(bool offroad); - private slots: void offroadTransition(bool offroad); void updateState(const UIState &s); diff --git a/selfdrive/ui/qt/python_helpers.py b/selfdrive/ui/qt/python_helpers.py index 4dd99951bf..905d41a634 100644 --- a/selfdrive/ui/qt/python_helpers.py +++ b/selfdrive/ui/qt/python_helpers.py @@ -1,4 +1,3 @@ - import os from cffi import FFI diff --git a/selfdrive/ui/qt/request_repeater.cc b/selfdrive/ui/qt/request_repeater.cc index d2c0f9bc30..7ef1c833ab 100644 --- a/selfdrive/ui/qt/request_repeater.cc +++ b/selfdrive/ui/qt/request_repeater.cc @@ -5,7 +5,7 @@ RequestRepeater::RequestRepeater(QObject *parent, const QString &requestURL, con timer = new QTimer(this); timer->setTimerType(Qt::VeryCoarseTimer); QObject::connect(timer, &QTimer::timeout, [=]() { - if ((!QUIState::ui_state.scene.started || while_onroad) && QUIState::ui_state.awake && !active()) { + if ((!uiState()->scene.started || while_onroad) && uiState()->awake && !active()) { sendRequest(requestURL); } }); diff --git a/selfdrive/ui/qt/sidebar.cc b/selfdrive/ui/qt/sidebar.cc index 73c4e2f8ce..cdff1fce86 100644 --- a/selfdrive/ui/qt/sidebar.cc +++ b/selfdrive/ui/qt/sidebar.cc @@ -26,14 +26,16 @@ void Sidebar::drawMetric(QPainter &p, const QString &label, QColor c, int y) { } Sidebar::Sidebar(QWidget *parent) : QFrame(parent) { - home_img = QImage("../assets/images/button_home.png").scaled(180, 180, Qt::KeepAspectRatio, Qt::SmoothTransformation); - settings_img = QImage("../assets/images/button_settings.png").scaled(settings_btn.width(), settings_btn.height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); + home_img = loadPixmap("../assets/images/button_home.png", {180, 180}); + settings_img = loadPixmap("../assets/images/button_settings.png", settings_btn.size(), Qt::IgnoreAspectRatio); connect(this, &Sidebar::valueChanged, [=] { update(); }); setAttribute(Qt::WA_OpaquePaintEvent); setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); setFixedWidth(300); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &Sidebar::updateState); } void Sidebar::mouseReleaseEvent(QMouseEvent *event) { @@ -88,9 +90,9 @@ void Sidebar::paintEvent(QPaintEvent *event) { // static imgs p.setOpacity(0.65); - p.drawImage(settings_btn.x(), settings_btn.y(), settings_img); + p.drawPixmap(settings_btn.x(), settings_btn.y(), settings_img); p.setOpacity(1.0); - p.drawImage(60, 1080 - 180 - 40, home_img); + p.drawPixmap(60, 1080 - 180 - 40, home_img); // network int x = 58; diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 6cea9bdb6d..5d1b921750 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -32,7 +32,7 @@ protected: void mouseReleaseEvent(QMouseEvent *event) override; void drawMetric(QPainter &p, const QString &label, QColor c, int y); - QImage home_img, settings_img; + QPixmap home_img, settings_img; const QMap network_type = { {cereal::DeviceState::NetworkType::NONE, "--"}, {cereal::DeviceState::NetworkType::WIFI, "Wi-Fi"}, diff --git a/selfdrive/ui/qt/spinner.cc b/selfdrive/ui/qt/spinner.cc index 8948d2adb7..b9868f6035 100644 --- a/selfdrive/ui/qt/spinner.cc +++ b/selfdrive/ui/qt/spinner.cc @@ -19,8 +19,8 @@ TrackWidget::TrackWidget(QWidget *parent) : QWidget(parent) { setFixedSize(spinner_size); // pre-compute all the track imgs. make this a gif instead? - QPixmap comma_img = QPixmap("../assets/img_spinner_comma.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); - QPixmap track_img = QPixmap("../assets/img_spinner_track.png").scaled(spinner_size, Qt::KeepAspectRatio, Qt::SmoothTransformation); + QPixmap comma_img = loadPixmap("../assets/img_spinner_comma.png", spinner_size); + QPixmap track_img = loadPixmap("../assets/img_spinner_track.png", spinner_size); QTransform transform(1, 0, 0, 1, width() / 2, height() / 2); QPixmap pm(spinner_size); diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index b1defb0081..16d5c174b5 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -98,21 +98,6 @@ void initApp() { } } -ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } - -void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { - emit clicked(); -} - -// Fix stylesheets -void ClickableWidget::paintEvent(QPaintEvent *) { - QStyleOption opt; - opt.init(this); - QPainter p(this); - style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); -} - - void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { static std::map levels = { {QtMsgType::QtDebugMsg, CLOUDLOG_DEBUG}, @@ -136,3 +121,11 @@ QWidget* topWidget (QWidget* widget) { while (widget->parentWidget() != nullptr) widget=widget->parentWidget(); return widget; } + +QPixmap loadPixmap(const QString &fileName, const QSize &size, Qt::AspectRatioMode aspectRatioMode) { + if (size.isEmpty()) { + return QPixmap(fileName); + } else { + return QPixmap(fileName).scaled(size, aspectRatioMode, Qt::SmoothTransformation); + } +} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 588dac704f..1b9461fabf 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -4,8 +4,8 @@ #include #include -#include #include +#include #include #include @@ -21,28 +21,4 @@ QString timeAgo(const QDateTime &date); void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); void initApp(); QWidget* topWidget (QWidget* widget); - - -// convenience class for wrapping layouts -class LayoutWidget : public QWidget { - Q_OBJECT - -public: - LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { - setLayout(l); - }; -}; - -class ClickableWidget : public QWidget { - Q_OBJECT - -public: - ClickableWidget(QWidget *parent = nullptr); - -protected: - void mouseReleaseEvent(QMouseEvent *event) override; - void paintEvent(QPaintEvent *) override; - -signals: - void clicked(); -}; +QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index a238f13c85..51553bcc8f 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" @@ -199,13 +199,18 @@ void CameraViewWidget::updateFrameMat(int w, int h) { } void CameraViewWidget::paintGL() { - if (latest_texture_id == -1) { - glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); - glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); - return; - } + glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); + glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + + std::lock_guard lk(lock); + + if (latest_texture_id == -1) return; glViewport(0, 0, width(), height()); + // sync with the PBO + if (wait_fence) { + wait_fence->wait(); + } glBindVertexArray(frame_vao); glActiveTexture(GL_TEXTURE0); @@ -290,20 +295,27 @@ 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); + memcpy(texture_buffer, buf->addr, buf->len); + gl_buffer->unmap(); + + // copy pixels from PBO to texture object + glBindTexture(GL_TEXTURE_2D, texture[buf->idx]->frame_tex); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, buf->width, buf->height, GL_RGB, GL_UNSIGNED_BYTE, 0); + glBindTexture(GL_TEXTURE_2D, 0); + assert(glGetError() == GL_NO_ERROR); + + wait_fence.reset(new WaitFence()); + + // Ensure the fence is in the GPU command queue, or waiting on it might block + // https://www.khronos.org/opengl/wiki/Sync_Object#Flushing_and_contexts + glFlush(); + } + latest_texture_id = buf->idx; } - latest_texture_id = buf->idx; // Schedule update. update() will be invoked on the gui thread. QMetaObject::invokeMethod(this, "update"); diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 4cfba3c6fb..03709cbdd8 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -36,11 +36,20 @@ protected: virtual void updateFrameMat(int w, int h); void vipcThread(); + struct WaitFence { + WaitFence() { sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } + ~WaitFence() { glDeleteSync(sync); } + void wait() { glWaitSync(sync, 0, GL_TIMEOUT_IGNORED); } + GLsync sync = 0; + }; + bool zoomed_view; - std::atomic latest_texture_id = -1; + std::mutex lock; + int latest_texture_id = -1; GLuint frame_vao, frame_vbo, frame_ibo; mat4 frame_mat; std::unique_ptr texture[UI_BUF_COUNT]; + std::unique_ptr wait_fence; std::unique_ptr program; QColor bg = QColor("#000000"); @@ -50,7 +59,6 @@ protected: std::atomic stream_type; QThread *vipc_thread = nullptr; - protected slots: void vipcConnected(VisionIpcClient *vipc_client); }; diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 8b09ba2bb3..d4e48fc64f 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -121,3 +121,17 @@ void ElidedLabel::paintEvent(QPaintEvent *event) { opt.initFrom(this); style()->drawItemText(&painter, contentsRect(), alignment(), opt.palette, isEnabled(), elidedText_, foregroundRole()); } + +ClickableWidget::ClickableWidget(QWidget *parent) : QWidget(parent) { } + +void ClickableWidget::mouseReleaseEvent(QMouseEvent *event) { + emit clicked(); +} + +// Fix stylesheets +void ClickableWidget::paintEvent(QPaintEvent *) { + QStyleOption opt; + opt.init(this); + QPainter p(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); +} diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index b793326056..13cd55ff3b 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -161,3 +161,27 @@ private: QVBoxLayout outer_layout; QVBoxLayout inner_layout; }; + +// convenience class for wrapping layouts +class LayoutWidget : public QWidget { + Q_OBJECT + +public: + LayoutWidget(QLayout *l, QWidget *parent = nullptr) : QWidget(parent) { + setLayout(l); + }; +}; + +class ClickableWidget : public QWidget { + Q_OBJECT + +public: + ClickableWidget(QWidget *parent = nullptr); + +protected: + void mouseReleaseEvent(QMouseEvent *event) override; + void paintEvent(QPaintEvent *) override; + +signals: + void clicked(); +}; diff --git a/selfdrive/ui/qt/widgets/prime.cc b/selfdrive/ui/qt/widgets/prime.cc index ab6dc67d36..2e9a9e6af9 100644 --- a/selfdrive/ui/qt/widgets/prime.cc +++ b/selfdrive/ui/qt/widgets/prime.cc @@ -314,8 +314,8 @@ void SetupWidget::replyFinished(const QString &response, bool success) { bool prime = json["prime"].toBool(); - if (QUIState::ui_state.has_prime != prime) { - QUIState::ui_state.has_prime = prime; + if (uiState()->has_prime != prime) { + uiState()->has_prime = prime; Params().putBool("HasPrime", prime); } diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 7fe87ce698..0fee52f736 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -12,14 +12,10 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { main_layout->addWidget(homeWindow); QObject::connect(homeWindow, &HomeWindow::openSettings, this, &MainWindow::openSettings); QObject::connect(homeWindow, &HomeWindow::closeSettings, this, &MainWindow::closeSettings); - QObject::connect(&qs, &QUIState::uiUpdate, homeWindow, &HomeWindow::update); - QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransition); - QObject::connect(&qs, &QUIState::offroadTransition, homeWindow, &HomeWindow::offroadTransitionSignal); settingsWindow = new SettingsWindow(this); main_layout->addWidget(settingsWindow); QObject::connect(settingsWindow, &SettingsWindow::closeSettings, this, &MainWindow::closeSettings); - QObject::connect(&qs, &QUIState::offroadTransition, settingsWindow, &SettingsWindow::offroadTransition); QObject::connect(settingsWindow, &SettingsWindow::reviewTrainingGuide, [=]() { onboardingWindow->showTrainingGuide(); main_layout->setCurrentWidget(onboardingWindow); @@ -37,17 +33,15 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { main_layout->setCurrentWidget(onboardingWindow); } - device.setAwake(true, true); - QObject::connect(&qs, &QUIState::uiUpdate, &device, &Device::update); - QObject::connect(&qs, &QUIState::offroadTransition, [=](bool offroad) { + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { if (!offroad) { closeSettings(); } }); - QObject::connect(&device, &Device::displayPowerChanged, [=]() { - if(main_layout->currentWidget() != onboardingWindow) { - closeSettings(); - } + QObject::connect(&device, &Device::interactiveTimout, [=]() { + if (main_layout->currentWidget() == settingsWindow) { + closeSettings(); + } }); // load fonts @@ -80,15 +74,14 @@ void MainWindow::openSettings() { void MainWindow::closeSettings() { main_layout->setCurrentWidget(homeWindow); - if (QUIState::ui_state.scene.started) { + if (uiState()->scene.started) { homeWindow->showSidebar(false); } } bool MainWindow::eventFilter(QObject *obj, QEvent *event) { - // wake screen on tap if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::TouchBegin) { - device.setAwake(true, true); + device.resetInteractiveTimout(); } #ifdef QCOM diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 069831cffe..0bd328aa8a 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -19,7 +19,6 @@ private: void closeSettings(); Device device; - QUIState qs; QStackedLayout *main_layout; HomeWindow *homeWindow; diff --git a/selfdrive/ui/replay/camera.cc b/selfdrive/ui/replay/camera.cc index b37332ae7e..1912c9b581 100644 --- a/selfdrive/ui/replay/camera.cc +++ b/selfdrive/ui/replay/camera.cc @@ -3,8 +3,6 @@ #include #include -const int YUV_BUF_COUNT = 50; - CameraServer::CameraServer(std::pair camera_size[MAX_CAMERAS], bool send_yuv) : send_yuv(send_yuv) { for (int i = 0; i < MAX_CAMERAS; ++i) { std::tie(cameras_[i].width, cameras_[i].height) = camera_size[i]; @@ -29,7 +27,7 @@ void CameraServer::startVipcServer() { std::cout << "camera[" << cam.type << "] frame size " << cam.width << "x" << cam.height << std::endl; vipc_server_->create_buffers(cam.rgb_type, UI_BUF_COUNT, true, cam.width, cam.height); if (send_yuv) { - vipc_server_->create_buffers(cam.yuv_type, YUV_BUF_COUNT, false, cam.width, cam.height); + vipc_server_->create_buffers(cam.yuv_type, YUV_BUFFER_COUNT, false, cam.width, cam.height); } if (!cam.thread.joinable()) { cam.thread = std::thread(&CameraServer::cameraThread, this, std::ref(cam)); diff --git a/selfdrive/ui/replay/framereader.cc b/selfdrive/ui/replay/framereader.cc index 32af922f1f..59e43cb58f 100644 --- a/selfdrive/ui/replay/framereader.cc +++ b/selfdrive/ui/replay/framereader.cc @@ -3,6 +3,8 @@ #include #include "libyuv.h" +#include "cereal/visionipc/visionbuf.h" + namespace { struct buffer_data { @@ -13,7 +15,8 @@ struct buffer_data { int readPacket(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; - buf_size = std::min((size_t)buf_size, bd->size - bd->offset); + assert(bd->offset <= bd->size); + buf_size = std::min((size_t)buf_size, (size_t)(bd->size - bd->offset)); if (!buf_size) return AVERROR_EOF; memcpy(buf, bd->data + bd->offset, buf_size); @@ -98,6 +101,7 @@ bool FrameReader::load(const std::byte *data, size_t size, bool no_cuda, std::at width = (decoder_ctx->width + 3) & ~3; height = decoder_ctx->height; + visionbuf_compute_aligned_width_and_height(width, height, &aligned_width, &aligned_height); if (has_cuda_device && !no_cuda) { if (!initHardwareDecoder(AV_HWDEVICE_TYPE_CUDA)) { @@ -218,19 +222,21 @@ bool FrameReader::copyBuffers(AVFrame *f, uint8_t *rgb, uint8_t *yuv) { libyuv::NV12ToI420(f->data[0], f->linesize[0], f->data[1], f->linesize[1], y, width, u, width / 2, v, width / 2, width, height); libyuv::I420ToRGB24(y, width, u, width / 2, v, width / 2, - rgb, width * 3, width, height); + rgb, aligned_width * 3, width, height); } else { if (yuv) { uint8_t *u = yuv + width * height; uint8_t *v = u + (width / 2) * (height / 2); - memcpy(yuv, f->data[0], width * height); - memcpy(u, f->data[1], width / 2 * height / 2); - memcpy(v, f->data[2], width / 2 * height / 2); + libyuv::I420Copy(f->data[0], f->linesize[0], + f->data[1], f->linesize[1], + f->data[2], f->linesize[2], + yuv, width, u, width / 2, v, width / 2, + width, height); } libyuv::I420ToRGB24(f->data[0], f->linesize[0], f->data[1], f->linesize[1], f->data[2], f->linesize[2], - rgb, width * 3, width, height); + rgb, aligned_width * 3, width, height); } return true; } diff --git a/selfdrive/ui/replay/framereader.h b/selfdrive/ui/replay/framereader.h index d572b727e5..8de5c1f93e 100644 --- a/selfdrive/ui/replay/framereader.h +++ b/selfdrive/ui/replay/framereader.h @@ -22,12 +22,13 @@ public: bool load(const std::string &url, bool no_cuda = false, std::atomic *abort = nullptr, bool local_cache = false, int chunk_size = -1, int retries = 0); bool load(const std::byte *data, size_t size, bool no_cuda = false, std::atomic *abort = nullptr); bool get(int idx, uint8_t *rgb, uint8_t *yuv); - int getRGBSize() const { return width * height * 3; } + int getRGBSize() const { return aligned_width * aligned_height * 3; } int getYUVSize() const { return width * height * 3 / 2; } size_t getFrameCount() const { return packets.size(); } bool valid() const { return valid_; } int width = 0, height = 0; + int aligned_width = 0, aligned_height = 0; private: bool initHardwareDecoder(AVHWDeviceType hw_device_type); diff --git a/selfdrive/ui/replay/logreader.cc b/selfdrive/ui/replay/logreader.cc index 8e2836a4ff..d836ef11f8 100644 --- a/selfdrive/ui/replay/logreader.cc +++ b/selfdrive/ui/replay/logreader.cc @@ -56,15 +56,17 @@ bool LogReader::load(const std::string &url, std::atomic *abort, bool loca } bool LogReader::load(const std::byte *data, size_t size, std::atomic *abort) { - raw_ = decompressBZ2(data, size); + raw_ = decompressBZ2(data, size, abort); if (raw_.empty()) { - std::cout << "failed to decompress log" << std::endl; + if (!(abort && *abort)) { + std::cout << "failed to decompress log" << std::endl; + } return false; } try { kj::ArrayPtr words((const capnp::word *)raw_.data(), raw_.size() / sizeof(capnp::word)); - while (words.size() > 0) { + while (words.size() > 0 && !(abort && *abort)) { #ifdef HAS_MEMORY_RESOURCE Event *evt = new (mbr_) Event(words); @@ -91,11 +93,14 @@ bool LogReader::load(const std::byte *data, size_t size, std::atomic *abor } } catch (const kj::Exception &e) { std::cout << "failed to parse log : " << e.getDescription().cStr() << std::endl; - if (events.empty()) return false; - - std::cout << "read " << events.size() << " events from corrupt log" << std::endl; + if (!events.empty()) { + std::cout << "read " << events.size() << " events from corrupt log" << std::endl; + } } - std::sort(events.begin(), events.end(), Event::lessThan()); - return true; + if (!events.empty() && !(abort && *abort)) { + std::sort(events.begin(), events.end(), Event::lessThan()); + return true; + } + return false; } diff --git a/selfdrive/ui/replay/main.cc b/selfdrive/ui/replay/main.cc index 062837885b..78e4403d7e 100644 --- a/selfdrive/ui/replay/main.cc +++ b/selfdrive/ui/replay/main.cc @@ -11,16 +11,12 @@ const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; struct termios oldt = {}; -Replay *replay = nullptr; void sigHandler(int s) { std::signal(s, SIG_DFL); if (oldt.c_lflag) { tcsetattr(STDIN_FILENO, TCSANOW, &oldt); } - if (replay) { - replay->stop(); - } qApp->quit(); } @@ -59,6 +55,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 +69,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 +111,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; @@ -134,7 +143,7 @@ int main(int argc, char *argv[]) { replay_flags |= flag; } } - replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); + Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); if (!replay->load()) { return 0; } diff --git a/selfdrive/ui/replay/replay.cc b/selfdrive/ui/replay/replay.cc index e3753ccf20..122a5e3ebb 100644 --- a/selfdrive/ui/replay/replay.cc +++ b/selfdrive/ui/replay/replay.cc @@ -25,22 +25,19 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s qDebug() << "services " << s; if (sm == nullptr) { - pm = new PubMaster(s); + pm = std::make_unique(s); } route_ = std::make_unique(route, data_dir); - events_ = new std::vector(); + events_ = std::make_unique>(); + new_events_ = std::make_unique>(); + qRegisterMetaType("FindFlag"); connect(this, &Replay::seekTo, this, &Replay::doSeek); + connect(this, &Replay::seekToFlag, this, &Replay::doSeekToFlag); connect(this, &Replay::segmentChanged, this, &Replay::queueSegment); } Replay::~Replay() { - stop(); - delete pm; - delete events_; -} - -void Replay::stop() { if (!stream_thread_ && segments_.empty()) return; qDebug() << "shutdown: in progress..."; @@ -63,8 +60,10 @@ bool Replay::load() { } for (auto &[n, f] : route_->segments()) { - if ((!f.rlog.isEmpty() || !f.qlog.isEmpty()) && (!f.road_cam.isEmpty() || !f.qcamera.isEmpty())) { - segments_[n] = nullptr; + bool has_log = !f.rlog.isEmpty() || !f.qlog.isEmpty(); + bool has_video = !f.road_cam.isEmpty() || !f.qcamera.isEmpty(); + if (has_log && (has_video || hasFlag(REPLAY_FLAG_NO_VIPC))) { + segments_.insert({n, nullptr}); } } if (segments_.empty()) { @@ -112,6 +111,55 @@ void Replay::doSeek(int seconds, bool relative) { queueSegment(); } +void Replay::doSeekToFlag(FindFlag flag) { + if (flag == FindFlag::nextEngagement) { + qInfo() << "seeking to the next engagement..."; + } else if (flag == FindFlag::nextDisEngagement) { + qInfo() << "seeking to the disengagement..."; + } + + updateEvents([&]() { + if (auto next = find(flag)) { + uint64_t tm = *next - 2 * 1e9; // seek to 2 seconds before next + if (tm <= cur_mono_time_) { + return true; + } + + cur_mono_time_ = tm; + current_segment_ = currentSeconds() / 60; + return isSegmentMerged(current_segment_); + } else { + qWarning() << "seeking failed"; + return true; + } + }); + + queueSegment(); +} + +std::optional Replay::find(FindFlag flag) { + // Search in all segments + for (const auto &[n, _] : segments_) { + if (n < current_segment_) continue; + + LogReader log; + bool cache_to_local = true; // cache qlog to local for fast seek + if (!log.load(route_->at(n).qlog.toStdString(), nullptr, cache_to_local, 0, 3)) continue; + + for (const Event *e : log.events) { + if (e->mono_time > cur_mono_time_ && e->which == cereal::Event::Which::CONTROLS_STATE) { + const auto cs = e->event.getControlsState(); + if (flag == FindFlag::nextEngagement && cs.getEnabled()) { + return e->mono_time; + } else if (flag == FindFlag::nextDisEngagement && !cs.getEnabled()) { + return e->mono_time; + } + } + } + } + return std::nullopt; +} + void Replay::pause(bool pause) { updateEvents([=]() { qInfo() << (pause ? "paused..." : "resuming"); @@ -181,28 +229,27 @@ void Replay::queueSegment() { void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap::iterator &end) { // merge 3 segments in sequence. std::vector segments_need_merge; + size_t new_events_size = 0; for (auto it = begin; it != end && it->second->isLoaded() && segments_need_merge.size() < 3; ++it) { segments_need_merge.push_back(it->first); + new_events_size += it->second->log->events.size(); } if (segments_need_merge != segments_merged_) { qDebug() << "merge segments" << segments_need_merge; - std::vector *new_events = new std::vector(); - new_events->reserve(std::accumulate(segments_need_merge.begin(), segments_need_merge.end(), 0, - [=](int v, int n) { return v + segments_[n]->log->events.size(); })); + new_events_->clear(); + new_events_->reserve(new_events_size); for (int n : segments_need_merge) { - auto &e = segments_[n]->log->events; - auto middle = new_events->insert(new_events->end(), e.begin(), e.end()); - std::inplace_merge(new_events->begin(), middle, new_events->end(), Event::lessThan()); + const auto &e = segments_[n]->log->events; + auto middle = new_events_->insert(new_events_->end(), e.begin(), e.end()); + std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); } - auto prev_events = events_; updateEvents([&]() { - events_ = new_events; + events_.swap(new_events_); segments_merged_ = segments_need_merge; return true; }); - delete prev_events; } } @@ -224,13 +271,15 @@ void Replay::startStream(const Segment *cur_segment) { } // start camera server - std::pair camera_size[MAX_CAMERAS] = {}; - for (auto type : ALL_CAMERAS) { - if (auto &fr = cur_segment->frames[type]) { - camera_size[type] = {fr->width, fr->height}; + if (!hasFlag(REPLAY_FLAG_NO_VIPC)) { + std::pair camera_size[MAX_CAMERAS] = {}; + for (auto type : ALL_CAMERAS) { + if (auto &fr = cur_segment->frames[type]) { + camera_size[type] = {fr->width, fr->height}; + } } + camera_server_ = std::make_unique(camera_size, hasFlag(REPLAY_FLAG_SEND_YUV)); } - camera_server_ = std::make_unique(camera_size, flags_ & REPLAY_FLAG_SEND_YUV); // start stream thread stream_thread_ = new QThread(); @@ -258,8 +307,8 @@ void Replay::publishFrame(const Event *e) { {cereal::Event::DRIVER_ENCODE_IDX, DriverCam}, {cereal::Event::WIDE_ROAD_ENCODE_IDX, WideRoadCam}, }; - if ((e->which == cereal::Event::DRIVER_ENCODE_IDX && !(flags_ & REPLAY_FLAG_DCAM)) || - (e->which == cereal::Event::WIDE_ROAD_ENCODE_IDX && !(flags_ & REPLAY_FLAG_ECAM))) { + if ((e->which == cereal::Event::DRIVER_ENCODE_IDX && !hasFlag(REPLAY_FLAG_DCAM)) || + (e->which == cereal::Event::WIDE_ROAD_ENCODE_IDX && !hasFlag(REPLAY_FLAG_ECAM))) { return; } auto eidx = capnp::AnyStruct::Reader(e->event).getPointerSection()[0].getAs(); @@ -320,21 +369,26 @@ void Replay::stream() { // reset start times evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); - } else if (behind_ns > 0) { + } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { precise_nano_sleep(behind_ns); } - if (evt->frame) { - publishFrame(evt); - } else { + if (!evt->frame) { publishMessage(evt); + } else if (camera_server_) { + if (hasFlag(REPLAY_FLAG_FULL_SPEED)) { + camera_server_->waitFinish(); + } + publishFrame(evt); } } } // wait for frame to be sent before unlock.(frameReader may be deleted after unlock) - camera_server_->waitFinish(); + if (camera_server_) { + camera_server_->waitFinish(); + } - if (eit == events_->end() && !(flags_ & REPLAY_FLAG_NO_LOOP)) { + if (eit == events_->end() && !hasFlag(REPLAY_FLAG_NO_LOOP)) { int last_segment = segments_.rbegin()->first; if (current_segment_ >= last_segment && isSegmentMerged(last_segment)) { qInfo() << "reaches the end of route, restart from beginning"; diff --git a/selfdrive/ui/replay/replay.h b/selfdrive/ui/replay/replay.h index 2d30d90e4b..ed024eba18 100644 --- a/selfdrive/ui/replay/replay.h +++ b/selfdrive/ui/replay/replay.h @@ -17,6 +17,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 +35,26 @@ public: ~Replay(); bool load(); void start(int seconds = 0); - void stop(); void pause(bool pause); bool isPaused() const { return paused_; } + inline bool hasFlag(REPLAY_FLAGS flag) const { return flags_ & flag; } + inline void addFlag(REPLAY_FLAGS flag) { flags_ |= flag; } + inline void removeFlag(REPLAY_FLAGS flag) { flags_ &= ~flag; } signals: void segmentChanged(); void seekTo(int seconds, bool relative); + void seekToFlag(FindFlag flag); protected slots: void queueSegment(); void doSeek(int seconds, bool relative); + void doSeekToFlag(FindFlag flag); void segmentLoadFinished(bool sucess); protected: typedef std::map> SegmentMap; + std::optional find(FindFlag flag); void startStream(const Segment *cur_segment); void stream(); void setCurrentSegment(int n); @@ -69,14 +81,15 @@ protected: bool events_updated_ = false; uint64_t route_start_ts_ = 0; uint64_t cur_mono_time_ = 0; - std::vector *events_ = nullptr; + std::unique_ptr> events_; + std::unique_ptr> new_events_; std::vector segments_merged_; // messaging SubMaster *sm = nullptr; - PubMaster *pm = nullptr; + std::unique_ptr pm; std::vector sockets_; std::unique_ptr route_; std::unique_ptr camera_server_; - uint32_t flags_ = REPLAY_FLAG_NONE; + std::atomic flags_ = REPLAY_FLAG_NONE; }; diff --git a/selfdrive/ui/replay/route.cc b/selfdrive/ui/replay/route.cc index 23c27073cb..fe6e21a91a 100644 --- a/selfdrive/ui/replay/route.cc +++ b/selfdrive/ui/replay/route.cc @@ -91,16 +91,16 @@ void Route::addFileToSegment(int n, const QString &file) { Segment::Segment(int n, const SegmentFile &files, uint32_t flags) : seg_num(n), flags(flags) { // [RoadCam, DriverCam, WideRoadCam, log]. fallback to qcamera/qlog - const QString file_list[] = { + const std::array file_list = { (flags & REPLAY_FLAG_QCAMERA) || files.road_cam.isEmpty() ? files.qcamera : files.road_cam, flags & REPLAY_FLAG_DCAM ? files.driver_cam : "", flags & REPLAY_FLAG_ECAM ? files.wide_road_cam : "", files.rlog.isEmpty() ? files.qlog : files.rlog, }; - for (int i = 0; i < std::size(file_list); i++) { - if (!file_list[i].isEmpty()) { - loading_++; - synchronizer_.addFuture(QtConcurrent::run([=] { loadFile(i, file_list[i].toStdString()); })); + for (int i = 0; i < file_list.size(); ++i) { + if (!file_list[i].isEmpty() && (!(flags & REPLAY_FLAG_NO_VIPC) || i >= MAX_CAMERAS)) { + ++loading_; + synchronizer_.addFuture(QtConcurrent::run(this, &Segment::loadFile, i, file_list[i].toStdString())); } } } diff --git a/selfdrive/ui/replay/tests/test_replay.cc b/selfdrive/ui/replay/tests/test_replay.cc index 5b9b7cdeb7..6063dfe7d5 100644 --- a/selfdrive/ui/replay/tests/test_replay.cc +++ b/selfdrive/ui/replay/tests/test_replay.cc @@ -17,11 +17,17 @@ TEST_CASE("httpMultiPartDownload") { const size_t chunk_size = 5 * 1024 * 1024; std::string content; SECTION("download to file") { - REQUIRE(httpDownload(TEST_RLOG_URL, filename, chunk_size)); + bool ret = false; + for (int i = 0; i < 3 && !ret; ++i) { + ret = httpDownload(TEST_RLOG_URL, filename, chunk_size); + } + REQUIRE(ret); content = util::read_file(filename); } SECTION("download to buffer") { - content = httpGet(TEST_RLOG_URL, chunk_size); + for (int i = 0; i < 3 && content.empty(); ++i) { + content = httpGet(TEST_RLOG_URL, chunk_size); + } REQUIRE(!content.empty()); } REQUIRE(content.size() == 9112651); diff --git a/selfdrive/ui/replay/util.cc b/selfdrive/ui/replay/util.cc index d6791465f2..f1c41fcba0 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; @@ -98,8 +99,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); @@ -193,11 +192,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 +222,10 @@ std::string decompressBZ2(const std::byte *in, size_t in_size) { if (bzerror == BZ_OK && strm.avail_in > 0 && strm.avail_out == 0) { out.resize(out.size() * 2); } - } while (bzerror == BZ_OK); + } while (bzerror == BZ_OK && !(abort && *abort)); BZ2_bzDecompressEnd(&strm); - if (bzerror == BZ_STREAM_END) { + if (bzerror == BZ_STREAM_END && !(abort && *abort)) { out.resize(strm.total_out_lo32); return out; } diff --git a/selfdrive/ui/replay/util.h b/selfdrive/ui/replay/util.h index 726e65cb94..1a09f2e971 100644 --- a/selfdrive/ui/replay/util.h +++ b/selfdrive/ui/replay/util.h @@ -5,8 +5,8 @@ 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); diff --git a/selfdrive/ui/soundd/sound.h b/selfdrive/ui/soundd/sound.h index b493c3a3d8..82b360fd38 100644 --- a/selfdrive/ui/soundd/sound.h +++ b/selfdrive/ui/soundd/sound.h @@ -16,7 +16,7 @@ const std::tuple sound_list[] = { {AudibleAlert::PROMPT_DISTRACTED, "prompt_distracted.wav", QSoundEffect::Infinite}, {AudibleAlert::WARNING_SOFT, "warning_soft.wav", QSoundEffect::Infinite}, - {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", 10}, + {AudibleAlert::WARNING_IMMEDIATE, "warning_immediate.wav", QSoundEffect::Infinite}, }; class Sound : public QObject { diff --git a/selfdrive/ui/tests/cycle_offroad_alerts.py b/selfdrive/ui/tests/cycle_offroad_alerts.py index 6b3453026b..6b6aea4477 100755 --- a/selfdrive/ui/tests/cycle_offroad_alerts.py +++ b/selfdrive/ui/tests/cycle_offroad_alerts.py @@ -18,7 +18,7 @@ if __name__ == "__main__": while True: print("setting alert update") params.put_bool("UpdateAvailable", True) - r = open(os.path.join(BASEDIR, "RELEASES.md"), "r").read() + r = open(os.path.join(BASEDIR, "RELEASES.md")).read() r = r[:r.find('\n\n')] # Slice latest release notes params.put("ReleaseNotes", r + "\n") diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 38c08eb484..e0e74d5442 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]); } } @@ -71,11 +73,11 @@ static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { auto model_position = model.getPosition(); float max_distance = std::clamp(model_position.getX()[TRAJECTORY_SIZE - 1], MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); + int max_idx = get_path_length_idx(model_position, max_distance); // update lane lines const auto lane_lines = model.getLaneLines(); const auto lane_line_probs = model.getLaneLineProbs(); - int max_idx = get_path_length_idx(lane_lines[0], max_distance); for (int i = 0; i < std::size(scene.lane_line_vertices); i++) { scene.lane_line_probs[i] = lane_line_probs[i]; update_line_data(s, lane_lines[i], 0.025 * scene.lane_line_probs[i], 0, &scene.lane_line_vertices[i], max_idx); @@ -94,8 +96,8 @@ static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { if (lead_one.getStatus()) { const float lead_d = lead_one.getDRel() * 2.; max_distance = std::clamp((float)(lead_d - fmin(lead_d * 0.35, 10.)), 0.0f, max_distance); + max_idx = get_path_length_idx(model_position, max_distance); } - max_idx = get_path_length_idx(model_position, max_distance); update_line_data(s, model_position, 0.5, 1.22, &scene.track_vertices, max_idx); } @@ -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,68 +186,66 @@ void ui_update_params(UIState *s) { s->scene.is_metric = Params().getBool("IsMetric"); } -static void update_status(UIState *s) { - if (s->scene.started && s->sm->updated("controlsState")) { - auto controls_state = (*s->sm)["controlsState"].getControlsState(); +void UIState::updateStatus() { + if (scene.started && sm->updated("controlsState")) { + auto controls_state = (*sm)["controlsState"].getControlsState(); auto alert_status = controls_state.getAlertStatus(); if (alert_status == cereal::ControlsState::AlertStatus::USER_PROMPT) { - s->status = STATUS_WARNING; + status = STATUS_WARNING; } else if (alert_status == cereal::ControlsState::AlertStatus::CRITICAL) { - s->status = STATUS_ALERT; + status = STATUS_ALERT; } else { - s->status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + status = controls_state.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; } } // Handle onroad/offroad transition - static bool started_prev = false; - if (s->scene.started != started_prev) { - if (s->scene.started) { - s->status = STATUS_DISENGAGED; - s->scene.started_frame = s->sm->frame; - s->scene.end_to_end = Params().getBool("EndToEndToggle"); - s->wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; + if (scene.started != started_prev) { + if (scene.started) { + status = STATUS_DISENGAGED; + scene.started_frame = sm->frame; + scene.end_to_end = Params().getBool("EndToEndToggle"); + wide_camera = Hardware::TICI() ? Params().getBool("EnableWideCamera") : false; } - // Invisible until we receive a calibration message. - s->scene.world_objects_visible = false; + started_prev = scene.started; + emit offroadTransition(!scene.started); + } else if (sm->frame == 1) { + emit offroadTransition(!scene.started); } - started_prev = s->scene.started; } - -QUIState::QUIState(QObject *parent) : QObject(parent) { - ui_state.sm = std::make_unique>({ +UIState::UIState(QObject *parent) : QObject(parent) { + sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", "pandaStates", "carParams", "driverMonitoringState", "sensorEvents", "carState", "liveLocationKalman", }); Params params; - ui_state.wide_camera = Hardware::TICI() ? params.getBool("EnableWideCamera") : false; - ui_state.has_prime = params.getBool("HasPrime"); + wide_camera = Hardware::TICI() ? params.getBool("EnableWideCamera") : false; + has_prime = params.getBool("HasPrime"); // update timer timer = new QTimer(this); - QObject::connect(timer, &QTimer::timeout, this, &QUIState::update); + QObject::connect(timer, &QTimer::timeout, this, &UIState::update); timer->start(1000 / UI_FREQ); } -void QUIState::update() { - update_sockets(&ui_state); - update_state(&ui_state); - update_status(&ui_state); - - if (ui_state.scene.started != started_prev || ui_state.sm->frame == 1) { - started_prev = ui_state.scene.started; - emit offroadTransition(!ui_state.scene.started); - } +void UIState::update() { + update_sockets(this); + update_state(this); + updateStatus(); - if (ui_state.sm->frame % UI_FREQ == 0) { + if (sm->frame % UI_FREQ == 0) { watchdog_kick(); } - emit uiUpdate(ui_state); + emit uiUpdate(*this); } Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) { + setAwake(true); + resetInteractiveTimout(); + + QObject::connect(uiState(), &UIState::uiUpdate, this, &Device::update); } void Device::update(const UIState &s) { @@ -258,20 +253,20 @@ void Device::update(const UIState &s) { updateWakefulness(s); // TODO: remove from UIState and use signals - QUIState::ui_state.awake = awake; + uiState()->awake = awake; } -void Device::setAwake(bool on, bool reset) { +void Device::setAwake(bool on) { if (on != awake) { awake = on; Hardware::set_display_power(awake); LOGD("setting display power %d", awake); emit displayPowerChanged(awake); } +} - if (reset) { - awake_timeout = 30 * UI_FREQ; - } +void Device::resetInteractiveTimout() { + interactive_timeout = (ignition_on ? 10 : 30) * UI_FREQ; } void Device::updateBrightness(const UIState &s) { @@ -297,23 +292,40 @@ void Device::updateBrightness(const UIState &s) { } if (brightness != last_brightness) { - std::thread{Hardware::set_brightness, brightness}.detach(); + if (!brightness_future.isRunning()) { + brightness_future = QtConcurrent::run(Hardware::set_brightness, brightness); + last_brightness = brightness; + } } - last_brightness = brightness; +} + +bool Device::motionTriggered(const UIState &s) { + static float accel_prev = 0; + static float gyro_prev = 0; + + bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2; + bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15; + + gyro_prev = s.scene.gyro_sensor; + accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples; + + return (!awake && accel_trigger && gyro_trigger); } void Device::updateWakefulness(const UIState &s) { - awake_timeout = std::max(awake_timeout - 1, 0); - - bool should_wake = s.scene.started || s.scene.ignition; - if (!should_wake) { - // tap detection while display is off - bool accel_trigger = abs(s.scene.accel_sensor - accel_prev) > 0.2; - bool gyro_trigger = abs(s.scene.gyro_sensor - gyro_prev) > 0.15; - should_wake = accel_trigger && gyro_trigger; - gyro_prev = s.scene.gyro_sensor; - accel_prev = (accel_prev * (accel_samples - 1) + s.scene.accel_sensor) / accel_samples; + bool ignition_just_turned_off = !s.scene.ignition && ignition_on; + ignition_on = s.scene.ignition; + + if (ignition_just_turned_off || motionTriggered(s)) { + resetInteractiveTimout(); + } else if (interactive_timeout > 0 && --interactive_timeout == 0) { + emit interactiveTimout(); } - setAwake(awake_timeout, should_wake); + setAwake(s.scene.ignition || interactive_timeout > 0); +} + +UIState *uiState() { + static UIState ui_state; + return &ui_state; } diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index 4b4ef0bb28..bc15257a08 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -32,29 +33,38 @@ struct Alert { QString type; cereal::ControlsState::AlertSize size; AudibleAlert sound; + bool equal(const Alert &a2) { return text1 == a2.text1 && text2 == a2.text2 && type == a2.type && sound == a2.sound; } static Alert get(const SubMaster &sm, uint64_t started_frame) { + const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); if (sm.updated("controlsState")) { - const cereal::ControlsState::Reader &cs = sm["controlsState"].getControlsState(); return {cs.getAlertText1().cStr(), cs.getAlertText2().cStr(), cs.getAlertType().cStr(), cs.getAlertSize(), cs.getAlertSound()}; } else if ((sm.frame - started_frame) > 5 * UI_FREQ) { const int CONTROLS_TIMEOUT = 5; + const int controls_missing = (nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9; + // Handle controls timeout if (sm.rcv_frame("controlsState") < started_frame) { // car is started, but controlsState hasn't been seen at all return {"openpilot Unavailable", "Waiting for controls to start", "controlsWaiting", cereal::ControlsState::AlertSize::MID, AudibleAlert::NONE}; - } else if ((nanos_since_boot() - sm.rcv_time("controlsState")) / 1e9 > CONTROLS_TIMEOUT) { + } else if (controls_missing > CONTROLS_TIMEOUT) { // car is started, but controls is lagging or died - return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", - "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, - AudibleAlert::WARNING_IMMEDIATE}; + if (cs.getEnabled() && (controls_missing - CONTROLS_TIMEOUT) < 10) { + return {"TAKE CONTROL IMMEDIATELY", "Controls Unresponsive", + "controlsUnresponsive", cereal::ControlsState::AlertSize::FULL, + AudibleAlert::WARNING_IMMEDIATE}; + } else { + return {"Controls Unresponsive", "Reboot Device", + "controlsUnresponsivePermanent", cereal::ControlsState::AlertSize::MID, + AudibleAlert::NONE}; + } } } return {}; @@ -82,8 +92,6 @@ typedef struct { typedef struct UIScene { mat3 view_from_calib; - bool world_objects_visible; - cereal::PandaState::PandaType pandaType; // modelV2 @@ -101,7 +109,16 @@ typedef struct UIScene { uint64_t started_frame; } UIScene; -typedef struct UIState { +class UIState : public QObject { + Q_OBJECT + +public: + UIState(QObject* parent = 0); + void updateStatus(); + inline bool worldObjectsVisible() const { + return sm->rcv_frame("liveCalibration") > scene.started_frame; + }; + int fb_w = 0, fb_h = 0; std::unique_ptr sm; @@ -114,17 +131,6 @@ typedef struct UIState { QTransform car_space_transform; bool wide_camera; -} UIState; - - -class QUIState : public QObject { - Q_OBJECT - -public: - QUIState(QObject* parent = 0); - - // TODO: get rid of this, only use signal - inline static UIState ui_state = {0}; signals: void uiUpdate(const UIState &s); @@ -138,6 +144,7 @@ private: bool started_prev = true; }; +UIState *uiState(); // device management class @@ -152,22 +159,23 @@ private: const float accel_samples = 5*UI_FREQ; bool awake = false; - int awake_timeout = 0; - float accel_prev = 0; - float gyro_prev = 0; + int interactive_timeout = 0; + bool ignition_on = false; int last_brightness = 0; FirstOrderFilter brightness_filter; - - QTimer *timer; + QFuture brightness_future; void updateBrightness(const UIState &s); void updateWakefulness(const UIState &s); + bool motionTriggered(const UIState &s); + void setAwake(bool on); signals: void displayPowerChanged(bool on); + void interactiveTimout(); public slots: - void setAwake(bool on, bool reset); + void resetInteractiveTimout(); void update(const UIState &s); }; diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 2acebf2a4e..1a87798619 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -40,7 +40,7 @@ from common.params import Params from selfdrive.hardware import EON, TICI, HARDWARE from selfdrive.swaglog import cloudlog from selfdrive.controls.lib.alertmanager import set_offroad_alert -from selfdrive.version import get_tested_branch +from selfdrive.version import is_tested_branch LOCK_FILE = os.getenv("UPDATER_LOCK_FILE", "/tmp/safe_staging_overlay.lock") STAGING_ROOT = os.getenv("UPDATER_STAGING_ROOT", "/data/safe_staging") @@ -142,7 +142,7 @@ def set_params(new_version: bool, failed_count: int, exception: Optional[str]) - now = datetime.datetime.utcnow() dt = now - last_update if failed_count > 15 and exception is not None: - if get_tested_branch(): + if is_tested_branch(): extra_text = "Ensure the software is correctly installed" else: extra_text = exception @@ -342,7 +342,7 @@ def fetch_update(wait_helper: WaitTimeHelper) -> bool: new_version = cur_hash != upstream_hash git_fetch_result = check_git_fetch_result(git_fetch_output) - cloudlog.info("comparing %s to %s" % (cur_hash, upstream_hash)) + cloudlog.info(f"comparing {cur_hash} to {upstream_hash}") if new_version or git_fetch_result: cloudlog.info("Running update") @@ -370,7 +370,7 @@ def fetch_update(wait_helper: WaitTimeHelper) -> bool: return new_version -def main(): +def main() -> None: params = Params() if params.get_bool("DisableUpdates"): @@ -380,7 +380,7 @@ def main(): ov_lock_fd = open(LOCK_FILE, 'w') try: fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except IOError as e: + except OSError as e: raise RuntimeError("couldn't get overlay lock; is another instance running?") from e # Set low io priority @@ -400,7 +400,7 @@ def main(): overlay_init.unlink(missing_ok=True) first_run = True - last_fetch_time = 0 + last_fetch_time = 0.0 update_failed_count = 0 # Set initial params for offroad alerts diff --git a/selfdrive/version.py b/selfdrive/version.py index 2ddfc93c69..a0cb61875e 100644 --- a/selfdrive/version.py +++ b/selfdrive/version.py @@ -7,7 +7,6 @@ from functools import lru_cache from common.basedir import BASEDIR from selfdrive.swaglog import cloudlog - TESTED_BRANCHES = ['devel', 'release2-staging', 'release3-staging', 'dashcam-staging', 'release2', 'release3', 'dashcam'] training_version: bytes = b"0.2.0" @@ -62,12 +61,12 @@ def get_version() -> str: @cache -def get_prebuilt() -> bool: +def is_prebuilt() -> bool: return os.path.exists(os.path.join(BASEDIR, 'prebuilt')) @cache -def get_comma_remote() -> bool: +def is_comma_remote() -> bool: origin = get_origin() if origin is None: return False @@ -76,12 +75,12 @@ def get_comma_remote() -> bool: @cache -def get_tested_branch() -> bool: +def is_tested_branch() -> bool: return get_short_branch() in TESTED_BRANCHES @cache -def get_dirty() -> bool: +def is_dirty() -> bool: origin = get_origin() branch = get_branch() if (origin is None) or (branch is None): @@ -90,7 +89,7 @@ def get_dirty() -> bool: dirty = False try: # Actually check dirty files - if not get_prebuilt(): + if not is_prebuilt(): # This is needed otherwise touched files might show up as modified try: subprocess.check_call(["git", "update-index", "--refresh"]) @@ -99,16 +98,7 @@ def get_dirty() -> bool: dirty = (subprocess.call(["git", "diff-index", "--quiet", branch, "--"]) != 0) - # Log dirty files - if dirty and get_comma_remote(): - try: - dirty_files = run_cmd(["git", "diff-index", branch, "--"]) - cloudlog.event("dirty comma branch", version=get_version(), dirty=dirty, origin=origin, branch=branch, - dirty_files=dirty_files, commit=get_commit(), origin_commit=get_commit(branch)) - except subprocess.CalledProcessError: - pass - - dirty = dirty or (not get_comma_remote()) + dirty = dirty or (not is_comma_remote()) dirty = dirty or ('master' in branch) except subprocess.CalledProcessError: @@ -125,9 +115,9 @@ if __name__ == "__main__": params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - print("Dirty: %s" % get_dirty()) - print("Version: %s" % get_version()) - print("Origin: %s" % get_origin()) - print("Branch: %s" % get_branch()) - print("Short branch: %s" % get_short_branch()) - print("Prebuilt: %s" % get_prebuilt()) + print(f"Dirty: {is_dirty()}") + print(f"Version: {get_version()}") + print(f"Origin: {get_origin()}") + print(f"Branch: {get_branch()}") + print(f"Short branch: {get_short_branch()}") + print(f"Prebuilt: {is_prebuilt()}") diff --git a/third_party/acados/Darwin/lib/libacados.dylib b/third_party/acados/Darwin/lib/libacados.dylib new file mode 100755 index 0000000000..36dc230f71 Binary files /dev/null and b/third_party/acados/Darwin/lib/libacados.dylib differ diff --git a/third_party/acados/Darwin/lib/libblasfeo.dylib b/third_party/acados/Darwin/lib/libblasfeo.dylib new file mode 100755 index 0000000000..cefef88940 Binary files /dev/null and b/third_party/acados/Darwin/lib/libblasfeo.dylib differ diff --git a/third_party/acados/Darwin/lib/libhpipm.dylib b/third_party/acados/Darwin/lib/libhpipm.dylib new file mode 100755 index 0000000000..5c05838618 Binary files /dev/null and b/third_party/acados/Darwin/lib/libhpipm.dylib differ diff --git a/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib new file mode 100755 index 0000000000..2ea5465beb Binary files /dev/null and b/third_party/acados/Darwin/lib/libqpOASES_e.3.1.dylib differ diff --git a/third_party/acados/Darwin/lib/libqpOASES_e.dylib b/third_party/acados/Darwin/lib/libqpOASES_e.dylib new file mode 120000 index 0000000000..e3d94c4bc2 --- /dev/null +++ b/third_party/acados/Darwin/lib/libqpOASES_e.dylib @@ -0,0 +1 @@ +libqpOASES_e.3.1.dylib \ No newline at end of file diff --git a/third_party/acados/Darwin/t_renderer b/third_party/acados/Darwin/t_renderer new file mode 100755 index 0000000000..7ee3eda293 Binary files /dev/null and b/third_party/acados/Darwin/t_renderer differ diff --git a/tools/joystick/joystickd.py b/tools/joystick/joystickd.py index ba7b29df54..0e3269556a 100755 --- a/tools/joystick/joystickd.py +++ b/tools/joystick/joystickd.py @@ -34,7 +34,8 @@ class Keyboard: class Joystick: def __init__(self): - self.max_axis_value = 255 # tune based on your joystick, 0 to this + self.min_axis_value = 0 + self.max_axis_value = 255 self.cancel_button = 'BTN_TRIGGER' self.axes_values = {'ABS_Y': 0., 'ABS_RZ': 0.} # gb, steer @@ -45,7 +46,10 @@ class Joystick: if event[0] == self.cancel_button and event[1] == 0: # state 0 is falling edge self.cancel = True elif event[0] in self.axes_values: - norm = -interp(event[1], [0, self.max_axis_value], [-1., 1.]) + self.max_axis_value = max(event[1], self.max_axis_value) + self.min_axis_value = min(event[1], self.min_axis_value) + + norm = -interp(event[1], [self.min_axis_value, self.max_axis_value], [-1., 1.]) self.axes_values[event[0]] = norm if abs(norm) > 0.05 else 0. # center can be noisy, deadzone of 5% else: return False diff --git a/tools/lib/auth.py b/tools/lib/auth.py index d5d2a60bb0..0e9e7705b5 100755 --- a/tools/lib/auth.py +++ b/tools/lib/auth.py @@ -109,7 +109,7 @@ def login(method): if 'code' in web_server.query_params: break elif 'error' in web_server.query_params: - print('Authentication Error: "%s". Description: "%s" ' % ( + print('Authentication Error: "{}". Description: "{}" '.format( web_server.query_params['error'], web_server.query_params.get('error_description')), file=sys.stderr) break diff --git a/tools/lib/framereader.py b/tools/lib/framereader.py index db07c20177..ac5962462e 100644 --- a/tools/lib/framereader.py +++ b/tools/lib/framereader.py @@ -50,7 +50,7 @@ def fingerprint_video(fn): with FileReader(fn) as f: header = f.read(4) if len(header) == 0: - raise DataUnreadableError("%s is empty" % fn) + raise DataUnreadableError(f"{fn} is empty") elif header == b"\x00\xc0\x12\x00": return FrameType.raw elif header == b"\x00\x00\x00\x01": @@ -90,7 +90,7 @@ def vidindex(fn, typ): try: subprocess.check_call([vidindex, typ, fn, prefix_f.name, index_f.name]) except subprocess.CalledProcessError: - raise DataUnreadableError("vidindex failed on file %s" % fn) + raise DataUnreadableError(f"vidindex failed on file {fn}") with open(index_f.name, "rb") as f: index = f.read() with open(prefix_f.name, "rb") as f: @@ -308,7 +308,7 @@ class RawFrameReader(BaseFrameReader): assert num+count <= self.frame_count if pix_fmt not in ("yuv420p", "rgb24"): - raise ValueError("Unsupported pixel format %r" % pix_fmt) + raise ValueError(f"Unsupported pixel format {pix_fmt!r}") app = [] for i in range(num, num+count): @@ -548,10 +548,10 @@ class GOPFrameReader(BaseFrameReader): assert self.frame_count is not None if num + count > self.frame_count: - raise ValueError("{} > {}".format(num + count, self.frame_count)) + raise ValueError(f"{num + count} > {self.frame_count}") if pix_fmt not in ("yuv420p", "rgb24", "yuv444p"): - raise ValueError("Unsupported pixel format %r" % pix_fmt) + raise ValueError(f"Unsupported pixel format {pix_fmt!r}") ret = [self._get_one(num + i, pix_fmt) for i in range(count)] @@ -572,15 +572,13 @@ class StreamFrameReader(StreamGOPReader, GOPFrameReader): def GOPFrameIterator(gop_reader, pix_fmt): dec = VideoStreamDecompressor(gop_reader.fn, gop_reader.vid_fmt, gop_reader.w, gop_reader.h, pix_fmt) - for frame in dec.read(): - yield frame + yield from dec.read() def FrameIterator(fn, pix_fmt, **kwargs): fr = FrameReader(fn, **kwargs) if isinstance(fr, GOPReader): - for v in GOPFrameIterator(fr, pix_fmt): - yield v + yield from GOPFrameIterator(fr, pix_fmt) else: for i in range(fr.frame_count): yield fr.get(i, pix_fmt=pix_fmt)[0] diff --git a/tools/lib/logreader.py b/tools/lib/logreader.py index 1c9cc81e32..fc7a8dcb70 100755 --- a/tools/lib/logreader.py +++ b/tools/lib/logreader.py @@ -12,10 +12,10 @@ except ImportError: from cereal import log as capnp_log # this is an iterator itself, and uses private variables from LogReader -class MultiLogIterator(object): - def __init__(self, log_paths, wraparound=False): +class MultiLogIterator: + def __init__(self, log_paths, sort_by_time=False): self._log_paths = log_paths - self._wraparound = wraparound + self.sort_by_time = sort_by_time self._first_log_idx = next(i for i in range(len(log_paths)) if log_paths[i] is not None) self._current_log = self._first_log_idx @@ -26,7 +26,7 @@ class MultiLogIterator(object): def _log_reader(self, i): if self._log_readers[i] is None and self._log_paths[i] is not None: log_path = self._log_paths[i] - self._log_readers[i] = LogReader(log_path) + self._log_readers[i] = LogReader(log_path, sort_by_time=self.sort_by_time) return self._log_readers[i] @@ -41,12 +41,8 @@ class MultiLogIterator(object): self._idx = 0 self._current_log = next(i for i in range(self._current_log + 1, len(self._log_readers) + 1) if i == len(self._log_readers) or self._log_paths[i] is not None) - # wraparound if self._current_log == len(self._log_readers): - if self._wraparound: - self._current_log = self._first_log_idx - else: - raise StopIteration + raise StopIteration def __next__(self): while 1: @@ -74,8 +70,8 @@ class MultiLogIterator(object): return True -class LogReader(object): - def __init__(self, fn, canonicalize=True, only_union_types=False): +class LogReader: + def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False): data_version = None _, ext = os.path.splitext(urllib.parse.urlparse(fn).path) with FileReader(fn) as f: @@ -90,7 +86,7 @@ class LogReader(object): else: raise Exception(f"unknown extension {ext}") - self._ents = list(ents) + self._ents = list(sorted(ents, key=lambda x: x.logMonoTime) if sort_by_time else ents) self._ts = [x.logMonoTime for x in self._ents] self.data_version = data_version self._only_union_types = only_union_types @@ -112,6 +108,6 @@ if __name__ == "__main__": # below line catches those errors and replaces the bytes with \x__ codecs.register_error("strict", codecs.backslashreplace_errors) log_path = sys.argv[1] - lr = LogReader(log_path) + lr = LogReader(log_path, sort_by_time=True) for msg in lr: print(msg) diff --git a/tools/lib/robust_logreader.py b/tools/lib/robust_logreader.py index e534a7c8f0..c7feb6c3ed 100755 --- a/tools/lib/robust_logreader.py +++ b/tools/lib/robust_logreader.py @@ -13,7 +13,7 @@ from cereal import log as capnp_log class RobustLogReader(LogReader): - def __init__(self, fn, canonicalize=True, only_union_types=False): # pylint: disable=super-init-not-called + def __init__(self, fn, canonicalize=True, only_union_types=False, sort_by_time=False): # pylint: disable=super-init-not-called data_version = None _, ext = os.path.splitext(urllib.parse.urlparse(fn).path) with FileReader(fn) as f: @@ -45,7 +45,7 @@ class RobustLogReader(LogReader): while True: try: ents = capnp_log.Event.read_multiple_bytes(dat) - self._ents = list(ents) + self._ents = list(sorted(ents, key=lambda x: x.logMonoTime) if sort_by_time else ents) break except capnp.lib.capnp.KjException: if progress is None: diff --git a/tools/lib/route.py b/tools/lib/route.py index 8c03cd9cca..c87bd04b9e 100644 --- a/tools/lib/route.py +++ b/tools/lib/route.py @@ -7,9 +7,10 @@ from itertools import chain from tools.lib.auth_config import get_token from tools.lib.api import CommaApi -SEGMENT_NAME_RE = r'[a-z0-9]{16}[|_][0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2}--[0-9]+' -EXPLORER_FILE_RE = r'^({})--([a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE) -OP_SEGMENT_DIR_RE = r'^({})$'.format(SEGMENT_NAME_RE) +ROUTE_NAME_RE = r'(?P[a-z0-9]{16})[|_/](?P[0-9]{4}-[0-9]{2}-[0-9]{2}--[0-9]{2}-[0-9]{2}-[0-9]{2})' +SEGMENT_NAME_RE = r'{}(?:--|/)(?P[0-9]+)'.format(ROUTE_NAME_RE) +EXPLORER_FILE_RE = r'^(?P{})--(?P[a-z]+\.[a-z0-9]+)$'.format(SEGMENT_NAME_RE) +OP_SEGMENT_DIR_RE = r'^(?P{})$'.format(SEGMENT_NAME_RE) QLOG_FILENAMES = ['qlog.bz2'] QCAMERA_FILENAMES = ['qcamera.ts'] @@ -18,48 +19,52 @@ CAMERA_FILENAMES = ['fcamera.hevc', 'video.hevc'] DCAMERA_FILENAMES = ['dcamera.hevc'] ECAMERA_FILENAMES = ['ecamera.hevc'] -class Route(object): - def __init__(self, route_name, data_dir=None): +class Route: + def __init__(self, name, data_dir=None): + self._name = RouteName(name) self.files = None - self.route_name = route_name.replace('_', '|') if data_dir is not None: self._segments = self._get_segments_local(data_dir) else: self._segments = self._get_segments_remote() - self.max_seg_number = self._segments[-1].canonical_name.segment_num + self.max_seg_number = self._segments[-1].name.segment_num + + @property + def name(self): + return self._name @property def segments(self): return self._segments def log_paths(self): - log_path_by_seg_num = {s.canonical_name.segment_num: s.log_path for s in self._segments} + log_path_by_seg_num = {s.name.segment_num: s.log_path for s in self._segments} return [log_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def qlog_paths(self): - qlog_path_by_seg_num = {s.canonical_name.segment_num: s.qlog_path for s in self._segments} + qlog_path_by_seg_num = {s.name.segment_num: s.qlog_path for s in self._segments} return [qlog_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def camera_paths(self): - camera_path_by_seg_num = {s.canonical_name.segment_num: s.camera_path for s in self._segments} + camera_path_by_seg_num = {s.name.segment_num: s.camera_path for s in self._segments} return [camera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def dcamera_paths(self): - dcamera_path_by_seg_num = {s.canonical_name.segment_num: s.dcamera_path for s in self._segments} + dcamera_path_by_seg_num = {s.name.segment_num: s.dcamera_path for s in self._segments} return [dcamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def ecamera_paths(self): - ecamera_path_by_seg_num = {s.canonical_name.segment_num: s.ecamera_path for s in self._segments} + ecamera_path_by_seg_num = {s.name.segment_num: s.ecamera_path for s in self._segments} return [ecamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] def qcamera_paths(self): - qcamera_path_by_seg_num = {s.canonical_name.segment_num: s.qcamera_path for s in self._segments} + qcamera_path_by_seg_num = {s.name.segment_num: s.qcamera_path for s in self._segments} return [qcamera_path_by_seg_num.get(i, None) for i in range(self.max_seg_number+1)] # TODO: refactor this, it's super repetitive def _get_segments_remote(self): api = CommaApi(get_token()) - route_files = api.get('v1/route/' + self.route_name + '/files') + route_files = api.get('v1/route/' + self.name.canonical_name + '/files') self.files = list(chain.from_iterable(route_files.values())) segments = {} @@ -67,7 +72,7 @@ class Route(object): _, dongle_id, time_str, segment_num, fn = urlparse(url).path.rsplit('/', maxsplit=4) segment_name = f'{dongle_id}|{time_str}--{segment_num}' if segments.get(segment_name): - segments[segment_name] = RouteSegment( + segments[segment_name] = Segment( segment_name, url if fn in LOG_FILENAMES else segments[segment_name].log_path, url if fn in QLOG_FILENAMES else segments[segment_name].qlog_path, @@ -77,7 +82,7 @@ class Route(object): url if fn in QCAMERA_FILENAMES else segments[segment_name].qcamera_path, ) else: - segments[segment_name] = RouteSegment( + segments[segment_name] = Segment( segment_name, url if fn in LOG_FILENAMES else None, url if fn in QLOG_FILENAMES else None, @@ -87,7 +92,7 @@ class Route(object): url if fn in QCAMERA_FILENAMES else None, ) - return sorted(segments.values(), key=lambda seg: seg.canonical_name.segment_num) + return sorted(segments.values(), key=lambda seg: seg.name.segment_num) def _get_segments_local(self, data_dir): files = os.listdir(data_dir) @@ -99,20 +104,21 @@ class Route(object): op_match = re.match(OP_SEGMENT_DIR_RE, f) if explorer_match: - segment_name, fn = explorer_match.groups() - if segment_name.replace('_', '|').startswith(self.route_name): + segment_name = explorer_match.group('segment_name') + fn = explorer_match.group('file_name') + if segment_name.replace('_', '|').startswith(self.name.canonical_name): segment_files[segment_name].append((fullpath, fn)) elif op_match and os.path.isdir(fullpath): - segment_name, = op_match.groups() - if segment_name.startswith(self.route_name): + segment_name = op_match.group('segment_name') + if segment_name.startswith(self.name.canonical_name): for seg_f in os.listdir(fullpath): segment_files[segment_name].append((os.path.join(fullpath, seg_f), seg_f)) - elif f == self.route_name: + elif f == self.name.canonical_name: for seg_num in os.listdir(fullpath): if not seg_num.isdigit(): continue - segment_name = '{}--{}'.format(self.route_name, seg_num) + segment_name = f'{self.name.canonical_name}--{seg_num}' for seg_f in os.listdir(os.path.join(fullpath, seg_num)): segment_files[segment_name].append((os.path.join(fullpath, seg_num, seg_f), seg_f)) @@ -149,15 +155,15 @@ class Route(object): except StopIteration: qcamera_path = None - segments.append(RouteSegment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) + segments.append(Segment(segment, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path)) if len(segments) == 0: - raise ValueError('Could not find segments for route {} in data directory {}'.format(self.route_name, data_dir)) - return sorted(segments, key=lambda seg: seg.canonical_name.segment_num) + raise ValueError(f'Could not find segments for route {self.name.canonical_name} in data directory {data_dir}') + return sorted(segments, key=lambda seg: seg.name.segment_num) -class RouteSegment(object): +class Segment: def __init__(self, name, log_path, qlog_path, camera_path, dcamera_path, ecamera_path, qcamera_path): - self._name = RouteSegmentName(name) + self._name = SegmentName(name) self.log_path = log_path self.qlog_path = qlog_path self.camera_path = camera_path @@ -167,21 +173,55 @@ class RouteSegment(object): @property def name(self): - return str(self._name) + return self._name + +class RouteName: + def __init__(self, name_str: str): + self._name_str = name_str + delim = next(c for c in self._name_str if c in ("|", "/")) + self._dongle_id, self._time_str = self._name_str.split(delim) + + assert len(self._dongle_id) == 16, self._name_str + assert len(self._time_str) == 20, self._name_str + self._canonical_name = f"{self._dongle_id}|{self._time_str}" @property - def canonical_name(self): - return self._name + def canonical_name(self) -> str: return self._canonical_name + + @property + def dongle_id(self) -> str: return self._dongle_id -class RouteSegmentName(object): - def __init__(self, name_str): - self._segment_name_str = name_str - self._route_name_str, num_str = self._segment_name_str.rsplit("--", 1) - self._num = int(num_str) + @property + def time_str(self) -> str: return self._time_str + + def __str__(self) -> str: return self._canonical_name + +class SegmentName: + # TODO: add constructor that takes dongle_id, time_str, segment_num and then create instances + # of this class instead of manually constructing a segment name (use canonical_name prop instead) + def __init__(self, name_str: str, allow_route_name=False): + self._name_str = name_str + seg_num_delim = "--" if self._name_str.count("--") == 2 else "/" + name_parts = self._name_str.rsplit(seg_num_delim, 1) + if allow_route_name and len(name_parts) == 1: + name_parts.append("-1") # no segment number + self._route_name = RouteName(name_parts[0]) + self._num = int(name_parts[1]) + self._canonical_name = f"{self._route_name._dongle_id}|{self._route_name._time_str}--{self._num}" + + @property + def canonical_name(self) -> str: return self._canonical_name + + @property + def dongle_id(self) -> str: return self._route_name.dongle_id + + @property + def time_str(self) -> str: return self._route_name.time_str + + @property + def segment_num(self) -> int: return self._num @property - def segment_num(self): - return self._num + def route_name(self) -> RouteName: return self._route_name - def __str__(self): - return self._segment_name_str + def __str__(self) -> str: return self._canonical_name diff --git a/tools/lib/url_file.py b/tools/lib/url_file.py index 2161770d03..8ad2f96b3a 100644 --- a/tools/lib/url_file.py +++ b/tools/lib/url_file.py @@ -22,7 +22,7 @@ def hash_256(link): return hsh -class URLFile(object): +class URLFile: _tlocal = threading.local() def __init__(self, url, debug=False, cache=None): @@ -70,7 +70,7 @@ class URLFile(object): return self._length file_length_path = os.path.join(CACHE_DIR, hash_256(self._url) + "_length") if os.path.exists(file_length_path) and not self._force_download: - with open(file_length_path, "r") as file_length: + with open(file_length_path) as file_length: content = file_length.read() self._length = int(content) return self._length @@ -156,7 +156,7 @@ class URLFile(object): if self._debug: t2 = time.time() if t2 - t1 > 0.1: - print("get %s %r %.f slow" % (self._url, headers, t2 - t1)) + print(f"get {self._url} {headers!r} {t2 - t1:.f} slow") response_code = c.getinfo(pycurl.RESPONSE_CODE) if response_code == 416: # Requested Range Not Satisfiable diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 9c8606022a..14037fb26d 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -1,6 +1,7 @@ #!/bin/bash -e -OP_ROOT=$(git rev-parse --show-toplevel) +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +ROOT="$(cd $DIR/../ && pwd)" # Install brew if required if [[ $(command -v brew) == "" ]]; then @@ -10,6 +11,7 @@ fi brew bundle --file=- <<-EOS brew "cmake" +brew "git-lfs" brew "zlib" brew "bzip2" brew "rust" @@ -37,6 +39,7 @@ elif [[ $SHELL == "/bin/bash" ]]; then RC_FILE="$HOME/.bash_profile" fi +# TODO: get rid of this somehow # Build requirements for macOS # https://github.com/pyenv/pyenv/issues/1740 # https://github.com/pyca/cryptography/blob/main/docs/installation.rst @@ -51,24 +54,17 @@ 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 + echo "source $ROOT/tools/openpilot_env.sh" >> $RC_FILE export PATH="$PATH:\"\$HOME/.cargo/bin\"" - source "$OP_ROOT/tools/openpilot_env.sh" + 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 -)" - -pip install pipenv==2020.8.13 -pipenv install --dev --deploy +# install python dependencies +$ROOT/update_requirements.sh echo echo "---- FINISH OPENPILOT SETUP ----" diff --git a/tools/openpilot_env.sh b/tools/openpilot_env.sh index 81ae00d64d..ac73cc8305 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,12 +8,10 @@ if [ -z "$OPENPILOT_ENV" ]; then # https://github.com/pyenv/pyenv/issues/1906 export PYENV_ROOT="$HOME/.pyenv" - unamestr=`uname` - if [[ "$unamestr" == 'Linux' ]]; then + if [[ "$(uname)" == 'Linux' ]]; then eval "$(pyenv init --path)" - 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 diff --git a/tools/plotjuggler/README.md b/tools/plotjuggler/README.md index 8ab150e821..2e79aecbca 100644 --- a/tools/plotjuggler/README.md +++ b/tools/plotjuggler/README.md @@ -4,24 +4,23 @@ ## Installation -**NOTE: this is Ubuntu only for now. Pull requests for macOS support are welcome.** - Once you've cloned and are in openpilot, this command will download PlotJuggler and install our plugins: -`cd tools/plotjuggler && ./install.sh` +`cd tools/plotjuggler && ./juggle.py --install` ## Usage ``` $ ./juggle.py -h -usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [route_name] [segment_number] [segment_count] +usage: juggle.py [-h] [--demo] [--qlog] [--can] [--stream] [--layout [LAYOUT]] [--install] + [route_or_segment_name] [segment_count] A helper to run PlotJuggler on openpilot routes positional arguments: - route_name The route name to plot (cabana share URL accepted) (default: None) - segment_number The index of the segment to plot (default: None) - segment_count The number of segments to plot (default: 1) + route_or_segment_name + The route or segment name to plot (cabana share URL accepted) (default: None) + segment_count The number of segments to plot (default: None) optional arguments: -h, --help show this help message and exit @@ -30,12 +29,17 @@ optional arguments: --can Parse CAN data (default: False) --stream Start PlotJuggler in streaming mode (default: False) --layout [LAYOUT] Run PlotJuggler with a pre-defined layout (default: None) + --install Install or update PlotJuggler + plugins (default: False) ``` -Example: +Examples using route name: `./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36"` +Examples using segment name: + +`./juggle.py "4cf7a6ad03080c90|2021-09-29--13-46-36--1"` + ## Streaming Explore live data from your car! Follow these steps to stream from your comma device to your laptop: diff --git a/tools/plotjuggler/install.sh b/tools/plotjuggler/install.sh deleted file mode 100755 index 27cb0dfb5d..0000000000 --- a/tools/plotjuggler/install.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -mkdir -p bin -cd bin - -for lib_name in libDataLoadRlog.so libDataStreamCereal.so plotjuggler; do - wget https://github.com/commaai/PlotJuggler/releases/download/latest/${lib_name}.tar.gz - tar -xf ${lib_name}.tar.gz - rm ${lib_name}.tar.gz -done diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index d488750701..7cafaf60ac 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -2,24 +2,51 @@ import os import sys import multiprocessing +import platform +import shutil import subprocess +import tarfile +import tempfile +import requests import argparse -from tempfile import NamedTemporaryFile from common.basedir import BASEDIR from selfdrive.test.process_replay.compare_logs import save_log from tools.lib.api import CommaApi from tools.lib.auth_config import get_token from tools.lib.robust_logreader import RobustLogReader -from tools.lib.route import Route +from tools.lib.route import Route, SegmentName from urllib.parse import urlparse, parse_qs juggle_dir = os.path.dirname(os.path.realpath(__file__)) DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" +RELEASES_URL="https://github.com/commaai/PlotJuggler/releases/download/latest" +INSTALL_DIR = os.path.join(juggle_dir, "bin") + + +def install(): + m = f"{platform.system()}-{platform.machine()}" + supported = ("Linux-x86_64", "Darwin-arm64", "Darwin-x86_64") + if m not in supported: + raise Exception(f"Unsupported platform: '{m}'. Supported platforms: {supported}") + + if os.path.exists(INSTALL_DIR): + shutil.rmtree(INSTALL_DIR) + os.mkdir(INSTALL_DIR) + + url = os.path.join(RELEASES_URL, m + ".tar.gz") + with requests.get(url, stream=True) as r, tempfile.NamedTemporaryFile() as tmp: + r.raise_for_status() + with open(tmp.name, 'wb') as tmpf: + for chunk in r.iter_content(chunk_size=1024*1024): + tmpf.write(chunk) + + with tarfile.open(tmp.name) as tar: + tar.extractall(path=INSTALL_DIR) + def load_segment(segment_name): - print(f"Loading {segment_name}") if segment_name is None: return [] @@ -29,46 +56,51 @@ def load_segment(segment_name): print(f"Error parsing {segment_name}: {e}") return [] + def start_juggler(fn=None, dbc=None, layout=None): env = os.environ.copy() env["BASEDIR"] = BASEDIR - pj = os.getenv("PLOTJUGGLER_PATH", os.path.join(juggle_dir, "bin/plotjuggler")) - + env["PATH"] = f"{INSTALL_DIR}:{os.getenv('PATH', '')}" if dbc: env["DBC_NAME"] = dbc - extra_args = [] + extra_args = "" if fn is not None: - extra_args.append(f'-d {fn}') - + extra_args += f" -d {fn}" if layout is not None: - extra_args.append(f'-l {layout}') + extra_args += f" -l {layout}" + + cmd = f'plotjuggler --plugin_folders {INSTALL_DIR}{extra_args}' + subprocess.call(cmd, shell=True, env=env, cwd=juggle_dir) - extra_args = " ".join(extra_args) - subprocess.call(f'{pj} --plugin_folders {os.path.join(juggle_dir, "bin")} {extra_args}', shell=True, env=env, cwd=juggle_dir) -def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): - if 'cabana' in route_name: - query = parse_qs(urlparse(route_name).query) +def juggle_route(route_or_segment_name, segment_count, qlog, can, layout): + segment_start = 0 + if 'cabana' in route_or_segment_name: + query = parse_qs(urlparse(route_or_segment_name).query) api = CommaApi(get_token()) logs = api.get(f'v1/route/{query["route"][0]}/log_urls?sig={query["sig"][0]}&exp={query["exp"][0]}') - elif route_name.startswith("http://") or route_name.startswith("https://") or os.path.isfile(route_name): - logs = [route_name] + elif route_or_segment_name.startswith("http://") or route_or_segment_name.startswith("https://") or os.path.isfile(route_or_segment_name): + logs = [route_or_segment_name] else: - r = Route(route_name) + route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) + segment_start = max(route_or_segment_name.segment_num, 0) + + if route_or_segment_name.segment_num != -1 and segment_count is None: + segment_count = 1 + + r = Route(route_or_segment_name.route_name.canonical_name) logs = r.qlog_paths() if qlog else r.log_paths() - if segment_number is not None: - logs = logs[segment_number:segment_number+segment_count] + segment_end = segment_start + segment_count if segment_count else -1 + logs = logs[segment_start:segment_end] if None in logs: - fallback_answer = input("At least one of the rlogs in this segment does not exist, would you like to use the qlogs? (y/n) : ") - if fallback_answer == 'y': - logs = r.qlog_paths() - if segment_number is not None: - logs = logs[segment_number:segment_number+segment_count] + ans = input(f"{logs.count(None)}/{len(logs)} of the rlogs in this segment are missing, would you like to fall back to the qlogs? (y/n) ") + if ans == 'y': + logs = r.qlog_paths()[segment_start:segment_end] else: - print(f"Please try a different {'segment' if segment_number is not None else 'route'}") + print("Please try a different route or segment") return all_data = [] @@ -85,17 +117,17 @@ def juggle_route(route_name, segment_number, segment_count, qlog, can, layout): try: DBC = __import__(f"selfdrive.car.{cp.carParams.carName}.values", fromlist=['DBC']).DBC dbc = DBC[cp.carParams.carFingerprint]['pt'] - except (ImportError, KeyError, AttributeError): + except Exception: pass break - tempfile = NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) - save_log(tempfile.name, all_data, compress=False) - del all_data + with tempfile.NamedTemporaryFile(suffix='.rlog', dir=juggle_dir) as tmp: + save_log(tmp.name, all_data, compress=False) + del all_data + start_juggler(tmp.name, dbc, layout) - start_juggler(tempfile.name, dbc, layout) -def get_arg_parser(): +if __name__ == "__main__": parser = argparse.ArgumentParser(description="A helper to run PlotJuggler on openpilot routes", formatter_class=argparse.ArgumentDefaultsHelpFormatter) @@ -104,20 +136,21 @@ def get_arg_parser(): parser.add_argument("--can", action="store_true", help="Parse CAN data") parser.add_argument("--stream", action="store_true", help="Start PlotJuggler in streaming mode") parser.add_argument("--layout", nargs='?', help="Run PlotJuggler with a pre-defined layout") - parser.add_argument("route_name", nargs='?', help="The route name to plot (cabana share URL accepted)") - parser.add_argument("segment_number", type=int, nargs='?', help="The index of the segment to plot") - parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot", default=1) - return parser + parser.add_argument("--install", action="store_true", help="Install or update PlotJuggler + plugins") + parser.add_argument("route_or_segment_name", nargs='?', help="The route or segment name to plot (cabana share URL accepted)") + parser.add_argument("segment_count", type=int, nargs='?', help="The number of segments to plot") -if __name__ == "__main__": - arg_parser = get_arg_parser() if len(sys.argv) == 1: - arg_parser.print_help() + parser.print_help() + sys.exit() + args = parser.parse_args() + + if args.install: + install() sys.exit() - args = arg_parser.parse_args(sys.argv[1:]) if args.stream: start_juggler(layout=args.layout) else: - route = DEMO_ROUTE if args.demo else args.route_name.strip() - juggle_route(route, args.segment_number, args.segment_count, args.qlog, args.can, args.layout) + route_or_segment_name = DEMO_ROUTE if args.demo else args.route_or_segment_name.strip() + juggle_route(route_or_segment_name, args.segment_count, args.qlog, args.can, args.layout) diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index 3e695c1990..edaec9c80a 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -7,27 +7,20 @@ import unittest from common.basedir import BASEDIR from common.timeout import Timeout -from selfdrive.test.openpilotci import get_url +from tools.plotjuggler.juggle import install class TestPlotJuggler(unittest.TestCase): - def test_install(self): - exit_code = os.system(os.path.join(BASEDIR, "tools/plotjuggler/install.sh")) - self.assertEqual(exit_code, 0) + def test_demo(self): + install() - def test_run(self): + pj = os.path.join(BASEDIR, "tools/plotjuggler/juggle.py") + p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', + stderr=subprocess.PIPE, shell=True, start_new_session=True) - test_url = get_url("ffccc77938ddbc44|2021-01-04--16-55-41", 0) - - # Launch PlotJuggler with the executable in the bin directory - os.environ["PLOTJUGGLER_PATH"] = f'{os.path.join(BASEDIR, "tools/plotjuggler/bin/plotjuggler")}' - p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {os.path.join(BASEDIR, "tools/plotjuggler/juggle.py")} \ - "{test_url}"', stderr=subprocess.PIPE, shell=True, - start_new_session=True) - - # Wait max 60 seconds for the "Done reading Rlog data" signal from the plugin + # Wait for "Done reading Rlog data" signal from the plugin output = "\n" - with Timeout(120, error_msg=output): + with Timeout(180, error_msg=output): while output.splitlines()[-1] != "Done reading Rlog data": output += p.stderr.readline().decode("utf-8") diff --git a/tools/replay/lib/ui_helpers.py b/tools/replay/lib/ui_helpers.py index 0573973f9f..7410c107c4 100644 --- a/tools/replay/lib/ui_helpers.py +++ b/tools/replay/lib/ui_helpers.py @@ -152,7 +152,7 @@ def init_plots(arr, name_to_arr_idx, plot_xlims, plot_ylims, plot_names, plot_co plots.append(plot) idxs.append(name_to_arr_idx[item]) plot_select.append(i) - axs[i].set_title(", ".join("%s (%s)" % (nm, cl) + axs[i].set_title(", ".join(f"{nm} ({cl})" for (nm, cl) in zip(pl_list, plot_colors[i])), fontsize=10) axs[i].tick_params(axis="x", colors="white") axs[i].tick_params(axis="y", colors="white") diff --git a/tools/scripts/fetch_image_from_route.py b/tools/scripts/fetch_image_from_route.py index e9111d9de2..39751ba551 100755 --- a/tools/scripts/fetch_image_from_route.py +++ b/tools/scripts/fetch_image_from_route.py @@ -2,7 +2,7 @@ import sys if len(sys.argv) < 4: - print("%s " % sys.argv[0]) + print(f"{sys.argv[0]} ") print('example: ./fetch_image_from_route.py "02c45f73a2e5c6e9|2020-06-01--18-03-08" 3 500') exit(0) @@ -33,5 +33,5 @@ if frame >= fr.frame_count: im = Image.fromarray(fr.get(frame, count=1, pix_fmt="rgb24")[0]) fn = "uxxx_"+route.replace("|", "_")+"_%d_%d.png" % (segment, frame) im.save(fn) -print("saved %s" % fn) +print(f"saved {fn}") diff --git a/tools/scripts/save_ubloxraw_stream.py b/tools/scripts/save_ubloxraw_stream.py index 518c4ecaf6..3fefd45ba8 100644 --- a/tools/scripts/save_ubloxraw_stream.py +++ b/tools/scripts/save_ubloxraw_stream.py @@ -35,7 +35,7 @@ def main(argv): args.data_dir = os.path.dirname(args.data_dir) route = Route(args.route_name, args.data_dir) - lr = MultiLogIterator(route.log_paths(), wraparound=False) + lr = MultiLogIterator(route.log_paths()) with open(args.out_path, 'wb') as f: try: @@ -52,7 +52,7 @@ def main(argv): i += 1 except StopIteration: print('All done') - print('Writed {} msgs'.format(i)) + print(f'Writed {i} msgs') if __name__ == "__main__": diff --git a/tools/scripts/setup_ssh_keys.py b/tools/scripts/setup_ssh_keys.py index 0eb44dbd59..5b5ebfbac5 100755 --- a/tools/scripts/setup_ssh_keys.py +++ b/tools/scripts/setup_ssh_keys.py @@ -7,7 +7,7 @@ import sys if __name__ == "__main__": if len(sys.argv) < 2: - print("%s " % sys.argv[0]) + print(f"{sys.argv[0]} ") exit(1) username = sys.argv[1] diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index 9924a40b11..2e19faefcd 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -93,7 +93,7 @@ class Camerad: rgb_cl = cl_array.to_device(self.queue, rgb) yuv_cl = cl_array.empty_like(rgb_cl) self.krnl(self.queue, (np.int32(self.Wdiv4), np.int32(self.Hdiv4)), None, rgb_cl.data, yuv_cl.data).wait() - yuv = np.resize(yuv_cl.get(), np.int32((rgb.size / 2))) + yuv = np.resize(yuv_cl.get(), np.int32(rgb.size / 2)) eof = self.frame_id * 0.05 # TODO: remove RGB send once the last RGB vipc subscriber is removed diff --git a/tools/sim/lib/manual_ctrl.py b/tools/sim/lib/manual_ctrl.py index d2111ad150..7c47e2ba4a 100755 --- a/tools/sim/lib/manual_ctrl.py +++ b/tools/sim/lib/manual_ctrl.py @@ -10,7 +10,7 @@ from typing import NoReturn print('Available devices:') for fn in os.listdir('/dev/input'): if fn.startswith('js'): - print(' /dev/input/%s' % (fn)) + print(f' /dev/input/{fn}') # We'll store the states here. axis_states = {} @@ -94,7 +94,7 @@ button_map = [] def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: # Open the joystick device. fn = '/dev/input/js0' - print('Opening %s...' % fn) + print(f'Opening {fn}...') jsdev = open(fn, 'rb') # Get the device name. @@ -102,7 +102,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: buf = array.array('B', [0] * 64) ioctl(jsdev, 0x80006a13 + (0x10000 * len(buf)), buf) # JSIOCGNAME(len) js_name = buf.tobytes().rstrip(b'\x00').decode('utf-8') - print('Device name: %s' % js_name) + print(f'Device name: {js_name}') # Get number of axes and buttons. buf = array.array('B', [0]) @@ -118,7 +118,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: ioctl(jsdev, 0x80406a32, buf) # JSIOCGAXMAP for _axis in buf[:num_axes]: - axis_name = axis_names.get(_axis, 'unknown(0x%02x)' % _axis) + axis_name = axis_names.get(_axis, f'unknown(0x{_axis:02x})') axis_map.append(axis_name) axis_states[axis_name] = 0.0 @@ -127,7 +127,7 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: ioctl(jsdev, 0x80406a34, buf) # JSIOCGBTNMAP for btn in buf[:num_buttons]: - btn_name = button_names.get(btn, 'unknown(0x%03x)' % btn) + btn_name = button_names.get(btn, f'unknown(0x{btn:03x})') button_map.append(btn_name) button_states[btn_name] = 0 @@ -153,19 +153,19 @@ def wheel_poll_thread(q: 'Queue[str]') -> NoReturn: fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put("throttle_%f" % normalized) + q.put(f"throttle_{normalized:f}") elif axis == "rz": # brake fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = (1 - fvalue) * 50 - q.put("brake_%f" % normalized) + q.put(f"brake_{normalized:f}") elif axis == "x": # steer angle fvalue = value / 32767.0 axis_states[axis] = fvalue normalized = fvalue - q.put("steer_%f" % normalized) + q.put(f"steer_{normalized:f}") elif mtype & 0x01: # buttons if value == 1: # press down diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index b64c7cac3e..2871f07167 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() { @@ -10,6 +15,7 @@ function install_ubuntu_common_requirements() { build-essential \ clang \ cmake \ + doxygen \ make \ cppcheck \ libtool \ @@ -70,7 +76,7 @@ function install_ubuntu_common_requirements() { libqt5x11extras5-dev \ libreadline-dev \ libdw1 \ - doxygen + valgrind } # Install Ubuntu 21.10 packages @@ -118,38 +124,16 @@ 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:" diff --git a/update_requirements.sh b/update_requirements.sh index be7d53f98b..bece43a51a 100755 --- a/update_requirements.sh +++ b/update_requirements.sh @@ -1,17 +1,12 @@ #!/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 +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if ! command -v "pyenv" > /dev/null 2>&1; then + echo "installing pyenv..." + 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)" @@ -19,7 +14,7 @@ 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} + CONFIGURE_OPTS="--enable-shared" pyenv install -f ${PYENV_PYTHON_VERSION} fi if ! command -v pipenv &> /dev/null; then @@ -29,24 +24,27 @@ fi 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) +fi