diff --git a/.github/workflows/prebuilt.yaml b/.github/workflows/prebuilt.yaml index c8b4c51e38..1fd093e2ac 100644 --- a/.github/workflows/prebuilt.yaml +++ b/.github/workflows/prebuilt.yaml @@ -16,7 +16,6 @@ jobs: build_prebuilt: name: build prebuilt runs-on: ubuntu-20.04 - timeout-minutes: 60 if: github.repository == 'commaai/openpilot' env: IMAGE_NAME: openpilot-prebuilt diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 8df89dcc38..7f83f61e0a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -8,7 +8,6 @@ jobs: build_masterci: name: build master-ci runs-on: ubuntu-20.04 - timeout-minutes: 60 if: github.repository == 'commaai/openpilot' steps: - name: Wait for green check mark diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index d84779103b..5873e7aee6 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: @@ -32,7 +32,6 @@ jobs: build_release: name: build release runs-on: ubuntu-20.04 - timeout-minutes: 30 env: STRIPPED_DIR: /tmp/releasepilot steps: @@ -61,12 +60,11 @@ jobs: cp .pylintrc $STRIPPED_DIR cp mypy.ini $STRIPPED_DIR cd $STRIPPED_DIR - ${{ env.RUN }} "SKIP=test_translations pre-commit run --all" + ${{ env.RUN }} "pre-commit run --all" build_all: name: build all runs-on: ubuntu-20.04 - timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: @@ -84,7 +82,6 @@ jobs: #build_mac: # name: build macos # runs-on: macos-latest - # timeout-minutes: 60 # steps: # - uses: actions/checkout@v3 # with: @@ -105,7 +102,7 @@ jobs: # /usr/local/Cellar # ~/github_brew_cache_entries.txt # /tmp/scons_cache - # key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'Pipfile*') }} + # key: macos-${{ hashFiles('tools/mac_setup.sh', 'update_requirements.sh', 'poetry.lock') }} # restore-keys: macos- # - name: Brew link restored dependencies # run: | @@ -121,11 +118,11 @@ jobs: # - name: Build openpilot # run: | # source tools/openpilot_env.sh - # pipenv run selfdrive/manager/build.py + # poetry run selfdrive/manager/build.py # # # cleanup scons cache # rm -rf /tmp/scons_cache/ - # pipenv run scons -j$(nproc) --cache-populate + # poetry run scons -j$(nproc) --cache-populate # - name: Remove pre-existing Homebrew packages for caching # if: steps.dependency-cache.outputs.cache-hit != 'true' # run: | @@ -141,7 +138,6 @@ jobs: docker_push: name: docker push runs-on: ubuntu-20.04 - timeout-minutes: 22 if: github.ref == 'refs/heads/master' && github.event_name != 'pull_request' && github.repository == 'commaai/openpilot' needs: static_analysis # hack to ensure slow tests run first since this and static_analysis are fast steps: @@ -150,14 +146,12 @@ jobs: submodules: true - name: Build Docker image run: eval "$BUILD" - timeout-minutes: 13 - name: Push to container registry run: | $DOCKER_LOGIN docker push $DOCKER_REGISTRY/$BASE_IMAGE:latest - name: Build CL Docker image run: eval "$BUILD_CL" - timeout-minutes: 4 - name: Push to container registry run: | $DOCKER_LOGIN @@ -166,7 +160,6 @@ jobs: static_analysis: name: static analysis runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -174,13 +167,11 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: pre-commit - timeout-minutes: 5 run: ${{ env.RUN }} "pre-commit run --all" valgrind: name: valgrind runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -198,7 +189,6 @@ jobs: unit_tests: name: unit tests runs-on: ubuntu-20.04 - timeout-minutes: 30 steps: - uses: actions/checkout@v3 with: @@ -207,7 +197,6 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Run unit tests - timeout-minutes: 15 run: | ${{ env.RUN }} "export SKIP_LONG_TESTS=1 && \ $UNIT_TEST common && \ @@ -215,10 +204,12 @@ jobs: $UNIT_TEST selfdrive/boardd && \ $UNIT_TEST selfdrive/controls && \ $UNIT_TEST selfdrive/monitoring && \ - $UNIT_TEST selfdrive/loggerd && \ + $UNIT_TEST system/loggerd && \ $UNIT_TEST selfdrive/car && \ $UNIT_TEST selfdrive/locationd && \ + $UNIT_TEST system/ubloxd && \ selfdrive/locationd/test/_test_locationd_lib.py && \ + ./system/ubloxd/tests/test_glonass_runner && \ $UNIT_TEST selfdrive/athena && \ $UNIT_TEST selfdrive/thermald && \ $UNIT_TEST system/hardware/tici && \ @@ -229,9 +220,10 @@ jobs: ./common/tests/test_util && \ ./common/tests/test_swaglog && \ ./selfdrive/boardd/tests/test_boardd_usbprotocol && \ - ./selfdrive/loggerd/tests/test_logger &&\ + ./system/loggerd/tests/test_logger &&\ ./system/proclogd/tests/test_proclog && \ ./tools/replay/tests/test_replay && \ + ./tools/cabana/tests/test_cabana && \ ./system/camerad/test/ae_gray_test && \ coverage xml" - name: "Upload coverage to Codecov" @@ -240,7 +232,6 @@ jobs: process_replay: name: process replay runs-on: ubuntu-20.04 - timeout-minutes: 25 steps: - uses: actions/checkout@v3 with: @@ -260,6 +251,7 @@ jobs: ${{ env.RUN }} "CI=1 coverage run selfdrive/test/process_replay/test_processes.py -j$(nproc) && \ coverage xml" - name: Print diff + id: print-diff if: always() run: cat selfdrive/test/process_replay/diff.txt - uses: actions/upload-artifact@v2 @@ -269,7 +261,7 @@ jobs: name: process_replay_diff.txt path: selfdrive/test/process_replay/diff.txt - name: Upload reference logs - if: ${{ failure() && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} + if: ${{ failure() && steps.print-diff.outcome == 'success' && github.event_name == 'pull_request' && github.repository == 'commaai/openpilot' && env.AZURE_TOKEN != '' }} run: | ${{ env.RUN }} "CI=1 AZURE_TOKEN='$AZURE_TOKEN' python selfdrive/test/process_replay/test_processes.py -j$(nproc) --upload-only" - name: "Upload coverage to Codecov" @@ -278,7 +270,6 @@ jobs: test_modeld: name: model tests runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -291,12 +282,10 @@ jobs: run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run model replay with ONNX - timeout-minutes: 2 run: | - ${{ env.RUN_CL }} "ONNXCPU=1 CI=1 coverage run selfdrive/test/process_replay/model_replay.py && \ + ${{ env.RUN_CL }} "ONNXCPU=1 CI=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ coverage xml" - name: Run unit tests - timeout-minutes: 5 run: | ${{ env.RUN_CL }} "$UNIT_TEST selfdrive/modeld && \ coverage xml" @@ -306,7 +295,6 @@ jobs: test_longitudinal: name: longitudinal runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -321,7 +309,6 @@ jobs: cd selfdrive/test/longitudinal_maneuvers && \ coverage run ./test_longitudinal.py && \ coverage xml" - timeout-minutes: 2 - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v2 - uses: actions/upload-artifact@v2 @@ -334,7 +321,6 @@ jobs: test_cars: name: cars runs-on: ubuntu-20.04 - timeout-minutes: 20 strategy: fail-fast: false matrix: @@ -353,7 +339,6 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models - timeout-minutes: 12 run: | ${{ env.RUN }} "coverage run -m pytest selfdrive/car/tests/test_models.py && \ coverage xml && \ @@ -367,7 +352,6 @@ jobs: car_docs_diff: name: PR comments runs-on: ubuntu-20.04 - timeout-minutes: 20 if: github.event_name == 'pull_request' steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index 549a2f4195..3b32d3c790 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -7,7 +7,7 @@ on: pull_request: concurrency: - group: ${{ github.workflow }}-${{ github.ref != 'refs/heads/master' && github.ref || github.run_id }}-${{ github.event_name }} + group: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' && github.run_id || github.head_ref || github.ref }}-${{ github.workflow }}-${{ github.event_name }} cancel-in-progress: true env: @@ -30,7 +30,6 @@ jobs: plotjuggler: name: plotjuggler runs-on: ubuntu-20.04 - timeout-minutes: 20 steps: - uses: actions/checkout@v3 with: @@ -38,7 +37,6 @@ jobs: - name: Build Docker image run: eval "$BUILD" - name: Unit test - timeout-minutes: 2 run: | ${{ env.RUN }} "scons -j$(nproc) --directory=/tmp/openpilot/cereal && \ apt-get update && \ @@ -49,7 +47,6 @@ jobs: simulator: name: simulator runs-on: ubuntu-20.04 - timeout-minutes: 30 env: IMAGE_NAME: openpilot-sim if: github.repository == 'commaai/openpilot' @@ -74,7 +71,6 @@ jobs: docs: name: build docs runs-on: ubuntu-20.04 - timeout-minutes: 25 steps: - uses: actions/checkout@v3 with: diff --git a/.gitignore b/.gitignore index 062358ef24..be6629f212 100644 --- a/.gitignore +++ b/.gitignore @@ -46,12 +46,10 @@ selfdrive/mapd/default_speeds_by_region.json system/proclogd/proclogd selfdrive/ui/_ui selfdrive/test/longitudinal_maneuvers/out -selfdrive/visiond/visiond -selfdrive/sensord/_gpsd -selfdrive/sensord/_sensord system/camerad/camerad system/camerad/test/ae_gray_test selfdrive/modeld/_modeld +selfdrive/modeld/_navmodeld selfdrive/modeld/_dmonitoringmodeld /src/ @@ -83,3 +81,5 @@ selfdrive/modeld/models/*.thneed build/ !**/.gitkeep + +poetry.toml diff --git a/.gitmodules b/.gitmodules index 544c95c943..26f93ef164 100644 --- a/.gitmodules +++ b/.gitmodules @@ -18,4 +18,4 @@ url = ../../commaai/body.git [submodule "tinygrad"] path = tinygrad_repo - url = ../../commaai/tinygrad.git + url = https://github.com/geohot/tinygrad.git diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 85c24b911b..1779947ff9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,7 @@ repos: rev: v4.1.0 hooks: - id: check-ast - exclude: '^(pyextra)/' + exclude: '^(third_party)/' - id: check-json - id: check-xml - id: check-yaml @@ -19,7 +19,7 @@ repos: rev: v2.2.1 hooks: - id: codespell - exclude: '^(pyextra/)|(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(include/)|(selfdrive/ui/translations/.*.ts)|(selfdrive/controls/lib/cluster)' + exclude: '^(third_party/)|(body/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(selfdrive/ui/translations/.*.ts)|(poetry.lock)' args: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup @@ -31,12 +31,12 @@ repos: entry: mypy language: system types: [python] - exclude: '^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' + exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/)' - repo: https://github.com/PyCQA/flake8 rev: 4.0.1 hooks: - id: flake8 - exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(selfdrive/debug/)/' + exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(opendbc/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(selfdrive/debug/)/' additional_dependencies: ['flake8-no-implicit-concat'] args: - --indent-size=2 @@ -51,7 +51,7 @@ repos: entry: pylint language: system types: [python] - exclude: '^(pyextra/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' + exclude: '^(third_party/)|(cereal/)|(rednose/)|(panda/)|(laika/)|(laika_repo/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)' args: - -j0 - -rn @@ -64,7 +64,7 @@ repos: entry: cppcheck language: system types: [c++] - exclude: '^(third_party/)|(pyextra/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)' + exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(tools/)|(selfdrive/modeld/thneed/debug/)|(selfdrive/modeld/test/)|(selfdrive/camerad/test/)|(installer/)' args: - --error-exitcode=1 - --language=c++ @@ -78,3 +78,7 @@ repos: entry: selfdrive/ui/tests/test_translations.py language: script pass_filenames: false +- repo: https://github.com/python-poetry/poetry + rev: '1.2.2' + hooks: + - id: poetry-check diff --git a/Dockerfile.openpilot b/Dockerfile.openpilot index 102da78d7d..acc0fcc784 100644 --- a/Dockerfile.openpilot +++ b/Dockerfile.openpilot @@ -10,7 +10,6 @@ WORKDIR ${OPENPILOT_PATH} COPY SConstruct ${OPENPILOT_PATH} -COPY ./pyextra ${OPENPILOT_PATH}/pyextra COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./laika ${OPENPILOT_PATH}/laika diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index 0de3008baf..4cd82259e2 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -12,19 +12,19 @@ ENV LANG en_US.UTF-8 ENV LANGUAGE en_US:en ENV LC_ALL en_US.UTF-8 -ENV PIPENV_SYSTEM=1 +ENV POETRY_VIRTUALENVS_CREATE=false ENV PYENV_VERSION=3.8.10 ENV PYENV_ROOT="/root/.pyenv" ENV PATH="$PYENV_ROOT/bin:$PYENV_ROOT/shims:$PATH" -COPY Pipfile Pipfile.lock .python-version update_requirements.sh /tmp/ +COPY pyproject.toml poetry.lock .python-version update_requirements.sh /tmp/ COPY tools/ubuntu_setup.sh /tmp/tools/ RUN cd /tmp && \ tools/ubuntu_setup.sh && \ rm -rf /var/lib/apt/lists/* && \ rm -rf /tmp/* && \ rm -rf /root/.cache && \ - pip uninstall -y pipenv && \ + pip uninstall -y poetry && \ # remove unused architectures from gcc for panda cd /usr/lib/gcc/arm-none-eabi/9.2.1 && \ rm -rf arm/ && \ diff --git a/Dockerfile.openpilot_base_cl b/Dockerfile.openpilot_base_cl index 7652b7e4e6..4c8ecfc78d 100644 --- a/Dockerfile.openpilot_base_cl +++ b/Dockerfile.openpilot_base_cl @@ -17,7 +17,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends\ # Intel OpenCL driver ARG INTEL_DRIVER=l_opencl_p_18.1.0.015.tgz -ARG INTEL_DRIVER_URL=http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532 +ARG INTEL_DRIVER_URL=https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532 RUN mkdir -p /tmp/opencl-driver-intel WORKDIR /tmp/opencl-driver-intel RUN echo INTEL_DRIVER is $INTEL_DRIVER && \ diff --git a/Jenkinsfile b/Jenkinsfile index b9b9eda667..4e80b5fa3c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -11,6 +11,9 @@ export SOURCE_DIR=${env.SOURCE_DIR} export GIT_BRANCH=${env.GIT_BRANCH} export GIT_COMMIT=${env.GIT_COMMIT} export AZURE_TOKEN='${env.AZURE_TOKEN}' +export MAPBOX_TOKEN='${env.MAPBOX_TOKEN}' + +export GIT_SSH_COMMAND="ssh -i /data/gitkey" source ~/.bash_profile if [ -f /TICI ]; then @@ -47,20 +50,34 @@ pipeline { TEST_DIR = "/data/openpilot" SOURCE_DIR = "/data/openpilot_source/" AZURE_TOKEN = credentials('azure_token') + MAPBOX_TOKEN = credentials('mapbox_token') } options { - timeout(time: 4, unit: 'HOURS') + timeout(time: 3, unit: 'HOURS') + disableConcurrentBuilds(abortPrevious: env.BRANCH_NAME != 'master') } stages { - stage('build release3') { + stage('build release3-staging') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } when { branch 'devel-staging' } steps { - phone_steps("tici", [ - ["build release3-staging & dashcam3-staging", "PUSH=1 $SOURCE_DIR/release/build_release.sh"], + phone_steps("tici-needs-can", [ + ["build release3-staging & dashcam3-staging", "RELEASE_BRANCH=release3-staging DASHCAM_BRANCH=dashcam3-staging $SOURCE_DIR/release/build_release.sh"], + ]) + } + } + + stage('build nightly') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + when { + branch 'master-ci' + } + steps { + phone_steps("tici-needs-can", [ + ["build nightly", "RELEASE_BRANCH=nightly $SOURCE_DIR/release/build_release.sh"], ]) } } @@ -78,6 +95,7 @@ pipeline { parallel { + /* stage('simulator') { agent { dockerfile { @@ -87,7 +105,8 @@ pipeline { } } steps { - sh "git config --global --add safe.directory ${WORKSPACE}" + sh "git config --global --add safe.directory '*'" + sh "git submodule update --init --recursive" sh "git lfs pull" lock(resource: "", label: "simulator", inversePrecedence: true, quantity: 1) { sh "${WORKSPACE}/tools/sim/build_container.sh" @@ -104,6 +123,7 @@ pipeline { } } } + */ stage('build') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } @@ -111,36 +131,45 @@ pipeline { R3_PUSH = "${env.BRANCH_NAME == 'master' ? '1' : ' '}" } steps { - phone_steps("tici", [ + phone_steps("tici-needs-can", [ ["build master-ci", "cd $SOURCE_DIR/release && TARGET_DIR=$TEST_DIR EXTRA_FILES='tools/' ./build_devel.sh"], ["build openpilot", "cd selfdrive/manager && ./build.py"], ["check dirty", "release/check-dirty.sh"], - ["test manager", "python selfdrive/manager/test/test_manager.py"], ["onroad tests", "cd selfdrive/test/ && ./test_onroad.py"], ["test car interfaces", "cd selfdrive/car/tests/ && ./test_car_interfaces.py"], ]) } } + stage('loopback-tests') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-loopback", [ + ["build openpilot", "cd selfdrive/manager && ./build.py"], + ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], + ]) + } + } + stage('HW + Unit Tests') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici2", [ + phone_steps("tici-common", [ ["build", "cd selfdrive/manager && ./build.py"], ["test power draw", "python system/hardware/tici/test_power_draw.py"], - ["test boardd loopback", "python selfdrive/boardd/tests/test_boardd_loopback.py"], - ["test loggerd", "python selfdrive/loggerd/tests/test_loggerd.py"], - ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python selfdrive/loggerd/tests/test_encoder.py"], - ["test sensord", "python selfdrive/sensord/tests/test_sensord.py"], - ["test pigeond", "python selfdrive/sensord/tests/test_pigeond.py"], + ["test loggerd", "python system/loggerd/tests/test_loggerd.py"], + ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib python system/loggerd/tests/test_encoder.py"], + ["test pigeond", "python system/sensord/tests/test_pigeond.py"], + ["test manager", "python selfdrive/manager/test/test_manager.py"], + ["test pandad", "python selfdrive/boardd/tests/test_pandad.py"], ]) } } - stage('camerad') { + stage('camerad-ar') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici-party", [ + phone_steps("tici-ar0231", [ ["build", "cd selfdrive/manager && ./build.py"], ["test camerad", "python system/camerad/test/test_camerad.py"], ["test exposure", "python system/camerad/test/test_exposure.py"], @@ -148,17 +177,43 @@ pipeline { } } - stage('replay') { + stage('camerad-ox') { agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } steps { - phone_steps("tici3", [ + phone_steps("tici-ox03c10", [ ["build", "cd selfdrive/manager && ./build.py"], - ["model replay", "cd selfdrive/test/process_replay && ./model_replay.py"], + ["test camerad", "python system/camerad/test/test_camerad.py"], + ["test exposure", "python system/camerad/test/test_exposure.py"], ]) } } - } + stage('sensord') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-lsmc", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd system/sensord/tests && python -m unittest test_sensord.py"], + ]) + phone_steps("tici-bmx-lsm", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["test sensord", "cd system/sensord/tests && python -m unittest test_sensord.py"], + ]) + } + } + + stage('replay') { + agent { docker { image 'ghcr.io/commaai/alpine-ssh'; args '--user=root' } } + steps { + phone_steps("tici-common", [ + ["build", "cd selfdrive/manager && ./build.py"], + ["model replay", "cd selfdrive/test/process_replay && NO_NAV=1 ./model_replay.py"], + ]) + } + } + + } } + } } diff --git a/Pipfile b/Pipfile deleted file mode 100644 index 778cf8c33c..0000000000 --- a/Pipfile +++ /dev/null @@ -1,100 +0,0 @@ -[[source]] -name = "pypi" -url = "https://pypi.org/simple" -verify_ssl = true - -[dev-packages] -av = "*" -azure-storage-blob = "~=2.1" -control = "*" -coverage = "*" -dictdiffer = "*" -fastcluster = "*" -hexdump = "*" -hypothesis = "==6.46.7" -inputs = "*" -lru-dict = "*" -markdown-it-py = "*" -matplotlib = "*" -mypy = "*" -myst-parser = "*" -natsort = "*" -numpy = "*" -opencv-python-headless = "*" -parameterized = "*" -paramiko = "*" -pprofile = "*" -pre-commit = "*" -pycurl = "*" -pygame = "*" -pyprof2calltree = "*" -pytest = "*" -pytest-xdist = "*" -reverse_geocoder = "*" -scipy = "*" -sphinx = "*" -sphinx-sitemap = "*" -sphinx-rtd-theme = "*" -breathe = "*" -subprocess32 = "*" -tenacity = "*" -mpld3 = "*" -carla = {version = "==0.9.13", markers="platform_system != 'Darwin'"} -ft4222 = "*" -pandas = "*" -tabulate = "*" -types-pyyaml = "*" -lxml = "*" -types-atomicwrites = "*" -types-pycurl = "*" -types-requests = "*" -types-certifi = "*" - -[packages] -atomicwrites = "*" -casadi = {version = "*", markers="platform_system != 'Darwin'"} -cffi = "*" -crcmod = "*" -cryptography = "*" -Cython = "*" -flake8 = "*" -Flask = "*" -future-fstrings = "*" # for acados -gunicorn = "*" -hexdump = "*" -Jinja2 = "*" -json-rpc = "*" -libusb1 = "*" -nose = "*" -numpy = "*" -protobuf = "==3.20.1" -onnx = "*" -onnxruntime-gpu = {version = "*", markers="platform_system != 'Darwin'"} -pillow = "*" -psutil = "*" -pycapnp = "==1.1.0" -pycryptodome = "*" -PyJWT = "*" -pylint = "*" -pyopencl = "*" -pyserial = "*" -python-dateutil = "*" -PyYAML = "*" -pyzmq = "*" -requests = "*" -scons = "*" -sentry-sdk = "*" -setproctitle = "*" -six = "*" -smbus2 = "*" -sympy = "!=1.6.1" -timezonefinder = "*" -tqdm = "*" -urllib3 = "*" -utm = "*" -websocket_client = "*" -hatanaka = "==2.4" -PySide2 = "*" - -[requires] -python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock deleted file mode 100644 index d033a517cb..0000000000 --- a/Pipfile.lock +++ /dev/null @@ -1,3072 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "c406463198490fc40dcac3dd438c77cf36f6fe681793072e96fdecf089ff7639" - }, - "pipfile-spec": 6, - "requires": { - "python_version": "3.8" - }, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "astroid": { - "hashes": [ - "sha256:81f870105d892e73bf535da77a8261aa5bde838fa4ed12bb2f435291a098c581", - "sha256:997e0c735df60d4a4caff27080a3afc51f9bdd693d3572a4a0b7090b645c36c5" - ], - "markers": "python_full_version >= '3.7.2'", - "version": "==2.12.10" - }, - "atomicwrites": { - "hashes": [ - "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11" - ], - "index": "pypi", - "version": "==1.4.1" - }, - "casadi": { - "hashes": [ - "sha256:09e103bb597d46aa338fc57bc49270068a1f07be35f9494c9f796dea4b801aeb", - "sha256:13277151efc76b221de8ca6b5ab7b8bbdd2b0e139f282866840adf88dfe53bc9", - "sha256:1c451a07b2440c00d552e040b6285b6e79b677d2978212368b28b86f5d267669", - "sha256:2322748a8d5e88750fd2fc0abcdc56cfbad1a8cd538fe0e7d7b6d8ce0cb3fa62", - "sha256:24fbac649ee26572884029dcd0e108b4a2412cad003a84ed915c4e44a94ecae7", - "sha256:253569c85f881a6a8fe5e1c0758858edb1ecb4c3d8bce4aee4b52e5dc59fc091", - "sha256:292e2768280393bad406256e0ef9c30ddcd4867dbd42148b36f9d92a32d9e199", - "sha256:353a79e50aa84ac5e0d9f04bc3b2d78a2cc8edae3b842d757756449682778944", - "sha256:36db4c84d8f3aad328faaeaeaa454a633c95a854d78ea188791b147888379342", - "sha256:3aec6737c282e7fb5be41f6c7d0649e52ce49efb3508f30bada707e809bbbb5f", - "sha256:4086669280b2335d235c664373db46dcd7f6485dba4663ce1944ea01753c5e8b", - "sha256:4143803af909f284400c02f59de4d97e5ba9319de28366215ef55ef261914f9a", - "sha256:473bb86fa64ac9703d74a474514703b4665fa9a384221ced620b5025e64532a7", - "sha256:4932b2b5361013420189dbc8d30e970672d036b37cb382f1c09c3b6cfe651a37", - "sha256:49a8b713f0ff0bbc2f2af2e71c515cdced238786e25ef504f5982618c84c67a7", - "sha256:54d89442058271007ae8573dfa33360bea10e26603545481090b45e8b90c9d10", - "sha256:55df534d003efdd120c4ebfeb6b252c443d273cdc4b97a394eb0268367477795", - "sha256:5de5c3c1381ac303e71fdef75dace34af6e1d50b46ac081051cd209b8b933837", - "sha256:5f6eb8de31735c14ecc777e3ad77b57767b5f2dbea29265909ef696f51e8be92", - "sha256:6192e2ed81c15a7dab2554f5f69b134df8d1a982f8d9f13e57bdef93364d2120", - "sha256:643e48f92eaf65eb82964816bb7e7064ddb8239959210fa6168e8bce6fe6ef94", - "sha256:6ce7ac8a301a145f98d46db0bfd13bc8b3831a5bb92e8054d531a1f233bb4b93", - "sha256:7309a75b27c57f09b00a61815fb38c40da8e62e3004598e55ea1b8f713d96221", - "sha256:77f33cb95be6a49b93d8d6b81f05193676ae09857699cedf8f1a14a4285d077e", - "sha256:7a624d40c7b5ded7916f6cc65998af4585b4557c9ea65dc1e3a6273ebb2313ec", - "sha256:a06c0b96eb9d3bc88c627eec6e465726934ca0394347dc33efc742b8c91db83d", - "sha256:a4ce51e988570160af9ccfbbb1b9679546cbb1865d3a74ef0276f37fd94d91d9", - "sha256:ab6a600a9b2ea27453d56fd4464ad0db0ae69f5cea42595fcbdaabcd40396440", - "sha256:ab85c7cf772ba54f2718ebe366b836fffff868443f7c0c02389ed0a288cbde1f", - "sha256:ac45b91616e9b8afbe266ca08e80770b28e9e6d7a5852e3677fb37e42bde2047", - "sha256:adf20c34ba2cec1840a026023d93cc6d9b3581dfda6a044f434fc75b50c9a2ce", - "sha256:bd94048388b602fc30fdac2fecb986c034110ed8d2d17af7fd13b0de45c58bd7", - "sha256:c3440c90c31b61ae1df82f6c784643393f723354dc08013f9d5cedf25507c67c", - "sha256:cd630a2e6ec6df6a4977af63080fa8d63a0053ff8c06ea0200959b47ae75201c", - "sha256:d4e49cb46404cef61f83b30bb20ec9597c50ae7f55cfd6b89c17facc74675437", - "sha256:ec26244f9d9047f1bb401f1b86ff4775e1ddf638f4b4992bbc362a27a6f56673", - "sha256:f08a99e98b0a15083f06b1e221f064a29b3ed9e20617dc55aa8e823f2f732ace", - "sha256:fbf39dcd63f1d3b63c300fce59b7ea678bd5ea1d014e1e090a5226600a4132cb" - ], - "index": "pypi", - "markers": "platform_system != 'Darwin'", - "version": "==3.5.5" - }, - "certifi": { - "hashes": [ - "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", - "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" - ], - "markers": "python_version >= '3.6'", - "version": "==2022.9.24" - }, - "cffi": { - "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "index": "pypi", - "version": "==1.15.1" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" - ], - "markers": "python_version >= '3.6'", - "version": "==2.1.1" - }, - "click": { - "hashes": [ - "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", - "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" - ], - "markers": "python_version >= '3.7'", - "version": "==8.1.3" - }, - "coloredlogs": { - "hashes": [ - "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934", - "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==15.0.1" - }, - "crcmod": { - "hashes": [ - "sha256:50586ab48981f11e5b117523d97bb70864a2a1af246cf6e4f5c4a21ef4611cd1", - "sha256:69a2e5c6c36d0f096a7beb4cd34e5f882ec5fd232efb710cdb85d4ff196bd52e", - "sha256:737fb308fa2ce9aed2e29075f0d5980d4a89bfbec48a368c607c5c63b3efb90e", - "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e" - ], - "index": "pypi", - "version": "==1.7" - }, - "cryptography": { - "hashes": [ - "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", - "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", - "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", - "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", - "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", - "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", - "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", - "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", - "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", - "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", - "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", - "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", - "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", - "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", - "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", - "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", - "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", - "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", - "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", - "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", - "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", - "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", - "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", - "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", - "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", - "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" - ], - "index": "pypi", - "version": "==38.0.1" - }, - "cython": { - "hashes": [ - "sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a", - "sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b", - "sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b", - "sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9", - "sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9", - "sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928", - "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b", - "sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b", - "sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0", - "sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da", - "sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0", - "sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65", - "sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98", - "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3", - "sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd", - "sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276", - "sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754", - "sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf", - "sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754", - "sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3", - "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7", - "sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833", - "sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457", - "sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673", - "sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9", - "sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094", - "sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b", - "sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6", - "sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b", - "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528", - "sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3", - "sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd", - "sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1", - "sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64", - "sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd", - "sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3", - "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b", - "sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c", - "sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3", - "sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831" - ], - "index": "pypi", - "version": "==0.29.32" - }, - "dill": { - "hashes": [ - "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302", - "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==0.3.5.1" - }, - "flake8": { - "hashes": [ - "sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db", - "sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248" - ], - "index": "pypi", - "version": "==5.0.4" - }, - "flask": { - "hashes": [ - "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b", - "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526" - ], - "index": "pypi", - "version": "==2.2.2" - }, - "flatbuffers": { - "hashes": [ - "sha256:0ae7d69c5b82bf41962ca5fde9cc43033bc9501311d975fd5a25e8a7d29c1245", - "sha256:71e135d533be527192819aaab757c5e3d109cb10fbb01e687f6bdb7a61ad39d1" - ], - "version": "==2.0.7" - }, - "future-fstrings": { - "hashes": [ - "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089", - "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63" - ], - "index": "pypi", - "version": "==1.2.0" - }, - "gunicorn": { - "hashes": [ - "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e", - "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8" - ], - "index": "pypi", - "version": "==20.1.0" - }, - "h3": { - "hashes": [ - "sha256:08a09f7a43ed142573c602ef487a058da54ab4d86c173082b29a5057805fe2d3", - "sha256:0a96ea1844182bd0511cdcdc89e38e3026d9a3d4139fd0c5e899709edd798ffa", - "sha256:1387166f453816f91d624c6ce70876a3c20356cd28a3a759920dee23c78684cf", - "sha256:25f0c22f4802ab71c45b86d206bd30fa0a6c7fbc3b630398b60c22907e9742e6", - "sha256:2bf4d75fe42a260ac23bf4cb9f9de6e6f2aa37279b2719387711f3e0727c4653", - "sha256:2faf304020493c5ffede34264bd28ed529b8b7238103e0904c0f3e9ca880bcfd", - "sha256:33b147ecc0e19ab1f27303d0e3ae28e5a457f3347ce18ca9a58b694a8b0cdd0a", - "sha256:38a084d74b234d48aafc01e4329cd9a92966e3f45b8cf21224118643b6eaa1c0", - "sha256:3b1642085939c597a9c723ae3b187f80527ffc79cad0ded0e55be9c9bac69c6c", - "sha256:4469fdf90034b1a67e155cac4f46b077fdc404b6182ab33abcb7081c9bfbf411", - "sha256:46a1284521d86cab414981056390be944dca780fe74c6c9e463a16d1c8d24871", - "sha256:585f375ba2a95ceb16b115a378e9118159c912c26703cf1627f57a004818c3b3", - "sha256:62057c1c3d1c7fe492841e42fa360825d66fafd55ac37dc4e90b2292af21cb47", - "sha256:68227df989274b0da54de9101a50741c70c48197ba3beacfb97c88170445c18e", - "sha256:7b0ddfdd02920996d7c6672c91e83efb5432c67ff83f89a03f774e84bcfe19f8", - "sha256:7c5366d24c2c01ef3bae68547c15f1965fac6053b2596c0073766bf7544ecaf0", - "sha256:8155b2de1938eb56128fe4fd96e4f6d2022d4c34d8137bc95d73cbf329f8f89e", - "sha256:83c2b0cd8259541f95b0493a620fb781b6a18c7c1e8fac1bda4fb234ae23ab43", - "sha256:8d03622433da1a2761574311af378ff1ff841f5956db25927837c6aee9d1c13c", - "sha256:960cd005b8817314d95fbaff3e848a72385df4e3c6c9703ff99b08581c8def69", - "sha256:9b0277d82578b3ed3220167ef5c5acd8b4e0ef2fcd6c2fd69dbf29e0c4e03765", - "sha256:a33fae02a54c63acb3c30fe49388715d658d76d42858a6ad4563e7e6859a9e57", - "sha256:a59d7d10597a2da9e9729637a625ae8dff2ed4e7c6c0b4952f0a5b2db6ef7152", - "sha256:ad21cfa8d97a62984ce30692a7ddf71a32a0c744cc247c43cbdbac1536aec4de", - "sha256:be63482de86bbb91db7f3f3b7dd452b9e08a11dacbda2088386831fb0e7de59c", - "sha256:c1108a9acb755310dce50a6e3c58ae1a2460ef60901d40e1155d633c7392f858", - "sha256:c644ab3f221c7faaab2d1ccd11bc3b1106f172e9bb1c85a863b0a097f6b71cce", - "sha256:c95c0818c163b69989c9e876dd82005e60edfbaabfd45429abebfc26f9a357e8", - "sha256:cdd68e684f0c6e18604d46ee04dbcfe5c79de62238b2c29f1db0f3a5d8dfa47b", - "sha256:ce86c6dce2c923bfb16e26586bc5f0443a8be61d4f43227be8587ccb95588a46", - "sha256:e61d3c6b1b66072f5b74d46dbee7df29daac6ce9738b9a6223a67dc577114515", - "sha256:f8edf5a546b31afdcd801b60448ea890ce1ff418fb784335e1329519f13aa85e" - ], - "version": "==3.7.4" - }, - "hatanaka": { - "hashes": [ - "sha256:5a0624f6812b13abb4c996398a60338566885c1786841c4c04de9b1b91da28d2", - "sha256:8fda4aa56f27313de75a806a2f5aa83ed5bb2dc7561bebab856a774d06cf1ee7", - "sha256:c22970b99169bddaf22e5239672e856a6bc9602c435f8793d26ad49619a70a99", - "sha256:ef594d63473782fac46df5b0c92a59211a3efea1d47c1a964244a0abffc9f3f6" - ], - "index": "pypi", - "version": "==2.4" - }, - "hexdump": { - "hashes": [ - "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db" - ], - "index": "pypi", - "version": "==3.3" - }, - "humanfriendly": { - "hashes": [ - "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477", - "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==10.0" - }, - "idna": { - "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - ], - "markers": "python_version >= '3.5'", - "version": "==3.4" - }, - "importlib-metadata": { - "hashes": [ - "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", - "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" - ], - "markers": "python_version < '3.10'", - "version": "==4.12.0" - }, - "importlib-resources": { - "hashes": [ - "sha256:5481e97fb45af8dcf2f798952625591c58fe599d0735d86b10f54de086a61681", - "sha256:f78a8df21a79bcc30cfd400bdc38f314333de7c0fb619763f6b9dabab8268bb7" - ], - "markers": "python_version >= '3.7'", - "version": "==5.9.0" - }, - "isort": { - "hashes": [ - "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7", - "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951" - ], - "markers": "python_version < '4' and python_full_version >= '3.6.1'", - "version": "==5.10.1" - }, - "itsdangerous": { - "hashes": [ - "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44", - "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.2" - }, - "jinja2": { - "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" - ], - "index": "pypi", - "version": "==3.1.2" - }, - "json-rpc": { - "hashes": [ - "sha256:84b45058e5ba95f49c7b6afcf7e03ab86bee89bf2c01f3ad8dd41fe114fc1f84", - "sha256:def0dbcf5b7084fc31d677f2f5990d988d06497f2f47f13024274cfb2d5d7589" - ], - "index": "pypi", - "version": "==1.13.0" - }, - "lazy-object-proxy": { - "hashes": [ - "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": [ - "sha256:083f75e5d15cb5e2159e64c308c5317284eae926a820d6dce53a9505d18be3b2", - "sha256:0e652b04cbe85ec8e74f9ee82b49f861fb14b5320ae51399387ad2601ccc0500", - "sha256:5792a9defee40f15d330a40d9b1800545c32e47ba7fc66b6f28f133c9fcc8538", - "sha256:6f6bb010632ada35c661d17a65e135077beef0fbb2434d5ffdb3a4a911fd9490" - ], - "index": "pypi", - "version": "==3.0.0" - }, - "markupsafe": { - "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "mccabe": { - "hashes": [ - "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325", - "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e" - ], - "markers": "python_version >= '3.6'", - "version": "==0.7.0" - }, - "mpmath": { - "hashes": [ - "sha256:604bc21bd22d2322a177c73bdb573994ef76e62edd595d17e00aff24b0667e5c", - "sha256:79ffb45cf9f4b101a807595bcb3e72e0396202e0b1d25d689134b48c4216a81a" - ], - "version": "==1.2.1" - }, - "ncompress": { - "hashes": [ - "sha256:0349d7de11edd70a7efea9ce9dc67f0e47b5774832dd063f7ae68a9e3e36ea31", - "sha256:070044eab19586a45d1855c3e50e000ce86d6075b122a5ec8cffd480713dffac", - "sha256:13fa26ec8000d786a8079bb265508b5df4b445a4f460481a13549b4bd3c83824", - "sha256:15f10fbfa11345ff0af090e3e6ae13a1fe2b52a2bb39d4f2373c2d6aeac75e5d", - "sha256:2a104803fbe3ab0a96edb14927fa22c8142be838aabe7e938b4a52a4e82db56e", - "sha256:34754041d9bac2d6908ae0d07ba541e4d6d606cca222ddd53f3a57e15f386b0a", - "sha256:34c6496168fd4dbc13f1fc0c0fcbadded1957639956f8cbc6894c39999817e36", - "sha256:3590e66313041721ae81e72ece06b7048c9293321bb30900358638673608e264", - "sha256:393cc3c126b9451fb32fe2bc07773264c90e73afbd37da0df472ac23bfd1a2d5", - "sha256:5336a8831a7e587829ce54e9e27d1fb2e04ddbc7d2d983693e35a3a03ac3ce79", - "sha256:5a2ae8a9170fa1f45df7efa292eb8c437b18c225b63d4adca4f50f9da0e8e0c7", - "sha256:6540556d47670a8fb93878a44d0206bbdc87f32e4c5b57d6fe63691efafbb982", - "sha256:66d991155a1655ccd98e8433c4a7e811d63eb649adb55f47d8f9528a30cc4b7a", - "sha256:736dbae078107742cf6ac7ccc11ae9c5eab77ef2c02aab3ef64802877bb01cab", - "sha256:7608fbda43d04d9f476be2dbf4ef3c96e72d83b9557a48b07fbc9ff3ad29cdd2", - "sha256:78674f246878938387b6f82b10d1aa2192e02544d214541943d12ef1a45e66c6", - "sha256:8322482e72ac2802d1dca1007ec06aa281a4d5cf1cf9f8c75bb51e917382b756", - "sha256:8b9acc46cf36bb998ed215d6e76a94e2bd1e827b9a4cb5362982b7004b5a7620", - "sha256:8eb4a55cbeaeb238a3b412952077be6b3f37b3416cd0211cc22776391ff2fef7", - "sha256:916671d62167191af58d6b4a17b1c09c647e349dcff1fc0b7d764aa64c3773ee", - "sha256:94b3f4e851f5b37e1d4cf2d8da911fa10783a59cba3d7f1f2ae5bd2842558077", - "sha256:9cd040ad73a3b0e917e01cdfba507e10e0bb56849daaac3ac3d86382d4d7ad82", - "sha256:9d89acf209858e7940223cf35324e1b2effec119bb009a41f039e2ea4db22177", - "sha256:9da7c81313aed4b6c6e8020442ed8d03d04bff72947f9380ea1ce2c63ffb8ad1", - "sha256:aaa18a509d9fc173b4b47d53c834e43ced1eda63d2aa7d4613dc59b2f802a31a", - "sha256:ab9fc62baaa55faf8ed8ac67f2c64a7295fec91d7c1f306ac46aa894ca4edf91", - "sha256:af0011bae90e44121f4e4edbff3dccdce7e4c5fc5e354db7eb48410d71f496df", - "sha256:b031e06b42037b181e3514261e1e85a9eae4af990c12b9348a9f22b8042201ff", - "sha256:d11df815d280985dfa660974df11dbe051a1a18dca2f91f9d30fbd6548237b8f", - "sha256:d45ec59a8a3ce00613df0c81e5567854574dbbbf01ecd1a5a0929cd8fb04844d", - "sha256:da216a53db7cd4c0247376f87367dd71df457443567e55310f6d3d23a9aff2f2", - "sha256:e0ebd71990ef7909b6627b5341a2fe1977dcce61dd3760a29e19e3f9e4c6a275", - "sha256:e6f5bf381412e9d3847b76e8b6bd1f84dfadcd3d9c25903c8592facb437909a0", - "sha256:e7bbf10cca1376f4f17ae2c447e33a9d4067525abb0c71d488c9a5ced50552f1", - "sha256:f9ba6ab2aadd6fd90365fdad5219e4dc7bc2459b94f1e900a733dddaf4e9b2e6", - "sha256:fe0a671a2f7dc1ee0438d278ef30ab425a969536100c4352b5cb6bc0b6210818" - ], - "version": "==1.0.0" - }, - "nose": { - "hashes": [ - "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", - "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", - "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" - ], - "index": "pypi", - "version": "==1.3.7" - }, - "numpy": { - "hashes": [ - "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", - "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", - "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", - "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", - "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", - "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", - "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", - "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", - "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", - "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", - "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", - "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", - "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", - "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", - "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", - "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", - "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", - "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", - "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", - "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", - "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", - "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", - "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", - "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", - "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", - "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", - "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", - "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" - ], - "index": "pypi", - "version": "==1.23.3" - }, - "onnx": { - "hashes": [ - "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9", - "sha256:213e73610173f6b2e99f99a4b0636f80b379c417312079d603806e48ada4ca8b", - "sha256:23781594bb8b7ee985de1005b3c601648d5b0568a81e01365c48f91d1f5648e4", - "sha256:2d9a7db54e75529160337232282a4816cc50667dc7dc34be178fd6f6b79d4705", - "sha256:341c7016e23273e9ffa9b6e301eee95b8c37d0f04df7cedbdb169d2c39524c96", - "sha256:3c6e6bcffc3f5c1e148df3837dc667fa4c51999788c1b76b0b8fbba607e02da8", - "sha256:5578b93dc6c918cec4dee7fb7d9dd3b09d338301ee64ca8b4f28bc217ed42dca", - "sha256:56ceb7e094c43882b723cfaa107d85ad673cfdf91faeb28d7dcadacca4f43a07", - "sha256:81a3555fd67be2518bf86096299b48fb9154652596219890abfe90bd43a9ec13", - "sha256:8a7aa61aea339bd28f310f4af4f52ce6c4b876386228760b16308efd58f95059", - "sha256:9fd2f4e23078df197bb76a59b9cd8f5a43a6ad2edc035edb3ecfb9042093e05a", - "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833", - "sha256:b3629e8258db15d4e2c9b7f1be91a3186719dd94661c218c6f5fde3cc7de3d4d", - "sha256:bdbd2578424c70836f4d0f9dda16c21868ddb07cc8192f9e8a176908b43d694b", - "sha256:c11162ffc487167da140f1112f49c4f82d815824f06e58bc3095407699f05863", - "sha256:c39a7a0352c856f1df30dccf527eb6cb4909052e5eaf6fa2772a637324c526aa", - "sha256:c7a9b3ea02c30efc1d2662337e280266aca491a8e86be0d8a657f874b7cccd1e", - "sha256:f66d2996e65f490a57b3ae952e4e9189b53cc9fe3f75e601d50d4db2dc1b1cd9", - "sha256:f8800f28c746ab06e51ef8449fd1215621f4ddba91be3ffc264658937d38a2af", - "sha256:fab13feb4d94342aae6d357d480f2e47d41b9f4e584367542b21ca6defda9e0a", - "sha256:fea5156a03398fe0e23248042d8651c1eaac5f6637d4dd683b4c1f1320b9f7b4" - ], - "index": "pypi", - "version": "==1.12.0" - }, - "onnxruntime-gpu": { - "hashes": [ - "sha256:296bd9733986cb7517d15bef5535c555d3f863963a71e6575e92d2a854aee61d", - "sha256:42b0393c5122ed90fa2eb76192a486261d86e9526ccb78b2a98923c22791d2d1", - "sha256:8e46d0724ce54c5908c5760037b78de741fbd48962b370c29ebc20e608b30eda", - "sha256:b37872527d03d3df10756408ca44014bd6ac354a044ab1c4286cd42dc138e518", - "sha256:d73204323aefebe4eecab9fcf76e22b1a00394e3d838c2962a28a27301186b73", - "sha256:e2be6f7f5a1ce0bc8471ce42e10eab92cfb19d0748b857edcb5320b5e98311b7", - "sha256:ecfe97335027e569d4f46725ba89316041e562b8c499690e25e11cfee4601cd1", - "sha256:fd919373be35b9ba54210688265df38ad5e19a530449385c40dab51da407345d" - ], - "index": "pypi", - "markers": "platform_system != 'Darwin'", - "version": "==1.12.1" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "pillow": { - "hashes": [ - "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", - "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", - "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", - "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", - "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", - "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", - "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", - "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", - "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", - "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", - "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", - "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", - "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", - "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", - "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", - "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437", - "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", - "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", - "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", - "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", - "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", - "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", - "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", - "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", - "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", - "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", - "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", - "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", - "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", - "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", - "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", - "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", - "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", - "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", - "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", - "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", - "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", - "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", - "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", - "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", - "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", - "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", - "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", - "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", - "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc", - "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", - "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", - "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", - "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", - "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", - "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", - "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", - "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", - "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", - "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", - "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", - "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", - "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", - "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", - "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" - ], - "index": "pypi", - "version": "==9.2.0" - }, - "platformdirs": { - "hashes": [ - "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", - "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" - ], - "markers": "python_version >= '3.7'", - "version": "==2.5.2" - }, - "protobuf": { - "hashes": [ - "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf", - "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f", - "sha256:0d4719e724472e296062ba8e82a36d64693fcfdb550d9dff98af70ca79eafe3d", - "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f", - "sha256:2b35602cb65d53c168c104469e714bf68670335044c38eee3c899d6a8af03ffc", - "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7", - "sha256:32fff501b6df3050936d1839b80ea5899bf34db24792d223d7640611f67de15a", - "sha256:34400fd76f85bdae9a2e9c1444ea4699c0280962423eff4418765deceebd81b5", - "sha256:3767c64593a49c7ac0accd08ed39ce42744405f0989d468f0097a17496fdbe84", - "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996", - "sha256:3f2ed842e8ca43b790cb4a101bcf577226e0ded98a6a6ba2d5e12095a08dc4da", - "sha256:52c1e44e25f2949be7ffa7c66acbfea940b0945dd416920231f7cb30ea5ac6db", - "sha256:5d9b5c8270461706973c3871c6fbdd236b51dfe9dab652f1fb6a36aa88287e47", - "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067", - "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c", - "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7", - "sha256:72d357cc4d834cc85bd957e8b8e1f4b64c2eac9ca1a942efeb8eb2e723fca852", - "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9", - "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c", - "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739", - "sha256:79cd8d0a269b714f6b32641f86928c718e8d234466919b3f552bfb069dbb159b", - "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91", - "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c", - "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153", - "sha256:a4c0c6f2f95a559e59a0258d3e4b186f340cbdc5adec5ce1bc06d01972527c88", - "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9", - "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388", - "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e", - "sha256:b309fda192850ac4184ca1777aab9655564bc8d10a9cc98f10e1c8bf11295c22", - "sha256:b3d7d4b4945fe3c001403b6c24442901a5e58c0a3059290f5a63523ed4435f82", - "sha256:c8829092c5aeb61619161269b2f8a2e36fd7cb26abbd9282d3bc453f02769146", - "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab", - "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde", - "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531", - "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8", - "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7", - "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20", - "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3" - ], - "index": "pypi", - "version": "==3.20.1" - }, - "psutil": { - "hashes": [ - "sha256:14b29f581b5edab1f133563272a6011925401804d52d603c5c606936b49c8b97", - "sha256:256098b4f6ffea6441eb54ab3eb64db9ecef18f6a80d7ba91549195d55420f84", - "sha256:39ec06dc6c934fb53df10c1672e299145ce609ff0611b569e75a88f313634969", - "sha256:404f4816c16a2fcc4eaa36d7eb49a66df2d083e829d3e39ee8759a411dbc9ecf", - "sha256:42638876b7f5ef43cef8dcf640d3401b27a51ee3fa137cb2aa2e72e188414c32", - "sha256:4642fd93785a29353d6917a23e2ac6177308ef5e8be5cc17008d885cb9f70f12", - "sha256:4fb54941aac044a61db9d8eb56fc5bee207db3bc58645d657249030e15ba3727", - "sha256:561dec454853846d1dd0247b44c2e66a0a0c490f937086930ec4b8f83bf44f06", - "sha256:5d39e3a2d5c40efa977c9a8dd4f679763c43c6c255b1340a56489955dbca767c", - "sha256:614337922702e9be37a39954d67fdb9e855981624d8011a9927b8f2d3c9625d9", - "sha256:67b33f27fc0427483b61563a16c90d9f3b547eeb7af0ef1b9fe024cdc9b3a6ea", - "sha256:68b35cbff92d1f7103d8f1db77c977e72f49fcefae3d3d2b91c76b0e7aef48b8", - "sha256:7cbb795dcd8ed8fd238bc9e9f64ab188f3f4096d2e811b5a82da53d164b84c3f", - "sha256:8f024fbb26c8daf5d70287bb3edfafa22283c255287cf523c5d81721e8e5d82c", - "sha256:91aa0dac0c64688667b4285fa29354acfb3e834e1fd98b535b9986c883c2ce1d", - "sha256:94e621c6a4ddb2573d4d30cba074f6d1aa0186645917df42c811c473dd22b339", - "sha256:9770c1d25aee91417eba7869139d629d6328a9422ce1cdd112bd56377ca98444", - "sha256:b1928b9bf478d31fdffdb57101d18f9b70ed4e9b0e41af751851813547b2a9ab", - "sha256:b2f248ffc346f4f4f0d747ee1947963613216b06688be0be2e393986fe20dbbb", - "sha256:b315febaebae813326296872fdb4be92ad3ce10d1d742a6b0c49fb619481ed0b", - "sha256:b3591616fa07b15050b2f87e1cdefd06a554382e72866fcc0ab2be9d116486c8", - "sha256:b4018d5f9b6651f9896c7a7c2c9f4652e4eea53f10751c4e7d08a9093ab587ec", - "sha256:d75291912b945a7351d45df682f9644540d564d62115d4a20d45fa17dc2d48f8", - "sha256:dc9bda7d5ced744622f157cc8d8bdd51735dafcecff807e928ff26bdb0ff097d", - "sha256:e3ac2c0375ef498e74b9b4ec56df3c88be43fe56cac465627572dbfb21c4be34", - "sha256:e4c4a7636ffc47b7141864f1c5e7d649f42c54e49da2dd3cceb1c5f5d29bfc85", - "sha256:ed29ea0b9a372c5188cdb2ad39f937900a10fb5478dc077283bf86eeac678ef1", - "sha256:f40ba362fefc11d6bea4403f070078d60053ed422255bd838cd86a40674364c9", - "sha256:f4cb67215c10d4657e320037109939b1c1d2fd70ca3d76301992f89fe2edb1f1", - "sha256:f7929a516125f62399d6e8e026129c8835f6c5a3aab88c3fff1a05ee8feb840d", - "sha256:fd331866628d18223a4265371fd255774affd86244fc307ef66eaf00de0633d5", - "sha256:feb861a10b6c3bb00701063b37e4afc754f8217f0f09c42280586bd6ac712b5c" - ], - "index": "pypi", - "version": "==5.9.2" - }, - "pycapnp": { - "hashes": [ - "sha256:0c770145a4eccfe97f53ab500283aa9bf969d4a37bffc75737a964db4f2af833", - "sha256:13badfb644e2eb7f1219aab259d18b262d1512021e4112fa1ad5e74d17bc30cf", - "sha256:1774a4fe9db5f094ba40cf00898fa4b437773e7f9c538b779275b9f422a92ebc", - "sha256:2a1746017079107232faf26af8ef4284ab0b20ce5cbe688d44e7553a67e5e5cb", - "sha256:2a7aa9af0185e5977a59228db5042dffb048b2d4bf4f665d2105b4781cf2fcbc", - "sha256:2b28d5d951602c0b832bbe63f85ebdd7685b33118b1c11c2c65a243ec9f35a66", - "sha256:47c8bc28521312660c95cfc8a552654949407f8b17bc7ed6955ad7dae34d21a4", - "sha256:60adf2674f89f629551171116b8f400b17e9a41a2ef15736767acec405d4ca50", - "sha256:61b009faba34855c9d29db107e188898c83099347e22ebcbc1d955774403247b", - "sha256:786a2e39b79e592a41e8a1eaeea6e41e2015ecb9f5b7f7c20dfc5768ba1ae077", - "sha256:a788a374ccb93354943c89f5b1caf785faf7bb90191cd6265e042aa004f8b206", - "sha256:b9a1d6306a0e3e0090574aeb08d432bd67f9eb04ab564e89ef34cd1fe320b20f", - "sha256:bdd013eae51d190a2426d00cc72d0aaed148a5be778ca86ee1adae3ab7a0613f" - ], - "index": "pypi", - "version": "==1.1.0" - }, - "pycodestyle": { - "hashes": [ - "sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785", - "sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b" - ], - "markers": "python_version >= '3.6'", - "version": "==2.9.1" - }, - "pycparser": { - "hashes": [ - "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9", - "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206" - ], - "version": "==2.21" - }, - "pycryptodome": { - "hashes": [ - "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79", - "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb", - "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e", - "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88", - "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763", - "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884", - "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13", - "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6", - "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2", - "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667", - "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a", - "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d", - "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e", - "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b", - "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83", - "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8", - "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f", - "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f", - "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676", - "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f", - "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8", - "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2", - "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f", - "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9", - "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b", - "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1", - "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5", - "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c", - "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9", - "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec" - ], - "index": "pypi", - "version": "==3.15.0" - }, - "pyflakes": { - "hashes": [ - "sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2", - "sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3" - ], - "markers": "python_version >= '3.6'", - "version": "==2.5.0" - }, - "pyjwt": { - "hashes": [ - "sha256:8d82e7087868e94dd8d7d418e5088ce64f7daab4b36db654cbaedb46f9d1ca80", - "sha256:e77ab89480905d86998442ac5788f35333fa85f65047a534adc38edf3c88fc3b" - ], - "index": "pypi", - "version": "==2.5.0" - }, - "pylint": { - "hashes": [ - "sha256:5fdfd44af182866999e6123139d265334267339f29961f00c89783155eacc60b", - "sha256:7f6aad1d8d50807f7bc64f89ac75256a9baf8e6ed491cc9bc65592bc3f462cf1" - ], - "index": "pypi", - "version": "==2.15.3" - }, - "pyopencl": { - "hashes": [ - "sha256:024f7ad835f70fff2c27a3d111eec438761c42413ca78af20cc87ad3ecaba01a", - "sha256:03f3e96c8743edf16dceddef564c5198f8d988152a26a859de9d3e1e0f14888c", - "sha256:07e58b74a59cd27c390f3099597c1f05e50c441f82fb17fb84d43e5785951ecf", - "sha256:07e9ffe8d5d38066fd5a8a5540b6944617322b13d55db7e3f78609dc309e4da8", - "sha256:10bc9e39e6bb5c6f842dd4a3af869cb73a4ee29d47a74deed8120390c7bcf4ad", - "sha256:15d4843b88eb2379cb29d2ef13b93b6ce8f917bae70eb6c1584ba21c4c5c4ff6", - "sha256:21da5f08aabbc2b2fdc81466ffaac07c1db94b1f47a95bc57f633b55590ccba2", - "sha256:2f5010cfcd434c56e9bbad5fb4dba2fa64b1de6d8f9282b29a5d837268fe93cc", - "sha256:3361f7b1797cdd38c9fff54c6e34750a5665c9c573aab46fcb486fefdf6fcfca", - "sha256:4599fba8aff4c381d2f16651c025b4e6e9488100781a95634215755ea4935a0d", - "sha256:462f1ba6ee3769492bc376bdaeaf2f34f3528bd0fd0dc60569f3edf9346cc769", - "sha256:4890137300a18d94265f1342f5eb8de420029637a09020b682f09f63271170c5", - "sha256:4e55c1df1976040c01315dc09477c7369422e67f36dcf69d9f570bdf48759802", - "sha256:58641144140025b39dbf27a095e250018426a25973e7cfd87724b24c6c8a6a3e", - "sha256:5b8f4358d6bdcf719c46f91c0e9a8ff8ece05843f26de1d047ea1b1ae1875aaf", - "sha256:647ff48cb851040650d79d99eae0229bfd9cd8d931ffae8d33a887891be2b8f4", - "sha256:71ec7b12833a9e29807dc95421791c5ebed8f9435f3e99c5ac77f9f409429863", - "sha256:80db5d46608dfa9e37109bfb925be64cb8d24b06e27945cf7c8996fa9e64ae50", - "sha256:8189b95ac0c845b2639395d5dffe2c4406ac5e42c61b8db972e5dc596e5b8685", - "sha256:8906173bf2d3bc036e0acc8e4d785fc33c89951bacbb0049d64e75d7c476beed", - "sha256:9fbd13defa6f7d719540433890b5318f16a8c3c453dd5feb66a6054a79144c01", - "sha256:a238dd164b141dd9eea0fbab39f0d247bd3c5f8d31b85997aa5619c43a6cb9f5", - "sha256:a2a00fbe65fcebf071a6af59e96fc6b03c8dc95c0d5623cd7d96a083930ffb31", - "sha256:a41e0b02b4c9e6bfdf880d8f6d8d5a21fdd04cd7e7f2afd4d0c5c94994963530", - "sha256:a6bb5e7cb9612b011da83a274ee5477ee9c8892860bae5f3f105c80b512192f9", - "sha256:a78827b7d0f5ee22890a950d08cd2516b136c6af10abad696e5a8f4b62d55db5", - "sha256:b0711e2508fbf9de1d26817f79f57a43b6d84f088f6e40208582e7173a0a3b22", - "sha256:bdbbe72731fdc8152b555360efec791f9c5b71f0eed033052302d51547ca00cb", - "sha256:c4d734261075c750fe7347eb07cdc155d53deb4175be2b9d73524bcc9868072c", - "sha256:c6bd1e1d086d4b28322e88d22deeff06684360f3a9017d7d7eb2f1ae48095479", - "sha256:c8835ad22ac0e656d5b437abb99c763bb4529485c858216015c1286d6a919159", - "sha256:cd322df0f8bec18445e3be0d9dfb7899ed570e520949d337dfb42cc3306e835a", - "sha256:d04291fa318c7ce1033fba5e16f5ca5f7b28dab49213596b5139bac51812cd69", - "sha256:d1eecc9bd2c2dbc32b6f550b7e9264bb8e8bef8520e75fd4dbc627c21faebaf1", - "sha256:d493b83e1be953e7d7849704a0970d2e6915fb5b3514222ab8a6abd3d47e123e", - "sha256:dae8527318f60b557103d4d385892a86c3f470eeed1f560b830d1e8a5724a4b5", - "sha256:e0e81c075fb1c21a3080259fe7de771f1b06861140901c22e47f4e8a69bd8984" - ], - "index": "pypi", - "version": "==2022.2.3" - }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" - }, - "pyserial": { - "hashes": [ - "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb", - "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0" - ], - "index": "pypi", - "version": "==3.5" - }, - "pyside2": { - "hashes": [ - "sha256:235240b6ec8206d9fdf0232472c6ef3241783d480425e5b54796f06e39ed23da", - "sha256:23886c6391ebd916e835fa1b5ae66938048504fd3a2934ae3189a96cd5ac0b46", - "sha256:439509e53cfe05abbf9a99422a2cbad086408b0f9bf5e6f642ff1b13b1f8b055", - "sha256:a9e2e6bbcb5d2ebb421e46e72244a0f4fe0943b2288115f80a863aacc1de1f06", - "sha256:af6b263fe63ba6dea7eaebae80aa7b291491fe66f4f0057c0aafe780cc83da9d", - "sha256:b5e1d92f26b0bbaefff67727ccbb2e1b577f2c0164b349b3d6e80febb4c5bde2" - ], - "index": "pypi", - "version": "==5.15.2.1" - }, - "python-dateutil": { - "hashes": [ - "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "index": "pypi", - "version": "==2.8.2" - }, - "pytools": { - "hashes": [ - "sha256:4d62875e9a2ab2a24e393a9a8b799492f1a721bffa840af3807bfd42871dd1f4" - ], - "markers": "python_version ~= '3.6'", - "version": "==2022.1.12" - }, - "pyyaml": { - "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" - ], - "index": "pypi", - "version": "==6.0" - }, - "pyzmq": { - "hashes": [ - "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107", - "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7", - "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d", - "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc", - "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c", - "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4", - "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8", - "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8", - "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287", - "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b", - "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f", - "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77", - "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066", - "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209", - "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd", - "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a", - "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4", - "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc", - "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58", - "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246", - "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62", - "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227", - "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b", - "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944", - "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2", - "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804", - "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8", - "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066", - "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1", - "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33", - "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639", - "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef", - "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a", - "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace", - "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00", - "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5", - "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b", - "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879", - "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340", - "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4", - "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4", - "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6", - "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3", - "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f", - "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3", - "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409", - "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913", - "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1", - "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43", - "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333", - "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75", - "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff", - "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e", - "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2", - "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef", - "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1", - "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330", - "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d", - "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0", - "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6", - "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea", - "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f", - "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff", - "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99", - "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b", - "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d", - "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be", - "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58", - "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc", - "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83", - "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d", - "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc", - "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66", - "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e" - ], - "index": "pypi", - "version": "==24.0.1" - }, - "requests": { - "hashes": [ - "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", - "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" - ], - "index": "pypi", - "version": "==2.28.1" - }, - "scons": { - "hashes": [ - "sha256:7703c4e9d2200b4854a31800c1dbd4587e1fa86e75f58795c740bcfa7eca7eaa", - "sha256:cbbd73b83cf85f1aaaf986370359de1bbfd3af97a765cb3554734f6dcec734e1" - ], - "index": "pypi", - "version": "==4.4.0" - }, - "sentry-sdk": { - "hashes": [ - "sha256:d6c71d2f85710b66822adaa954af7912bab135d6c85febd5b0f3dfd4ab37e181", - "sha256:ef925b5338625448645a778428d8f22a3d17de8b28cc8e6fba60b93393ad86fe" - ], - "index": "pypi", - "version": "==1.9.9" - }, - "setproctitle": { - "hashes": [ - "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b", - "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9", - "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31", - "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83", - "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f", - "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca", - "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494", - "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe", - "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172", - "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084", - "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4", - "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1", - "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c", - "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973", - "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c", - "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202", - "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5", - "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65", - "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18", - "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13", - "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005", - "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02", - "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7", - "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665", - "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f", - "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1", - "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989", - "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827", - "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42", - "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927", - "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122", - "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9", - "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f", - "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d", - "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98", - "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796", - "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb", - "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd", - "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe", - "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806", - "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484", - "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e", - "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575", - "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76", - "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246", - "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb", - "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099", - "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c", - "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258", - "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865", - "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b", - "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d", - "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10", - "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f", - "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94", - "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6", - "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963", - "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4", - "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8", - "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761" - ], - "index": "pypi", - "version": "==1.3.2" - }, - "setuptools": { - "hashes": [ - "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9", - "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1" - ], - "markers": "python_version >= '3.7'", - "version": "==65.4.0" - }, - "shiboken2": { - "hashes": [ - "sha256:63debfcc531b6a2b4985aa9b71433d2ad3bac542acffc729cc0ecaa3854390c0", - "sha256:87079c07587859a525b9800d60b1be971338ce9b371d6ead81f15ee5a46d448b", - "sha256:a0d0fdeb12b72c8af349b9642ccc67afd783dca449309f45e78cda50272fd6b7", - "sha256:eb0da44b6fa60c6bd317b8f219e500595e94e0322b33ec5b4e9f406bedaee555", - "sha256:f890f5611ab8f48b88cfecb716da2ac55aef99e2923198cefcf781842888ea65", - "sha256:ffd3d0ec3d508e592d7ee3885d27fee1f279a49989f734eb130f46d9501273a9" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '3.11'", - "version": "==5.15.2.1" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "index": "pypi", - "version": "==1.16.0" - }, - "smbus2": { - "hashes": [ - "sha256:50f3c78e436b42a9583948be06961a8104cf020ebad5edfaaf2657528bef0818", - "sha256:634541ed794068a822fe7499f1577468b9d4641b68dd9bfb6d0eb7270f4d2a32" - ], - "index": "pypi", - "version": "==0.4.2" - }, - "sympy": { - "hashes": [ - "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf", - "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658" - ], - "index": "pypi", - "version": "==1.11.1" - }, - "timezonefinder": { - "hashes": [ - "sha256:96c96db94e75e072187843152e6c5dc0718500a9a91986032365abe09162d0e7", - "sha256:f2ee561b1e7692b933fcd914df38800e93db7caf278e7328de7328829b04f275" - ], - "index": "pypi", - "version": "==6.1.3" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "tomlkit": { - "hashes": [ - "sha256:25d4e2e446c453be6360c67ddfb88838cfc42026322770ba13d1fbd403a93a5c", - "sha256:3235a9010fae54323e727c3ac06fb720752fe6635b3426e379daec60fbd44a83" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==0.11.4" - }, - "tqdm": { - "hashes": [ - "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4", - "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1" - ], - "index": "pypi", - "version": "==4.64.1" - }, - "typing-extensions": { - "hashes": [ - "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" - ], - "markers": "python_version < '3.10'", - "version": "==4.3.0" - }, - "urllib3": { - "hashes": [ - "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", - "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" - ], - "index": "pypi", - "version": "==1.26.12" - }, - "utm": { - "hashes": [ - "sha256:3c9a3650e98bb6eecec535418d0dfd4db8f88c8ceaca112a0ff0787e116566e2" - ], - "index": "pypi", - "version": "==0.7.0" - }, - "websocket-client": { - "hashes": [ - "sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090", - "sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef" - ], - "index": "pypi", - "version": "==1.4.1" - }, - "werkzeug": { - "hashes": [ - "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f", - "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5" - ], - "markers": "python_version >= '3.7'", - "version": "==2.2.2" - }, - "wrapt": { - "hashes": [ - "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3", - "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b", - "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4", - "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2", - "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656", - "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3", - "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff", - "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310", - "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a", - "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57", - "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069", - "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383", - "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe", - "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87", - "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d", - "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b", - "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907", - "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f", - "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0", - "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28", - "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1", - "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853", - "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc", - "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3", - "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3", - "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164", - "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1", - "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c", - "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1", - "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7", - "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1", - "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320", - "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed", - "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1", - "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248", - "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c", - "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456", - "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77", - "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef", - "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1", - "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7", - "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86", - "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4", - "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d", - "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d", - "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8", - "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5", - "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471", - "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00", - "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68", - "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3", - "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d", - "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735", - "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d", - "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569", - "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7", - "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59", - "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5", - "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb", - "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b", - "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f", - "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462", - "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015", - "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af" - ], - "markers": "python_version < '3.11'", - "version": "==1.14.1" - }, - "zipp": { - "hashes": [ - "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", - "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" - ], - "markers": "python_version >= '3.7'", - "version": "==3.8.1" - } - }, - "develop": { - "alabaster": { - "hashes": [ - "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359", - "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02" - ], - "version": "==0.7.12" - }, - "attrs": { - "hashes": [ - "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6", - "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c" - ], - "markers": "python_version >= '3.5'", - "version": "==22.1.0" - }, - "av": { - "hashes": [ - "sha256:0340cc68f3d222bc9438b4fde12e3d68f949eeb5de9e090db182f2cb06e23d53", - "sha256:0d9cad890e6eccf2697b1c932761bee6f5e1e7faf9b8c03cf10f18f697d29ba3", - "sha256:109526152e658921731018c50a05db802e7c9f3eb04a7a5fcbd8321fb3b73134", - "sha256:17a7b6617d4201214f3dd5f628041b4fe56f4244dcd48399ed8d0cf324ca24d1", - "sha256:1cbf031f650f89943023eef80e8b2c99588bf9ba26ffef8b3b54bef7102ea3dc", - "sha256:1e2a50a146b3f33a24ea059af913ad368dbb61ed494234debe140a09f1076950", - "sha256:24dac414eafcc20423f2ec7e873706489433648f0e9af08a537996880aa55979", - "sha256:28d85b8476f7d8fb18e3af9bd6d22bb292f1d810a20f8910fe481f648372e798", - "sha256:29373aa86a055a07eebb14d253cb202033f63ba98c5a4b0233d6d4c07fc7a292", - "sha256:2b46b54ddf64409d4455f408b5970f8494c27c0273181b81c2f7d5072c9afb55", - "sha256:343b11d9b03e71da29f3ce56bc0a6c2d40aba448225dcf8296ab53c10527fff0", - "sha256:3ea180bfd89bc0a9e392c32de204cf4e51648aefe2f375d430ce39c04e3ed625", - "sha256:3facfe8dc5ba7f9ec7fd7e4c0466e577b84d5f2a1671428f7e28ebcd2cb0ccd3", - "sha256:45816a39255b39e514a72125e0b6e29eb24fe0994bef3f4f87f3b9d9960b3fa8", - "sha256:48819e401cea5be57bd03299d8e5f700082c411746d1ac23eb5e5a931d3d3ced", - "sha256:49481c2d5bc296f451ccd3f93b1cb692d7f58a804b794b99c8b7743e058cae71", - "sha256:587dd492a2ef3eb20324a0a8d67e6a2e686845d8c1dfdcad058377ac84268d67", - "sha256:6a1c2c1dcc1947473ea1e2cbbf50549e2655e49e08bdd2a6427a97276d7a92c8", - "sha256:6b01fbe8047da81892f8bd2aee5690f00465bf5215e3f6b6372863ac9408d75f", - "sha256:7a5dc26b9df656bed5e1bdeaf8bcc4ff4a2e009ee90b3b3024a86cf8476b2cbf", - "sha256:8671fa01648ce7aac76e71816c2421ddb1939bf706e2e14684608ab1ce9dbbbb", - "sha256:9b0f124e335561cf4de6b7cdc461283c5eba5f05cccb1a5e1b8ceb1cd15393d8", - "sha256:a616a6eb46b62f41ff69569cafe12b0005a6dd14389f597dee115340336a910f", - "sha256:a6a35e6028dec677caed97d19bfab3b66182690d43b0ec3c355778d740ce0509", - "sha256:a9983bc45dab65d2416d2f8a63785caa076a751590823fc8c199617d0dbad390", - "sha256:ab90aa3ac2cbdf1f22087fc0fa439f643e96979f169ecfa1d496e114c3c3a8b3", - "sha256:af951271d998f736a20e54fbc0d944f263db7b17592f11cd489947957bf46aa8", - "sha256:b07b91f534ee7a096068149404c67c3c0e5b4c373580b016151de0fcb440cd3f", - "sha256:b6be9388618af978304b56d1cf6b74c811db4f220dd320da5bd79640aa443358", - "sha256:ba3d9e3fe23fd8a14e810f291386225acbdef1c7e5376cc10c8e85c2d4280771", - "sha256:bf941896b4c800ee707211c802f94c6e0b4642d3000e25d1974d0b6032af4f66", - "sha256:d080f34ddfde551de3a5f2d0d06d7518718e3115af81e56182e158cc03111662", - "sha256:d380925732e7497c1c11545107eabe1f498cab214f49f32d1b5d6abe01a2b36b", - "sha256:d6a3c9126d658029b151484b48c656b73af1b145b143c50de5b8b983ac60e095", - "sha256:d730f3ed30eda46d06849bd71ad87d480cf0cad9fd064f33a0386dee95461e31", - "sha256:e3e4a28fa0eabd3ab5b0915e9c005e9155039f9e1a4466212434c40eb69a33fb", - "sha256:e59e4ab0e8832bf87707e5024283b3a24cc01784604f0b0e96fbfbadbd8d9fc0", - "sha256:f2a7c226724d7f7745b376b459c500d9d17bd8d0473b7ea6bf8ddb4f7957c69d" - ], - "index": "pypi", - "version": "==9.2.0" - }, - "azure-common": { - "hashes": [ - "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3", - "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad" - ], - "version": "==1.1.28" - }, - "azure-storage-blob": { - "hashes": [ - "sha256:a8e91a51d4f62d11127c7fd8ba0077385c5b11022f0269f8a2a71b9fc36bef31", - "sha256:b90323aad60f207f9f90a0c4cf94c10acc313c20b39403398dfba51f25f7b454" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "azure-storage-common": { - "hashes": [ - "sha256:b01a491a18839b9d05a4fe3421458a0ddb5ab9443c14e487f40d16f9a1dc2fbe", - "sha256:ccedef5c67227bc4d6670ffd37cec18fb529a1b7c3a5e53e4096eb0cf23dc73f" - ], - "version": "==2.1.0" - }, - "babel": { - "hashes": [ - "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51", - "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb" - ], - "markers": "python_version >= '3.6'", - "version": "==2.10.3" - }, - "bcrypt": { - "hashes": [ - "sha256:0b0f0c7141622a31e9734b7f649451147c04ebb5122327ac0bd23744df84be90", - "sha256:1c3334446fac200499e8bc04a530ce3cf0b3d7151e0e4ac5c0dddd3d95e97843", - "sha256:2d0dd19aad87e4ab882ef1d12df505f4c52b28b69666ce83c528f42c07379227", - "sha256:594780b364fb45f2634c46ec8d3e61c1c0f1811c4f2da60e8eb15594ecbf93ed", - "sha256:7c7dd6c1f05bf89e65261d97ac3a6520f34c2acb369afb57e3ea4449be6ff8fd", - "sha256:845b1daf4df2dd94d2fdbc9454953ca9dd0e12970a0bfc9f3dcc6faea3fa96e4", - "sha256:8780e69f9deec9d60f947b169507d2c9816e4f11548f1f7ebee2af38b9b22ae4", - "sha256:bf413f2a9b0a2950fc750998899013f2e718d20fa4a58b85ca50b6df5ed1bbf9", - "sha256:bfb67f6a6c72dfb0a02f3df51550aa1862708e55128b22543e2b42c74f3620d7", - "sha256:c59c170fc9225faad04dde1ba61d85b413946e8ce2e5f5f5ff30dfd67283f319", - "sha256:dc6ec3dc19b1c193b2f7cf279d3e32e7caf447532fbcb7af0906fe4398900c33", - "sha256:ede0f506554571c8eda80db22b83c139303ec6b595b8f60c4c8157bdd0bdee36" - ], - "markers": "python_version >= '3.6'", - "version": "==4.0.0" - }, - "breathe": { - "hashes": [ - "sha256:48804dcf0e607a89fb6ad88c729ef12743a42db03ae9489be4ef8f7c4011774a", - "sha256:ac0768a5e84addad3e632028fe67749c567aba2b29088493b64c2c1634bcdba1" - ], - "index": "pypi", - "version": "==4.34.0" - }, - "carla": { - "hashes": [ - "sha256:1210cce213e968a644effd4e2e48458a072481459d073424b05725056ba3d77d", - "sha256:339fcb1e392f3ade1be82b7258de19c533e2efae111e954a6eb174efb296903d", - "sha256:5f065825ce812343bf27a80a19d647b3200b31b44a9e80cea0340e3bd20cdf81", - "sha256:954ca34d5bdd4516ceca353db907fee8cec6630d6b31a732b17dd1554e0f0f94", - "sha256:a64ee78fe91137fa7d4828c7fc06d5824bd7312e29e4ea4f31a5d74dd28bff40", - "sha256:a95d2d4218ea388c863c66b7c2ab3fe49ffefe53999305cfcb6a8107042f79af", - "sha256:d2bfaea2d6824a2d758cbe813856c69420494f5c97d2a2dfb45653ccf976f1ce" - ], - "index": "pypi", - "markers": "platform_system != 'Darwin'", - "version": "==0.9.13" - }, - "certifi": { - "hashes": [ - "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14", - "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382" - ], - "markers": "python_version >= '3.6'", - "version": "==2022.9.24" - }, - "cffi": { - "hashes": [ - "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5", - "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef", - "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104", - "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426", - "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405", - "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375", - "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a", - "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e", - "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc", - "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf", - "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185", - "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497", - "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3", - "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35", - "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c", - "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83", - "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21", - "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca", - "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984", - "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac", - "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd", - "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee", - "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a", - "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2", - "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192", - "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7", - "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585", - "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f", - "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e", - "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27", - "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b", - "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e", - "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e", - "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d", - "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c", - "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415", - "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82", - "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02", - "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314", - "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325", - "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c", - "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3", - "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914", - "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045", - "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d", - "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9", - "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5", - "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2", - "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c", - "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3", - "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2", - "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8", - "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d", - "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d", - "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9", - "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162", - "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76", - "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4", - "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e", - "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9", - "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6", - "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b", - "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01", - "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0" - ], - "index": "pypi", - "version": "==1.15.1" - }, - "cfgv": { - "hashes": [ - "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426", - "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736" - ], - "markers": "python_full_version >= '3.6.1'", - "version": "==3.3.1" - }, - "charset-normalizer": { - "hashes": [ - "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845", - "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f" - ], - "markers": "python_version >= '3.6'", - "version": "==2.1.1" - }, - "contourpy": { - "hashes": [ - "sha256:0389349875424aa8c5e61f757e894687916bc4e9616cc6afcbd8051aa2428952", - "sha256:0395ae71164bfeb2dedd136e03c71a2718a5aa9873a46f518f4133be0d63e1d2", - "sha256:057114f698ffb9e54657e8fda6802e2f5c8fad609845cf6afaf31590ef6a33c0", - "sha256:061e1f066c419ffe25b615a1df031b4832ea1d7f2676937e69e8e00e24512005", - "sha256:06c4d1dde5ee4f909a8a95ba1eb04040c6c26946b4f3b5beaf10d45f14e940ee", - "sha256:09ed9b63f4df8a7591b7a4a26c1ad066dcaafda1f846250fdcb534074a411692", - "sha256:0f7672148f8fca48e4efc16aba24a7455b40c22d4f8abe42475dec6a12b0bb9a", - "sha256:0f89f0608a5aa8142ed0e53957916623791a88c7f5e5f07ae530c328beeb888f", - "sha256:128bd7acf569f8443ad5b2227f30ac909e4f5399ed221727eeacf0c6476187e6", - "sha256:19ea64fa0cf389d2ebc10974616acfa1fdecbd73d1fd9c72215b782f3c40f561", - "sha256:1fb782982c42cee667b892a0b0c52a9f6c7ecf1da5c5f4345845f04eaa862f93", - "sha256:218722a29c5c26677d37c44f5f8a372daf6f07870aad793a97d47eb6ad6b3290", - "sha256:2b5e334330d82866923015b455260173cb3b9e3b4e297052d758abd262031289", - "sha256:2bf5c846c257578b03d498b20f54f53551616a507d8e5463511c58bb58e9a9cf", - "sha256:2d0ad9a85f208473b1f3613c45756c7aa6fcc288266a8c7b873f896aaf741b6b", - "sha256:2f54dcc9bb9390fd0636301ead134d46d5229fe86da0db4d974c0fda349f560e", - "sha256:3109fa601d2a448cec4643abd3a31f972bf05b7c2f2e83df9d3429878f8c10ae", - "sha256:3210d93ad2af742b6a96cf39792f7181822edbb8fe11c3ef29d1583fe637a8d8", - "sha256:3b3082ade8849130203d461b98c2a061b382c46074b43b4edd5cefd81af92b8a", - "sha256:3c3f2f6b898a40207843ae01970e57e33d22a26b22f23c6a5e07b4716751085f", - "sha256:3ca40d7844b391d90b864c6a6d1bb6b88b09035fb4d866d64d43c4d26fb0ab64", - "sha256:3cfc067ddde78b76dcbc9684d82688b7d3c5158fa2254a085f9bcb9586c1e2d8", - "sha256:434942fa2f9019b9ae525fb752dc523800c49a1a28fbd6d9240b0fa959573dcc", - "sha256:46b8e24813e2fb5a3e598c1f8b9ae403e1438cb846a80cc2b33cddf19dddd7f2", - "sha256:59c827e536bb5e3ef58e06da0faba61fd89a14f30b68bcfeca41f43ca83a1942", - "sha256:60f37acd4e4227c5a29f737d9a85ca3145c529a8dd4bf70af7f0637c61b49222", - "sha256:689d7d2a840619915d0abd1ecc6e399fee202f8ad315acda2807f4ca420d0802", - "sha256:6c02e22cf09996194bcb3a4784099975cf527d5c29caf759abadf29ebdb2fe27", - "sha256:79908b9d02b1d6c1c71ff3b7ad127f3f82e14a8e091ab44b3c7e34b649fea733", - "sha256:7c9e99aac7b430f6a9f15eebf058c742097cea3369f23a2bfc5e64d374b67e3a", - "sha256:813c2944e940ef8dccea71305bacc942d4b193a021140874b3e58933ec44f5b6", - "sha256:87121b9428ac568fb84fae4af5e7852fc34f02eadc4e3e91f6c8989327692186", - "sha256:896631cd40222aef3697e4e51177d14c3709fda49d30983269d584f034acc8a4", - "sha256:970a4be7ec84ccda7c27cb4ae74930bbbd477bc8d849ed55ea798084dd5fca8c", - "sha256:9939796abcadb2810a63dfb26ff8ca4595fe7dd70a3ceae7f607a2639b714307", - "sha256:99a8071e351b50827ad976b92ed91845fb614ac67a3c41109b24f3d8bd3afada", - "sha256:9c16fa267740d67883899e054cccb4279e002f3f4872873b752c1ba15045ff49", - "sha256:a30e95274f5c0e007ccc759ec258aa5708c534ec058f153ee25ac700a2f1438b", - "sha256:a74afd8d560eaafe0d9e3e1db8c06081282a05ca4de00ee416195085a79d7d3d", - "sha256:b46a04588ceb7cf132568e0e564a854627ef87a1ed3bf536234540a79ced44b0", - "sha256:b4963cf08f4320d98ae72ec7694291b8ab85cb7da3b0cd824bc32701bc992edf", - "sha256:b50e481a4317a8efcfffcfddcd4c9b36eacba440440e70cbe0256aeb6fd6abae", - "sha256:b85553699862c09937a7a5ea14ee6229087971a7d51ae97d5f4b407f571a2c17", - "sha256:bcc98d397c3dea45d5b262029564b29cb8e945f2607a38bee6163694c0a8b4ef", - "sha256:bed3a2a823a041e8d249b1a7ec132933e1505299329b5cfe1b2b5ec689ec7675", - "sha256:bf6b4c0c723664f65c2a47c8cb6ebbf660b0b2e2d936adf2e8503d4e93359465", - "sha256:bfd634cb9685161b2a51f73a7fc4736fd0d67a56632d52319317afaa27f08243", - "sha256:c0d5ee865b5fd16bf62d72122aadcc90aab296c30c1adb0a32b4b66bd843163e", - "sha256:c2b4eab7c12f9cb460509bc34a3b086f9802f0dba27c89a63df4123819ad64af", - "sha256:c51568e94f7f232296de30002f2a50f77a7bd346673da3e4f2aaf9d2b833f2e5", - "sha256:c5158616ab39d34b76c50f40c81552ee180598f7825dc7a66fd187d29958820f", - "sha256:cdacddb18d55ffec42d1907079cdc04ec4fa8a990cdf5b9d9fe67d281fc0d12e", - "sha256:ce763369e646e59e4ca2c09735cd1bdd3048d909ad5f2bc116e83166a9352f3c", - "sha256:d45822b0a2a452327ab4f95efe368d234d5294bbf89a99968be27c7938a21108", - "sha256:d8150579bf30cdf896906baf256aa200cd50dbe6e565c17d6fd3d678e21ff5de", - "sha256:d88814befbd1433152c5f6dd536905149ba028d795a22555b149ae0a36024d9e", - "sha256:dca5be83a6dfaf933a46e3bc2b9f2685e5ec61b22f6a38ad740aac9c16e9a0ff", - "sha256:dd084459ecdb224e617e4ab3f1d5ebe4d1c48facb41f24952b76aa6ba9712bb0", - "sha256:def9a01b73c9e27d70ea03b381fb3e7aadfac1f398dbd63751313c3a46747ef5", - "sha256:df65f4b2b4e74977f0336bef12a88051ab24e6a16873cd9249f34d67cb3e345d", - "sha256:dfe924e5a63861c82332a12adeeab955dc8c8009ddbbd80cc2fcca049ff89a49", - "sha256:e67dcaa34dcd908fcccbf49194211d847c731b6ebaac661c1c889f1bf6af1e44", - "sha256:eba62b7c21a33e72dd8adab2b92dd5610d8527f0b2ac28a8e0770e71b21a13f9", - "sha256:ed9c91bf4ce614efed5388c3f989a7cfe08728ab871d995a486ea74ff88993db", - "sha256:f05d311c937da03b0cd26ac3e14cb991f6ff8fc94f98b3df9713537817539795", - "sha256:f1cc623fd6855b25da52b3275e0c9e51711b86a9dccc75f8c9ab4432fd8e42c7", - "sha256:f670686d99c867d0f24b28ce8c6f02429c6eef5e2674aab287850d0ee2d20437", - "sha256:f856652f9b533c6cd2b9ad6836a7fc0e43917d7ff15be46c5baf1350f8cdc5d9", - "sha256:fb0458d74726937ead9e2effc91144aea5a58ecee9754242f8539a782bed685a" - ], - "markers": "python_version >= '3.7'", - "version": "==1.0.5" - }, - "control": { - "hashes": [ - "sha256:0891d2d32d6006ac1faa4e238ed8223ca342a4721d202dfeccae24fb02563183" - ], - "index": "pypi", - "version": "==0.9.2" - }, - "coverage": { - "hashes": [ - "sha256:01778769097dbd705a24e221f42be885c544bb91251747a8a3efdec6eb4788f2", - "sha256:08002f9251f51afdcc5e3adf5d5d66bb490ae893d9e21359b085f0e03390a820", - "sha256:1238b08f3576201ebf41f7c20bf59baa0d05da941b123c6656e42cdb668e9827", - "sha256:14a32ec68d721c3d714d9b105c7acf8e0f8a4f4734c811eda75ff3718570b5e3", - "sha256:15e38d853ee224e92ccc9a851457fb1e1f12d7a5df5ae44544ce7863691c7a0d", - "sha256:354df19fefd03b9a13132fa6643527ef7905712109d9c1c1903f2133d3a4e145", - "sha256:35ef1f8d8a7a275aa7410d2f2c60fa6443f4a64fae9be671ec0696a68525b875", - "sha256:4179502f210ebed3ccfe2f78bf8e2d59e50b297b598b100d6c6e3341053066a2", - "sha256:42c499c14efd858b98c4e03595bf914089b98400d30789511577aa44607a1b74", - "sha256:4b7101938584d67e6f45f0015b60e24a95bf8dea19836b1709a80342e01b472f", - "sha256:564cd0f5b5470094df06fab676c6d77547abfdcb09b6c29c8a97c41ad03b103c", - "sha256:5f444627b3664b80d078c05fe6a850dd711beeb90d26731f11d492dcbadb6973", - "sha256:6113e4df2fa73b80f77663445be6d567913fb3b82a86ceb64e44ae0e4b695de1", - "sha256:61b993f3998ee384935ee423c3d40894e93277f12482f6e777642a0141f55782", - "sha256:66e6df3ac4659a435677d8cd40e8eb1ac7219345d27c41145991ee9bf4b806a0", - "sha256:67f9346aeebea54e845d29b487eb38ec95f2ecf3558a3cffb26ee3f0dcc3e760", - "sha256:6913dddee2deff8ab2512639c5168c3e80b3ebb0f818fed22048ee46f735351a", - "sha256:6a864733b22d3081749450466ac80698fe39c91cb6849b2ef8752fd7482011f3", - "sha256:7026f5afe0d1a933685d8f2169d7c2d2e624f6255fb584ca99ccca8c0e966fd7", - "sha256:783bc7c4ee524039ca13b6d9b4186a67f8e63d91342c713e88c1865a38d0892a", - "sha256:7a98d6bf6d4ca5c07a600c7b4e0c5350cd483c85c736c522b786be90ea5bac4f", - "sha256:8d032bfc562a52318ae05047a6eb801ff31ccee172dc0d2504614e911d8fa83e", - "sha256:98c0b9e9b572893cdb0a00e66cf961a238f8d870d4e1dc8e679eb8bdc2eb1b86", - "sha256:9c7b9b498eb0c0d48b4c2abc0e10c2d78912203f972e0e63e3c9dc21f15abdaa", - "sha256:9cc4f107009bca5a81caef2fca843dbec4215c05e917a59dec0c8db5cff1d2aa", - "sha256:9d6e1f3185cbfd3d91ac77ea065d85d5215d3dfa45b191d14ddfcd952fa53796", - "sha256:a095aa0a996ea08b10580908e88fbaf81ecf798e923bbe64fb98d1807db3d68a", - "sha256:a3b2752de32c455f2521a51bd3ffb53c5b3ae92736afde67ce83477f5c1dd928", - "sha256:ab066f5ab67059d1f1000b5e1aa8bbd75b6ed1fc0014559aea41a9eb66fc2ce0", - "sha256:c1328d0c2f194ffda30a45f11058c02410e679456276bfa0bbe0b0ee87225fac", - "sha256:c35cca192ba700979d20ac43024a82b9b32a60da2f983bec6c0f5b84aead635c", - "sha256:cbbb0e4cd8ddcd5ef47641cfac97d8473ab6b132dd9a46bacb18872828031685", - "sha256:cdbb0d89923c80dbd435b9cf8bba0ff55585a3cdb28cbec65f376c041472c60d", - "sha256:cf2afe83a53f77aec067033199797832617890e15bed42f4a1a93ea24794ae3e", - "sha256:d5dd4b8e9cd0deb60e6fcc7b0647cbc1da6c33b9e786f9c79721fd303994832f", - "sha256:dfa0b97eb904255e2ab24166071b27408f1f69c8fbda58e9c0972804851e0558", - "sha256:e16c45b726acb780e1e6f88b286d3c10b3914ab03438f32117c4aa52d7f30d58", - "sha256:e1fabd473566fce2cf18ea41171d92814e4ef1495e04471786cbc943b89a3781", - "sha256:e3d3c4cc38b2882f9a15bafd30aec079582b819bec1b8afdbde8f7797008108a", - "sha256:e431e305a1f3126477abe9a184624a85308da8edf8486a863601d58419d26ffa", - "sha256:e7b4da9bafad21ea45a714d3ea6f3e1679099e420c8741c74905b92ee9bfa7cc", - "sha256:ee2b2fb6eb4ace35805f434e0f6409444e1466a47f620d1d5763a22600f0f892", - "sha256:ee6ae6bbcac0786807295e9687169fba80cb0617852b2fa118a99667e8e6815d", - "sha256:ef6f44409ab02e202b31a05dd6666797f9de2aa2b4b3534e9d450e42dea5e817", - "sha256:f67cf9f406cf0d2f08a3515ce2db5b82625a7257f88aad87904674def6ddaec1", - "sha256:f855b39e4f75abd0dfbcf74a82e84ae3fc260d523fcb3532786bcbbcb158322c", - "sha256:fc600f6ec19b273da1d85817eda339fb46ce9eef3e89f220055d8696e0a06908", - "sha256:fcbe3d9a53e013f8ab88734d7e517eb2cd06b7e689bedf22c0eb68db5e4a0a19", - "sha256:fde17bc42e0716c94bf19d92e4c9f5a00c5feb401f5bc01101fdf2a8b7cacf60", - "sha256:ff934ced84054b9018665ca3967fc48e1ac99e811f6cc99ea65978e1d384454b" - ], - "index": "pypi", - "version": "==6.4.4" - }, - "cryptography": { - "hashes": [ - "sha256:0297ffc478bdd237f5ca3a7dc96fc0d315670bfa099c04dc3a4a2172008a405a", - "sha256:10d1f29d6292fc95acb597bacefd5b9e812099d75a6469004fd38ba5471a977f", - "sha256:16fa61e7481f4b77ef53991075de29fc5bacb582a1244046d2e8b4bb72ef66d0", - "sha256:194044c6b89a2f9f169df475cc167f6157eb9151cc69af8a2a163481d45cc407", - "sha256:1db3d807a14931fa317f96435695d9ec386be7b84b618cc61cfa5d08b0ae33d7", - "sha256:3261725c0ef84e7592597606f6583385fed2a5ec3909f43bc475ade9729a41d6", - "sha256:3b72c360427889b40f36dc214630e688c2fe03e16c162ef0aa41da7ab1455153", - "sha256:3e3a2599e640927089f932295a9a247fc40a5bdf69b0484532f530471a382750", - "sha256:3fc26e22840b77326a764ceb5f02ca2d342305fba08f002a8c1f139540cdfaad", - "sha256:5067ee7f2bce36b11d0e334abcd1ccf8c541fc0bbdaf57cdd511fdee53e879b6", - "sha256:52e7bee800ec869b4031093875279f1ff2ed12c1e2f74923e8f49c916afd1d3b", - "sha256:64760ba5331e3f1794d0bcaabc0d0c39e8c60bf67d09c93dc0e54189dfd7cfe5", - "sha256:765fa194a0f3372d83005ab83ab35d7c5526c4e22951e46059b8ac678b44fa5a", - "sha256:79473cf8a5cbc471979bd9378c9f425384980fcf2ab6534b18ed7d0d9843987d", - "sha256:896dd3a66959d3a5ddcfc140a53391f69ff1e8f25d93f0e2e7830c6de90ceb9d", - "sha256:89ed49784ba88c221756ff4d4755dbc03b3c8d2c5103f6d6b4f83a0fb1e85294", - "sha256:ac7e48f7e7261207d750fa7e55eac2d45f720027d5703cd9007e9b37bbb59ac0", - "sha256:ad7353f6ddf285aeadfaf79e5a6829110106ff8189391704c1d8801aa0bae45a", - "sha256:b0163a849b6f315bf52815e238bc2b2346604413fa7c1601eea84bcddb5fb9ac", - "sha256:b6c9b706316d7b5a137c35e14f4103e2115b088c412140fdbd5f87c73284df61", - "sha256:c2e5856248a416767322c8668ef1845ad46ee62629266f84a8f007a317141013", - "sha256:ca9f6784ea96b55ff41708b92c3f6aeaebde4c560308e5fbbd3173fbc466e94e", - "sha256:d1a5bd52d684e49a36582193e0b89ff267704cd4025abefb9e26803adeb3e5fb", - "sha256:d3971e2749a723e9084dd507584e2a2761f78ad2c638aa31e80bc7a15c9db4f9", - "sha256:d4ef6cc305394ed669d4d9eebf10d3a101059bdcf2669c366ec1d14e4fb227bd", - "sha256:d9e69ae01f99abe6ad646947bba8941e896cb3aa805be2597a0400e0764b5818" - ], - "index": "pypi", - "version": "==38.0.1" - }, - "cycler": { - "hashes": [ - "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3", - "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f" - ], - "markers": "python_version >= '3.6'", - "version": "==0.11.0" - }, - "dictdiffer": { - "hashes": [ - "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578", - "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595" - ], - "index": "pypi", - "version": "==0.9.0" - }, - "distlib": { - "hashes": [ - "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46", - "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e" - ], - "version": "==0.3.6" - }, - "docutils": { - "hashes": [ - "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125", - "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61" - ], - "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:03f8efe6435a7b947fa4a420676942d0267dac0d323ec5ead50f1856cc7cf96f", - "sha256:060c1cb3c84942d8d3618385e2c25998ba690c46ec8c73d64477f808abfac3f2", - "sha256:06df1d97edca68b2ffa43d81c3b5f4e4147bc12ab241c6585fadcdeb0bfa23ca", - "sha256:0bb54283b4d5ec96f167c7fd31921f169226c1261637434fdae7a69ee3c69573", - "sha256:11748a4e245c745030e9ddd8c2c37e378f8ad8bd8e869d954c84ff674495499f", - "sha256:1801c9daa9aa5bbbb0830efe8bd3034b4b7a417e4b8dd353683999be29797df2", - "sha256:2fcb0973ca0e6978e3242046338c350cbed1493108929231fae9bd35ad05a6d6", - "sha256:4093d5454bcbe48b30e32da5db43056a08889480a96e4555f28c1f7004fc5323", - "sha256:4b9cfd426966b8037bec2fc03a0d7a9c87313482c699b36ffa1432b49f84ed2e", - "sha256:58958a0333e3dfbad198394e9b7dd6254de0a3e622019b057288405b2a4a6bba", - "sha256:5fe543b6d45da27e84e5af6248722475b88943d8ef40d835cbabbb0ba5ee786b", - "sha256:6a7c7f51a6d2f5ab58b1d85e9d0af2af9600ec13bb43bc6aafc9085d2c4ccd93", - "sha256:6cf156d4203708348522393c523c2e61c81f5a6a500e0411dcba2b064551ea2f", - "sha256:6e51db0067e65687a5c46f00a11843d0bb15ca759e8a1767eebac8c4f6e3f4df", - "sha256:72503e727887a61a15f9aaa13178798d3994dfec58aa7a943e42dcfda07c0149", - "sha256:7254f81dc71cd29ef6f2d9747cf97ff907b569c9ef9d9760352391be5b57118c", - "sha256:855ab2b7e6fa9b05f19c4f3023dedfb1a35a88d831933d65d0d9e10a070a9e85", - "sha256:86a1ad972e83ba48144884fa849f87626346308b650002157123aee67d3b16fe", - "sha256:8bac5cf64691060cf86b0752dd385ef1eccff6d24bdb8b60691cf8cbf0e4f9ef", - "sha256:8d3c9eab8e69cb36dcdd64c8b3200e008aedf88e34d39e01ae6af98a9605ad18", - "sha256:9020899b67fe492d0ed87a3e993ec9962c5a0b51ea2df71d86b1766f065f1cef", - "sha256:a085e7e13f1afc517358981b2b7ed774dc9abf95f2be0da9a495d9e6b58c4409", - "sha256:a5ceb39379327316d34613f7c16c06d7a3816aa38f4437b5e8433aa1bf149e2c", - "sha256:a6f8da329c0032f2acaf4beaef958a2db0dae43d3f946f592dad5c29aa82c832", - "sha256:a952a84453123db0c2336b9a9c86162e99ad0b897bae8213107c055a64effd41", - "sha256:aa4a4c01c5fbec3623e92bc33a9f712ca416ce93255c402f5c904ac4b890ac3c", - "sha256:aab886efa7b6bba7ac124f4498153d053e5a08b822d2254926b7206cdf5a8aa6", - "sha256:ab9337b0a6a9b07b6f86fc724972d1ad729c890e2f539c1b33271c2f1f00af8b", - "sha256:c12224da0b1f2f9d2b3d715dc82ecb1a3a33b990606f97da075cc46bc6d9576f", - "sha256:c61be0bad81a21ee3e5bef91469fdd11968f33d41d142c656accba9b2992babe", - "sha256:c8be01f97bc2bf11a9188537864f8e520e1103cdc6007aa2c5d7979b1363b121", - "sha256:cb27c13225f5f77f3c5986a27ca27277cce7db12844330cf535019cd38021257", - "sha256:cf5acfe1156849279ebd44a8d1fbcbe8b8e21334f7538eda782ae31e7dade9e2", - "sha256:d0e8faef0437a25fd083df70fb86cc65ce3c9c9780d4ae377cbe6521e7746ce0", - "sha256:e03a228e018457842eb81de85be7af0b5fe8065d666dd093193e3bdcf1f13d2e", - "sha256:eb3f98791427d5d5d02d023b66bcef61e48954edfadae6527ef72d70cf32ec86", - "sha256:ffdb00782cd63bbf2c45bb048897531e868326dff5081ab9b752d294b0426c1d" - ], - "index": "pypi", - "version": "==1.2.6" - }, - "filelock": { - "hashes": [ - "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc", - "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4" - ], - "markers": "python_version >= '3.7'", - "version": "==3.8.0" - }, - "fonttools": { - "hashes": [ - "sha256:a5bc5f5d48faa4085310b8ebd4c5d33bf27c6636c5f10a7de792510af2745a81", - "sha256:f32ef6ec966cf0e7d2aa88601fed2e3a8f2851c26b5db2c80ccc8f82bee4eedc" - ], - "markers": "python_version >= '3.7'", - "version": "==4.37.3" - }, - "ft4222": { - "hashes": [ - "sha256:074f4eb234450306b9040d721eb5ab2a423d3b07d6f4a30824198afeb0a9bbb6", - "sha256:09f18a5610d0d81c568c9095798f542166b98025d119c9fa91555fa5bfddc2ee", - "sha256:0cceacb43d75a5cb3cdf2d95d4164fb2c1cfde007d9782538150bc10d7933680", - "sha256:10136015000719f68b2a4b319b612183a1601816870521e6a45c8c0b67d38325", - "sha256:1894469abffd739fc3a83f818ea29532283965c74c3c64e5c9b9e6b454971d03", - "sha256:24cdb72a7cd420c1ed009c0821555b9818b8828dc31a1a01224a35d4757c7e5d", - "sha256:286a2f3c023e9beb5c6ca6b692c3ed92a1fe44b326f7cb58807d0b99f372c7d7", - "sha256:28b472bbbb18e6f65dacd139231a53640827f2e486c54750fbcff7a16454cb6d", - "sha256:296555ca4096b5ce13e79b089622de5cf9236f3d32b074c8483ae72490e7448b", - "sha256:396f5c7c38e0c8dc1b31d5d9709d7eb86ef0ee75412867ccc352506aa3a29ae4", - "sha256:39a7d8795d32b8c126ed253a0c1a9d7971f3af83d2ceb796af47fbef03485741", - "sha256:50e37f59b8e553384cdbfab64096e3fb1c7ca9f15ae419ae0d0fdda3a2e05f54", - "sha256:511b785a23ba2fc8480dbaeda33e1f22ffe5dd58731e1c53e379989731fb42ad", - "sha256:5f91e530ee6fe6a13c08ec4dc3c7f54a704fc642b3d73f4392a9db22c5e243dd", - "sha256:65e5b7bfbe3552b771770807df0a251616bb2c8f7541e4e9f350715f225a71c0", - "sha256:6e208af13621b8a79a8c623c8deb3b971f4d4e4587156622a6558b91719f9d33", - "sha256:70aec6df75d1f8ee051c5d16a48363e4d3552feecf3466cec2700415c073e5e4", - "sha256:9670396daab3deb91847ee40c0338bca07f4041176b2aed1c49277dc1ef3497f", - "sha256:a300c2749adb674ef3d95b17a1311deaf0a3318e14ee7e9d7e56317e307b0012", - "sha256:a675b88124dfb1d2744f27823e3dfd094c0236674e453e83d486fc17358761ff", - "sha256:ab5ea9522bd0fd1b87348bf26d0a1131e87f586366271581c9b1d0acdf870173", - "sha256:baf80af3de3af376080bfb8f75f3d6aba9e9415001a6f72299cbb344e6b739cb", - "sha256:bb3cb6485d7a0d1eac0e0027eab6b9ec95e5f5722e853cdc2850d2ae70086eea", - "sha256:c5a993dd3af47ab69f5b58920dba15c98658de7a73b9c81d402fe6eaf292edab", - "sha256:c7e31cefcdcfe3653df35cd993f821b746e747b294ded8bff27d1f8bd8c5e43b", - "sha256:caec2458db0d8e29888da2c22aa4427c1d993e943ed325ef7a6b8eb24f55d163", - "sha256:d688830cf004cde39b3cc757fef8ec31ae266fceda1566ca53b3b79e5ab7b6e4" - ], - "index": "pypi", - "version": "==1.5.0" - }, - "hexdump": { - "hashes": [ - "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db" - ], - "index": "pypi", - "version": "==3.3" - }, - "hypothesis": { - "hashes": [ - "sha256:2696cdb9005946bf1d2b215cc91d3fc01625e3342eb8743ddd04b667b2f1882b", - "sha256:4ad26c5d434171ffc02aba569dd52255573d615554c062bc30734dbe6f318c61", - "sha256:69978811f1d9c19710c7d2bf8233dc43c80efa964251b72efbe8274044e073b4", - "sha256:967009fa561b3a3f8363a73d71923357271c37dc7fa27b30c2d21a1b6092b240" - ], - "index": "pypi", - "version": "==6.46.7" - }, - "identify": { - "hashes": [ - "sha256:322a5699daecf7c6fd60e68852f36f2ecbb6a36ff6e6e973e0d2bb6fca203ee6", - "sha256:ef78c0d96098a3b5fe7720be4a97e73f439af7cf088ebf47b620aeaa10fadf97" - ], - "markers": "python_version >= '3.7'", - "version": "==2.5.5" - }, - "idna": { - "hashes": [ - "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4", - "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2" - ], - "markers": "python_version >= '3.5'", - "version": "==3.4" - }, - "imagesize": { - "hashes": [ - "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b", - "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.4.1" - }, - "importlib-metadata": { - "hashes": [ - "sha256:637245b8bab2b6502fcbc752cc4b7a6f6243bb02b31c5c26156ad103d3d45670", - "sha256:7401a975809ea1fdc658c3aa4f78cc2195a0e019c5cbc4c06122884e9ae80c23" - ], - "markers": "python_version < '3.10'", - "version": "==4.12.0" - }, - "iniconfig": { - "hashes": [ - "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3", - "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32" - ], - "version": "==1.1.1" - }, - "inputs": { - "hashes": [ - "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec", - "sha256:a31d5b96a3525f1232f326be9e7ce8ccaf873c6b1fb84d9f3c9bc3d79b23eae4" - ], - "index": "pypi", - "version": "==0.5" - }, - "jinja2": { - "hashes": [ - "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", - "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" - ], - "index": "pypi", - "version": "==3.1.2" - }, - "kiwisolver": { - "hashes": [ - "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b", - "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166", - "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c", - "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c", - "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0", - "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4", - "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9", - "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286", - "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767", - "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c", - "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6", - "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b", - "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004", - "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf", - "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494", - "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac", - "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626", - "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766", - "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514", - "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6", - "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f", - "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d", - "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191", - "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d", - "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51", - "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f", - "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8", - "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454", - "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb", - "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da", - "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8", - "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de", - "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a", - "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9", - "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008", - "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3", - "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32", - "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938", - "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1", - "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9", - "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d", - "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824", - "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b", - "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd", - "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2", - "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5", - "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69", - "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3", - "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae", - "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597", - "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e", - "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955", - "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca", - "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a", - "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea", - "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede", - "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4", - "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6", - "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686", - "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408", - "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871", - "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29", - "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750", - "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897", - "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0", - "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2", - "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09", - "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c" - ], - "markers": "python_version >= '3.7'", - "version": "==1.4.4" - }, - "lru-dict": { - "hashes": [ - "sha256:075b9dd46d7022b675419bc6e3631748ae184bc8af195d20365a98b4f3bb2914", - "sha256:0972d669e9e207617e06416166718b073a49bf449abbd23940d9545c0847a4d9", - "sha256:0f83cd70a6d32f9018d471be609f3af73058f700691657db4a3d3dd78d3f96dd", - "sha256:10fe823ff90b655f0b6ba124e2b576ecda8c61b8ead76b456db67831942d22f2", - "sha256:163079dbda54c3e6422b23da39fb3ecc561035d65e8496ff1950cbdb376018e1", - "sha256:1fe16ade5fd0a57e9a335f69b8055aaa6fb278fbfa250458e4f6b8255115578f", - "sha256:262a4e622010ceb960a6a5222ed011090e50954d45070fd369c0fa4d2ed7d9a9", - "sha256:2f340b61f3cdfee71f66da7dbfd9a5ea2db6974502ccff2065cdb76619840dca", - "sha256:348167f110494cfafae70c066470a6f4e4d43523933edf16ccdb8947f3b5fae0", - "sha256:3b1692755fef288b67af5cd8a973eb331d1f44cb02cbdc13660040809c2bfec6", - "sha256:3ca497cb25f19f24171f9172805f3ff135b911aeb91960bd4af8e230421ccb51", - "sha256:3d003a864899c29b0379e412709a6e516cbd6a72ee10b09d0b33226343617412", - "sha256:3fef595c4f573141d54a38bda9221b9ee3cbe0acc73d67304a1a6d5972eb2a02", - "sha256:484ac524e4615f06dc72ffbfd83f26e073c9ec256de5413634fbd024c010a8bc", - "sha256:55aeda6b6789b2d030066b4f5f6fc3596560ba2a69028f35f3682a795701b5b1", - "sha256:5a592363c93d6fc6472d5affe2819e1c7590746aecb464774a4f67e09fbefdfc", - "sha256:5b09dbe47bc4b4d45ffe56067aff190bc3c0049575da6e52127e114236e0a6a7", - "sha256:6e2a7aa9e36626fb48fdc341c7e3685a31a7b50ea4918677ea436271ad0d904d", - "sha256:70364e3cbef536adab8762b4835e18f5ca8e3fddd8bd0ec9258c42bbebd0ee77", - "sha256:720f5728e537f11a311e8b720793a224e985d20e6b7c3d34a891a391865af1a2", - "sha256:7284bdbc5579bbdc3fc8f869ed4c169f403835566ab0f84567cdbfdd05241847", - "sha256:7be1b66926277993cecdc174c15a20c8ce785c1f8b39aa560714a513eef06473", - "sha256:86d32a4498b74a75340497890a260d37bf1560ad2683969393032977dd36b088", - "sha256:878bc8ef4073e5cfb953dfc1cf4585db41e8b814c0106abde34d00ee0d0b3115", - "sha256:881104711900af45967c2e5ce3e62291dd57d5b2a224d58b7c9f60bf4ad41b8c", - "sha256:8c50ab9edaa5da5838426816a2b7bcde9d576b4fc50e6a8c062073dbc4969d78", - "sha256:8f6561f9cd5a452cb84905c6a87aa944fdfdc0f41cc057d03b71f9b29b2cc4bd", - "sha256:93336911544ebc0e466272043adab9fb9f6e9dcba6024b639c32553a3790e089", - "sha256:9447214e4857e16d14158794ef01e4501d8fad07d298d03308d9f90512df02fa", - "sha256:97c24ffc55de6013075979f440acd174e88819f30387074639fb7d7178ca253e", - "sha256:99f6cfb3e28490357a0805b409caf693e46c61f8dbb789c51355adb693c568d3", - "sha256:9be6c4039ef328676b868acea619cd100e3de1a35b3be211cf0eaf9775563b65", - "sha256:9d70257246b8207e8ef3d8b18457089f5ff0dfb087bd36eb33bce6584f2e0b3a", - "sha256:a777d48319d293b1b6a933d606c0e4899690a139b4c81173451913bbcab6f44f", - "sha256:add762163f4af7f4173fafa4092eb7c7f023cf139ef6d2015cfea867e1440d82", - "sha256:b6f64005ede008b7a866be8f3f6274dbf74e656e15e4004e9d99ad65efb01809", - "sha256:beb089c46bd95243d1ac5b2bd13627317b08bf40dd8dc16d4b7ee7ecb3cf65ca", - "sha256:c07163c9dcbb2eca377f366b1331f46302fd8b6b72ab4d603087feca00044bb0", - "sha256:c2fe692332c2f1d81fd27457db4b35143801475bfc2e57173a2403588dd82a42", - "sha256:ca8f89361e0e7aad0bf93ae03a31502e96280faeb7fb92267f4998fb230d36b2", - "sha256:d2ed4151445c3f30423c2698f72197d64b27b1cd61d8d56702ffe235584e47c2", - "sha256:db20597c4e67b4095b376ce2e83930c560f4ce481e8d05737885307ed02ba7c1", - "sha256:de972c7f4bc7b6002acff2a8de984c55fbd7f2289dba659cfd90f7a0f5d8f5d1", - "sha256:f1df1da204a9f0b5eb8393a46070f1d984fa8559435ee790d7f8f5602038fc00", - "sha256:f4d0a6d733a23865019b1c97ed6fb1fdb739be923192abf4dbb644f697a26a69", - "sha256:f874e9c2209dada1a080545331aa1277ec060a13f61684a8642788bf44b2325f", - "sha256:f877f53249c3e49bbd7612f9083127290bede6c7d6501513567ab1bf9c581381", - "sha256:f9d5815c0e85922cd0fb8344ca8b1c7cf020bf9fc45e670d34d51932c91fd7ec" - ], - "index": "pypi", - "version": "==1.1.8" - }, - "lxml": { - "hashes": [ - "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318", - "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c", - "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b", - "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000", - "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73", - "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d", - "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb", - "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8", - "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2", - "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345", - "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94", - "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e", - "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b", - "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc", - "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a", - "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9", - "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc", - "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387", - "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb", - "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7", - "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4", - "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97", - "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67", - "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627", - "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7", - "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd", - "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3", - "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7", - "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130", - "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b", - "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036", - "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785", - "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca", - "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91", - "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc", - "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536", - "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391", - "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3", - "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d", - "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21", - "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3", - "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d", - "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29", - "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715", - "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed", - "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25", - "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c", - "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785", - "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837", - "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4", - "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b", - "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2", - "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067", - "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448", - "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d", - "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2", - "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc", - "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c", - "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5", - "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84", - "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8", - "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf", - "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7", - "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e", - "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb", - "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b", - "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3", - "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad", - "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8", - "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f" - ], - "index": "pypi", - "version": "==4.9.1" - }, - "markdown-it-py": { - "hashes": [ - "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27", - "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "markupsafe": { - "hashes": [ - "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003", - "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88", - "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5", - "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7", - "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a", - "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603", - "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1", - "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135", - "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247", - "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6", - "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601", - "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77", - "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02", - "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e", - "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63", - "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f", - "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980", - "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b", - "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812", - "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff", - "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96", - "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1", - "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925", - "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a", - "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6", - "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e", - "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f", - "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4", - "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f", - "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3", - "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c", - "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a", - "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417", - "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a", - "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a", - "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37", - "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452", - "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933", - "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a", - "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7" - ], - "markers": "python_version >= '3.7'", - "version": "==2.1.1" - }, - "matplotlib": { - "hashes": [ - "sha256:0958fc3fdc59c1b716ee1a5d14e73d03d541d873241a37c5c3a86f7ef6017923", - "sha256:0ae1b9b555212c1e242666af80e7ed796705869581e2d749971db4e682ccc1f3", - "sha256:11c1987b803cc2b26725659cfe817478f0a9597878e5c4bf374cfe4e12cbbd79", - "sha256:140316427a7c384e3dd37efb3a73cd67e14b0b237a6d277def91227f43cdcec2", - "sha256:1559213b803959a2b8309122585b5226d1c2fb66c933b1a2094cf1e99cb4fb90", - "sha256:16a899b958dd76606b571bc7eaa38f09160c27dfb262e493584644cfd4a77f0f", - "sha256:1739935d293d0348d7bf662e8cd0edb9c2aa8f20ccd646db755ce0f3456d24e4", - "sha256:1a4835c177821f3729be27ae9be7b8ae209fe75e83db7d9b2bfd319a998f0a42", - "sha256:2b60d4abcb6a405ca7d909c80791b00637d22c62aa3bb0ffff7e589f763867f5", - "sha256:2ed779a896b70c8012fe301fb91ee37e713e1dda1eb8f37de04cdbf506706983", - "sha256:3ec2edf7f74829eae287aa53d64d83ad5d43ee51d29fb1d88e689d8b36028312", - "sha256:408bbf968c15e9e38df9f25a588e372e28a43240cf5884c9bc6039a5021b7d5b", - "sha256:4699bb671dbc4afdb544eb893e4deb8a34e294b7734733f65b4fd2787ba5fbc6", - "sha256:4eba6972b796d97c8fcc5266b6dc42ef27c2dce4421b846cded0f3af851b81c9", - "sha256:51092d13499be72e47c15c3a1ae0209edaca6be42b65ffbbefbe0c85f6153c6f", - "sha256:62319d57dab5ad3e3494dd97a214e22079d3f72a0c8a2fd001829c2c6abbf8d1", - "sha256:657fb7712185f82211170ac4debae0800ed4f5992b8f7ebba2a9eabaf133a857", - "sha256:66a0db13f77aa7806dba29273874cf862450c61c2e5158245d17ee85d983fe8e", - "sha256:6b98e098549d3aea2bfb93f38f0b2ecadcb423fa1504bbff902c01efdd833fd8", - "sha256:7127e2b94571318531caf098dc9e8f60f5aba1704600f0b2483bf151d535674a", - "sha256:798559837156b8e2e2df97cffca748c5c1432af6ec5004c2932e475d813f1743", - "sha256:802feae98addb9f21707649a7f229c90a59fad34511881f20b906a5e8e6ea475", - "sha256:89e1978c3fbe4e3d4c6ad7db7e6f982607cb2546f982ccbe42708392437b1972", - "sha256:9295ca10a140c21e40d2ee43ef423213dc20767f6cea6b87c36973564bc51095", - "sha256:9711ef291e184b5a73c9d3af3f2d5cfe25d571c8dd95aa498415f74ac7e221a8", - "sha256:b0320f882214f6ffde5992081520b57b55450510bdaa020e96aacff9b7ae10e6", - "sha256:b5bd3b3ff191f81509d9a1afd62e1e3cda7a7889c35b5b6359a1241fe1511015", - "sha256:baa19508d8445f5648cd1ffe4fc6d4f7daf8b876f804e9a453df6c3708f6200b", - "sha256:c5108ebe67da60a9204497d8d403316228deb52b550388190c53a57394d41531", - "sha256:ccea337fb9a44866c5300c594b13d4d87e827ebc3c353bff15d298bac976b654", - "sha256:cd73a16a759865831be5a8fb6546f2a908c8d7d7f55c75f94ee7c2ca13cc95de", - "sha256:d840712f4b4c7d2a119f993d7e43ca9bcaa73aeaa24c322fa2bdf4f689a3ee09", - "sha256:df26a09d955b3ab9b6bc18658b9403ed839096c97d7abe8806194e228a485a3c", - "sha256:e01382c06ac3710155a0ca923047c5abe03c676d08f03e146c6a240d0a910713", - "sha256:e572c67958f7d55eae77f5f64dc7bd31968cc9f24c233926833efe63c60545f2", - "sha256:eca6f59cd0729edaeaa7032d582dffce518a420d4961ef3e8c93dce86be352c3", - "sha256:efd2e12f8964f8fb4ba1984df71d85d02ef0531e687e59f78ec8fc07271a3857", - "sha256:efe9e8037b989b14bb1887089ae763385431cc06fe488406413079cfd2a3a089", - "sha256:f0d5b9b14ccc7f539143ac9eb1c6b57d26d69ca52d30c3d719a7bc4123579e44", - "sha256:f1954d71cdf15c19e7f3bf2235a4fe1600ba42f34d472c9495bcf54d75a43e4e", - "sha256:fbbceb0a0dfe9213f6314510665a32ef25fe29b50657567cd00115fbfcb3b20d" - ], - "index": "pypi", - "version": "==3.6.0" - }, - "mdit-py-plugins": { - "hashes": [ - "sha256:b1279701cee2dbf50e188d3da5f51fee8d78d038cdf99be57c6b9d1aa93b4073", - "sha256:ecc24f51eeec6ab7eecc2f9724e8272c2fb191c2e93cf98109120c2cace69750" - ], - "markers": "python_version ~= '3.6'", - "version": "==0.3.0" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, - "mpld3": { - "hashes": [ - "sha256:1a167dbef836dd7c66d8aa71c06a32d50bffa18725f304d93cb74fdb3545043b", - "sha256:41938e65de4ba41a1b53d92e7c8609e7172e09b33ef5db42bb6f73701106c8b7" - ], - "index": "pypi", - "version": "==0.5.8" - }, - "mypy": { - "hashes": [ - "sha256:794f385653e2b749387a42afb1e14c2135e18daeb027e0d97162e4b7031210f8", - "sha256:ad77c13037d3402fbeffda07d51e3f228ba078d1c7096a73759c9419ea031bf4", - "sha256:fa38f82f53e1e7beb45557ff167c177802ba7b387ad017eab1663d567017c8ee" - ], - "index": "pypi", - "version": "==0.981" - }, - "mypy-extensions": { - "hashes": [ - "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d", - "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8" - ], - "version": "==0.4.3" - }, - "myst-parser": { - "hashes": [ - "sha256:4965e51918837c13bf1c6f6fe2c6bddddf193148360fbdaefe743a4981358f6a", - "sha256:739a4d96773a8e55a2cacd3941ce46a446ee23dcd6b37e06f73f551ad7821d86" - ], - "index": "pypi", - "version": "==0.18.0" - }, - "natsort": { - "hashes": [ - "sha256:04fe18fdd2b9e5957f19f687eb117f102ef8dde6b574764e536e91194bed4f5f", - "sha256:57f85b72c688b09e053cdac302dd5b5b53df5f73ae20b4874fcbffd8bf783d11" - ], - "index": "pypi", - "version": "==8.2.0" - }, - "nodeenv": { - "hashes": [ - "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e", - "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6'", - "version": "==1.7.0" - }, - "numpy": { - "hashes": [ - "sha256:004f0efcb2fe1c0bd6ae1fcfc69cc8b6bf2407e0f18be308612007a0762b4089", - "sha256:09f6b7bdffe57fc61d869a22f506049825d707b288039d30f26a0d0d8ea05164", - "sha256:0ea3f98a0ffce3f8f57675eb9119f3f4edb81888b6874bc1953f91e0b1d4f440", - "sha256:17c0e467ade9bda685d5ac7f5fa729d8d3e76b23195471adae2d6a6941bd2c18", - "sha256:1f27b5322ac4067e67c8f9378b41c746d8feac8bdd0e0ffede5324667b8a075c", - "sha256:22d43376ee0acd547f3149b9ec12eec2f0ca4a6ab2f61753c5b29bb3e795ac4d", - "sha256:2ad3ec9a748a8943e6eb4358201f7e1c12ede35f510b1a2221b70af4bb64295c", - "sha256:301c00cf5e60e08e04d842fc47df641d4a181e651c7135c50dc2762ffe293dbd", - "sha256:39a664e3d26ea854211867d20ebcc8023257c1800ae89773cbba9f9e97bae036", - "sha256:51bf49c0cd1d52be0a240aa66f3458afc4b95d8993d2d04f0d91fa60c10af6cd", - "sha256:78a63d2df1d947bd9d1b11d35564c2f9e4b57898aae4626638056ec1a231c40c", - "sha256:7cd1328e5bdf0dee621912f5833648e2daca72e3839ec1d6695e91089625f0b4", - "sha256:8355fc10fd33a5a70981a5b8a0de51d10af3688d7a9e4a34fcc8fa0d7467bb7f", - "sha256:8c79d7cf86d049d0c5089231a5bcd31edb03555bd93d81a16870aa98c6cfb79d", - "sha256:91b8d6768a75247026e951dce3b2aac79dc7e78622fc148329135ba189813584", - "sha256:94c15ca4e52671a59219146ff584488907b1f9b3fc232622b47e2cf832e94fb8", - "sha256:98dcbc02e39b1658dc4b4508442a560fe3ca5ca0d989f0df062534e5ca3a5c1a", - "sha256:a64403f634e5ffdcd85e0b12c08f04b3080d3e840aef118721021f9b48fc1460", - "sha256:bc6e8da415f359b578b00bcfb1d08411c96e9a97f9e6c7adada554a0812a6cc6", - "sha256:bdc9febce3e68b697d931941b263c59e0c74e8f18861f4064c1f712562903411", - "sha256:c1ba66c48b19cc9c2975c0d354f24058888cdc674bebadceb3cdc9ec403fb5d1", - "sha256:c9f707b5bb73bf277d812ded9896f9512a43edff72712f31667d0a8c2f8e71ee", - "sha256:d5422d6a1ea9b15577a9432e26608c73a78faf0b9039437b075cf322c92e98e7", - "sha256:e5d5420053bbb3dd64c30e58f9363d7a9c27444c3648e61460c1237f9ec3fa14", - "sha256:e868b0389c5ccfc092031a861d4e158ea164d8b7fdbb10e3b5689b4fc6498df6", - "sha256:efd9d3abe5774404becdb0748178b48a218f1d8c44e0375475732211ea47c67e", - "sha256:f8c02ec3c4c4fcb718fdf89a6c6f709b14949408e8cf2a2be5bfa9c49548fd85", - "sha256:ffcf105ecdd9396e05a8e58e81faaaf34d3f9875f137c7372450baa5d77c9a54" - ], - "index": "pypi", - "version": "==1.23.3" - }, - "opencv-python-headless": { - "hashes": [ - "sha256:21e70f8b0c04098cdf466d27184fe6c3820aaef944a22548db95099959c95889", - "sha256:2c032c373e447c3fc2a670bca20e2918a1205a6e72854df60425fd3f82c78c32", - "sha256:3bacd806cce1f1988e58f3d6f761538e0215d6621d316de94c009dc0acbd6ad3", - "sha256:d5291d7e10aa2c19cab6fd86f0d61af8617290ecd2d7ffcb051e446868d04cc5", - "sha256:e6c069bc963d7e8fcec21b3e33e594d35948badd63eccb2e80f88b0a12102c03", - "sha256:eec6281054346103d6af93f173b7c6a206beb2663d3adc04aa3ddc66e85093df", - "sha256:ffbf26fcd697af996408440a93bc69c49c05a36845771f984156dfbeaa95d497" - ], - "index": "pypi", - "version": "==4.6.0.66" - }, - "packaging": { - "hashes": [ - "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb", - "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522" - ], - "markers": "python_version >= '3.6'", - "version": "==21.3" - }, - "pandas": { - "hashes": [ - "sha256:0d8d7433d19bfa33f11c92ad9997f15a902bda4f5ad3a4814a21d2e910894484", - "sha256:1642fc6138b4e45d57a12c1b464a01a6d868c0148996af23f72dde8d12486bbc", - "sha256:171cef540bfcec52257077816a4dbbac152acdb8236ba11d3196ae02bf0959d8", - "sha256:1b82ccc7b093e0a93f8dffd97a542646a3e026817140e2c01266aaef5fdde11b", - "sha256:1d34b1f43d9e3f4aea056ba251f6e9b143055ebe101ed04c847b41bb0bb4a989", - "sha256:207d63ac851e60ec57458814613ef4b3b6a5e9f0b33c57623ba2bf8126c311f8", - "sha256:2504c032f221ef9e4a289f5e46a42b76f5e087ecb67d62e342ccbba95a32a488", - "sha256:33a9d9e21ab2d91e2ab6e83598419ea6a664efd4c639606b299aae8097c1c94f", - "sha256:3ee61b881d2f64dd90c356eb4a4a4de75376586cd3c9341c6c0fcaae18d52977", - "sha256:41aec9f87455306496d4486df07c1b98c15569c714be2dd552a6124cd9fda88f", - "sha256:4e30a31039574d96f3d683df34ccb50bb435426ad65793e42a613786901f6761", - "sha256:5cc47f2ebaa20ef96ae72ee082f9e101b3dfbf74f0e62c7a12c0b075a683f03c", - "sha256:62e61003411382e20d7c2aec1ee8d7c86c8b9cf46290993dd8a0a3be44daeb38", - "sha256:73844e247a7b7dac2daa9df7339ecf1fcf1dfb8cbfd11e3ffe9819ae6c31c515", - "sha256:85a516a7f6723ca1528f03f7851fa8d0360d1d6121cf15128b290cf79b8a7f6a", - "sha256:86d87279ebc5bc20848b4ceb619073490037323f80f515e0ec891c80abad958a", - "sha256:8a4fc04838615bf0a8d3a03ed68197f358054f0df61f390bcc64fbe39e3d71ec", - "sha256:8e8e5edf97d8793f51d258c07c629bd49d271d536ce15d66ac00ceda5c150eb3", - "sha256:947ed9f896ee61adbe61829a7ae1ade493c5a28c66366ec1de85c0642009faac", - "sha256:a68a9b9754efff364b0c5ee5b0f18e15ca640c01afe605d12ba8b239ca304d6b", - "sha256:c76f1d104844c5360c21d2ef0e1a8b2ccf8b8ebb40788475e255b9462e32b2be", - "sha256:c7f38d91f21937fe2bec9449570d7bf36ad7136227ef43b321194ec249e2149d", - "sha256:de34636e2dc04e8ac2136a8d3c2051fd56ebe9fd6cd185581259330649e73ca9", - "sha256:e178ce2d7e3b934cf8d01dc2d48d04d67cb0abfaffdcc8aa6271fd5a436f39c8", - "sha256:e252a9e49b233ff96e2815c67c29702ac3a062098d80a170c506dff3470fd060", - "sha256:e9c5049333c5bebf993033f4bf807d163e30e8fada06e1da7fa9db86e2392009", - "sha256:fc987f7717e53d372f586323fff441263204128a1ead053c1b98d7288f836ac9" - ], - "index": "pypi", - "version": "==1.5.0" - }, - "parameterized": { - "hashes": [ - "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c", - "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9" - ], - "index": "pypi", - "version": "==0.8.1" - }, - "paramiko": { - "hashes": [ - "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938", - "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270" - ], - "index": "pypi", - "version": "==2.11.0" - }, - "pillow": { - "hashes": [ - "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927", - "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14", - "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc", - "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58", - "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60", - "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76", - "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c", - "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac", - "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490", - "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1", - "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f", - "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d", - "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f", - "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069", - "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402", - "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437", - "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885", - "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e", - "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be", - "sha256:408673ed75594933714482501fe97e055a42996087eeca7e5d06e33218d05aa8", - "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff", - "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da", - "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004", - "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f", - "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20", - "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d", - "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c", - "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544", - "sha256:727dd1389bc5cb9827cbd1f9d40d2c2a1a0c9b32dd2261db522d22a604a6eec9", - "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3", - "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04", - "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c", - "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5", - "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4", - "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb", - "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4", - "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c", - "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467", - "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e", - "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421", - "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b", - "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8", - "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb", - "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3", - "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc", - "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf", - "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1", - "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a", - "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28", - "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0", - "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1", - "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8", - "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd", - "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4", - "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8", - "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f", - "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013", - "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59", - "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc", - "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4" - ], - "index": "pypi", - "version": "==9.2.0" - }, - "platformdirs": { - "hashes": [ - "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788", - "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19" - ], - "markers": "python_version >= '3.7'", - "version": "==2.5.2" - }, - "pluggy": { - "hashes": [ - "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159", - "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3" - ], - "markers": "python_version >= '3.6'", - "version": "==1.0.0" - }, - "pprofile": { - "hashes": [ - "sha256:b2bb56603dadf40c0bc0f61621f22c20e41638425f729945d9b7f8e4ae8cdd4a" - ], - "index": "pypi", - "version": "==2.1.0" - }, - "pre-commit": { - "hashes": [ - "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7", - "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959" - ], - "index": "pypi", - "version": "==2.20.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" - ], - "version": "==2.21" - }, - "pycurl": { - "hashes": [ - "sha256:a863ad18ff478f5545924057887cdae422e1b2746e41674615f687498ea5b88a" - ], - "index": "pypi", - "version": "==7.45.1" - }, - "pygame": { - "hashes": [ - "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:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1", - "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42" - ], - "markers": "python_version >= '3.6'", - "version": "==2.13.0" - }, - "pynacl": { - "hashes": [ - "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858", - "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d", - "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93", - "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1", - "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92", - "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff", - "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba", - "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394", - "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b", - "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543" - ], - "markers": "python_version >= '3.6'", - "version": "==1.5.0" - }, - "pyparsing": { - "hashes": [ - "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", - "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" - ], - "markers": "python_full_version >= '3.6.8'", - "version": "==3.0.9" - }, - "pyprof2calltree": { - "hashes": [ - "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f" - ], - "index": "pypi", - "version": "==1.4.5" - }, - "pytest": { - "hashes": [ - "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7", - "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39" - ], - "index": "pypi", - "version": "==7.1.3" - }, - "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", - "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9" - ], - "index": "pypi", - "version": "==2.8.2" - }, - "pytz": { - "hashes": [ - "sha256:220f481bdafa09c3955dfbdddb7b57780e9a94f5127e35456a48589b9e0c0197", - "sha256:cea221417204f2d1a2aa03ddae3e867921971d0d76f14d87abb4414415bbdcf5" - ], - "version": "==2022.2.1" - }, - "pyyaml": { - "hashes": [ - "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf", - "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293", - "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b", - "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57", - "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b", - "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4", - "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07", - "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba", - "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9", - "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287", - "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513", - "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0", - "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782", - "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0", - "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92", - "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f", - "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2", - "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc", - "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1", - "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c", - "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86", - "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4", - "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c", - "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34", - "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b", - "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d", - "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c", - "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb", - "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7", - "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737", - "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3", - "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d", - "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358", - "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53", - "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78", - "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803", - "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a", - "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f", - "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174", - "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5" - ], - "index": "pypi", - "version": "==6.0" - }, - "requests": { - "hashes": [ - "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983", - "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349" - ], - "index": "pypi", - "version": "==2.28.1" - }, - "reverse-geocoder": { - "hashes": [ - "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c" - ], - "index": "pypi", - "version": "==1.5.1" - }, - "scipy": { - "hashes": [ - "sha256:0419485dbcd0ed78c0d5bf234c5dd63e86065b39b4d669e45810d42199d49521", - "sha256:09412eb7fb60b8f00b328037fd814d25d261066ebc43a1e339cdce4f7502877e", - "sha256:26d28c468900e6d5fdb37d2812ab46db0ccd22c63baa095057871faa3a498bc9", - "sha256:34441dfbee5b002f9e15285014fd56e5e3372493c3e64ae297bae2c4b9659f5a", - "sha256:39ab9240cd215a9349c85ab908dda6d732f7d3b4b192fa05780812495536acc4", - "sha256:3bc1ab68b9a096f368ba06c3a5e1d1d50957a86665fc929c4332d21355e7e8f4", - "sha256:3c6f5d1d4b9a5e4fe5e14f26ffc9444fc59473bbf8d45dc4a9a15283b7063a72", - "sha256:47d1a95bd9d37302afcfe1b84c8011377c4f81e33649c5a5785db9ab827a6ade", - "sha256:71487c503e036740635f18324f62a11f283a632ace9d35933b2b0a04fd898c98", - "sha256:7a412c476a91b080e456229e413792bbb5d6202865dae963d1e6e28c2bb58691", - "sha256:825951b88f56765aeb6e5e38ac9d7d47407cfaaeb008d40aa1b45a2d7ea2731e", - "sha256:8cc81ac25659fec73599ccc52c989670e5ccd8974cf34bacd7b54a8d809aff1a", - "sha256:8d3faa40ac16c6357aaf7ea50394ea6f1e8e99d75e927a51102b1943b311b4d9", - "sha256:90c805f30c46cf60f1e76e947574f02954d25e3bb1e97aa8a07bc53aa31cf7d1", - "sha256:96d7cf7b25c9f23c59a766385f6370dab0659741699ecc7a451f9b94604938ce", - "sha256:b97b479f39c7e4aaf807efd0424dec74bbb379108f7d22cf09323086afcd312c", - "sha256:bc4e2c77d4cd015d739e75e74ebbafed59ba8497a7ed0fd400231ed7683497c4", - "sha256:c61b4a91a702e8e04aeb0bfc40460e1f17a640977c04dda8757efb0199c75332", - "sha256:d79da472015d0120ba9b357b28a99146cd6c17b9609403164b1a8ed149b4dfc8", - "sha256:e8fe305d9d67a81255e06203454729405706907dccbdfcc330b7b3482a6c371d", - "sha256:eb954f5aca4d26f468bbebcdc5448348eb287f7bea536c6306f62ea062f63d9a", - "sha256:f7c39f7dbb57cce00c108d06d731f3b0e2a4d3a95c66d96bce697684876ce4d4", - "sha256:f950a04b33e17b38ff561d5a0951caf3f5b47caa841edd772ffb7959f20a6af0" - ], - "index": "pypi", - "version": "==1.9.1" - }, - "setuptools": { - "hashes": [ - "sha256:a8f6e213b4b0661f590ccf40de95d28a177cd747d098624ad3f69c40287297e9", - "sha256:c2d2709550f15aab6c9110196ea312f468f41cd546bceb24127a1be6fdcaeeb1" - ], - "markers": "python_version >= '3.7'", - "version": "==65.4.0" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "index": "pypi", - "version": "==1.16.0" - }, - "snowballstemmer": { - "hashes": [ - "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1", - "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a" - ], - "version": "==2.2.0" - }, - "sortedcontainers": { - "hashes": [ - "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88", - "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0" - ], - "version": "==2.4.0" - }, - "sphinx": { - "hashes": [ - "sha256:3dcf00fcf82cf91118db9b7177edea4fc01998976f893928d0ab0c58c54be2ca", - "sha256:c009bb2e9ac5db487bcf53f015504005a330ff7c631bb6ab2604e0d65bae8b54" - ], - "index": "pypi", - "version": "==5.2.1" - }, - "sphinx-rtd-theme": { - "hashes": [ - "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8", - "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c" - ], - "index": "pypi", - "version": "==1.0.0" - }, - "sphinx-sitemap": { - "hashes": [ - "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737" - ], - "index": "pypi", - "version": "==2.2.0" - }, - "sphinxcontrib-applehelp": { - "hashes": [ - "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a", - "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-devhelp": { - "hashes": [ - "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e", - "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.2" - }, - "sphinxcontrib-htmlhelp": { - "hashes": [ - "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07", - "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2" - ], - "markers": "python_version >= '3.6'", - "version": "==2.0.0" - }, - "sphinxcontrib-jsmath": { - "hashes": [ - "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178", - "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.1" - }, - "sphinxcontrib-qthelp": { - "hashes": [ - "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72", - "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6" - ], - "markers": "python_version >= '3.5'", - "version": "==1.0.3" - }, - "sphinxcontrib-serializinghtml": { - "hashes": [ - "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd", - "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952" - ], - "markers": "python_version >= '3.5'", - "version": "==1.1.5" - }, - "subprocess32": { - "hashes": [ - "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b", - "sha256:e45d985aef903c5b7444d34350b05da91a9e0ea015415ab45a21212786c649d0", - "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d" - ], - "index": "pypi", - "version": "==3.5.4" - }, - "tabulate": { - "hashes": [ - "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc", - "sha256:436f1c768b424654fce8597290d2764def1eea6a77cfa5c33be00b1bc0f4f63d", - "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519" - ], - "index": "pypi", - "version": "==0.8.10" - }, - "tenacity": { - "hashes": [ - "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac", - "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445" - ], - "index": "pypi", - "version": "==8.1.0" - }, - "toml": { - "hashes": [ - "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b", - "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==0.10.2" - }, - "tomli": { - "hashes": [ - "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f" - ], - "markers": "python_version < '3.11'", - "version": "==2.0.1" - }, - "types-atomicwrites": { - "hashes": [ - "sha256:458a985b2cfaa963becec21ba63faaa5dd241e237ba8bf024732b37b18690de6", - "sha256:ef824f7e639c178e3e571f60d228c7745198756ebfcc5d249a0e3e02e04b2858" - ], - "index": "pypi", - "version": "==1.4.5" - }, - "types-certifi": { - "hashes": [ - "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f", - "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a" - ], - "index": "pypi", - "version": "==2021.10.8.3" - }, - "types-pycurl": { - "hashes": [ - "sha256:82e00aa2981595bfa55e5a3bac42221eb3435b0026dffbe1177f6ac9f2d51200", - "sha256:9eab3414da4a1b1e9a628bd288fc5172b8c182e1d9fb6d8d082441b0fd64baed" - ], - "index": "pypi", - "version": "==7.45.1" - }, - "types-pyyaml": { - "hashes": [ - "sha256:7f7da2fd11e9bc1e5e9eb3ea1be84f4849747017a59fc2eee0ea34ed1147c2e0", - "sha256:8f890028123607379c63550179ddaec4517dc751f4c527a52bb61934bf495989" - ], - "index": "pypi", - "version": "==6.0.11" - }, - "types-requests": { - "hashes": [ - "sha256:7ee827eb8ce611b02b5117cfec5da6455365b6a575f5e3ff19f655ba603e6b4e", - "sha256:af5f55e803cabcfb836dad752bd6d8a0fc8ef1cd84243061c0e27dee04ccf4fd" - ], - "index": "pypi", - "version": "==2.28.11" - }, - "types-urllib3": { - "hashes": [ - "sha256:a1b3aaea7dda3eb1b51699ee723aadd235488e4dc4648e030f09bc429ecff42f", - "sha256:cf7918503d02d3576e503bbfb419b0e047c4617653bba09624756ab7175e15c9" - ], - "version": "==1.26.24" - }, - "typing-extensions": { - "hashes": [ - "sha256:25642c956049920a5aa49edcdd6ab1e06d7e5d467fc00e0506c44ac86fbfca02", - "sha256:e6d2677a32f47fc7eb2795db1dd15c1f34eff616bcaf2cfb5e997f854fa1c4a6" - ], - "markers": "python_version < '3.10'", - "version": "==4.3.0" - }, - "urllib3": { - "hashes": [ - "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e", - "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997" - ], - "index": "pypi", - "version": "==1.26.12" - }, - "virtualenv": { - "hashes": [ - "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da", - "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27" - ], - "markers": "python_version >= '3.6'", - "version": "==20.16.5" - }, - "zipp": { - "hashes": [ - "sha256:05b45f1ee8f807d0cc928485ca40a07cb491cf092ff587c0df9cb1fd154848d2", - "sha256:47c40d7fe183a6f21403a199b3e4192cca5774656965b0a4988ad2f8feb5f009" - ], - "markers": "python_version >= '3.7'", - "version": "==3.8.1" - } - } -} diff --git a/README.md b/README.md index cfeb625bfe..a71a9e671e 100755 --- a/README.md +++ b/README.md @@ -103,13 +103,15 @@ Directory Structure ├── opendbc # Files showing how to interpret data from cars ├── panda # Code used to communicate on CAN ├── third_party # External libraries - ├── pyextra # Extra python packages └── system # Generic services ├── camerad # Driver to capture images from the camera sensors ├── clocksd # Broadcasts current time ├── hardware # Hardware abstraction classes ├── logcatd # systemd journal as a service - └── proclogd # Logs information from /proc + ├── loggerd # Logger and uploader of car data + ├── proclogd # Logs information from /proc + ├── sensord # IMU interface code + └── ubloxd # u-blox GNSS module interface code └── selfdrive # Code needed to drive the car ├── assets # Fonts, images, and sounds for UI ├── athena # Allows communication with the app @@ -118,12 +120,10 @@ Directory Structure ├── controls # Planning and controls ├── debug # Tools to help you debug and do car ports ├── locationd # Precise localization and vehicle parameter estimation - ├── loggerd # Logger and uploader of car data ├── manager # Daemon that starts/stops all other daemons as needed ├── modeld # Driving and monitoring model runners ├── monitoring # Daemon to determine driver attention ├── navd # Turn-by-turn navigation - ├── sensord # IMU interface code ├── test # Unit tests, system tests, and a car simulator └── ui # The UI @@ -143,7 +143,4 @@ NO WARRANTY EXPRESSED OR IMPLIED.** [![openpilot tests](https://github.com/commaai/openpilot/workflows/openpilot%20tests/badge.svg?event=push)](https://github.com/commaai/openpilot/actions) -[![Total alerts](https://img.shields.io/lgtm/alerts/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/alerts/) -[![Language grade: Python](https://img.shields.io/lgtm/grade/python/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:python) -[![Language grade: C/C++](https://img.shields.io/lgtm/grade/cpp/g/commaai/openpilot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/commaai/openpilot/context:cpp) [![codecov](https://codecov.io/gh/commaai/openpilot/branch/master/graph/badge.svg)](https://codecov.io/gh/commaai/openpilot) diff --git a/RELEASES.md b/RELEASES.md index 35ed1b9520..55b0dbad7e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,17 +1,62 @@ -Version 0.8.17 (2022-XX-XX) +Version 0.9.2 (2023-03-XX) +======================== +* Draw MPC path instead of model predicted path, this is a more accurate representation of what the car will do. +* Buick LaCrosse 2017-19 support thanks to koch-cf! +* Škoda Fabia 2022-23 support thanks to jyoung8607! + +Version 0.9.1 (2023-02-28) ======================== * New driving model - * Internal feature space accuracy increased tenfold during training, this makes the model dramatically more accurate. -* Self-tuning torque lateral controller parameters - * Parameters learned live for each car - * Enabled only on Toyota Corolla for now + * 30% improved height estimation resulting in better driving performance for tall cars +* Driver monitoring: removed timer resetting on user interaction if distracted +* UI updates + * Adjust alert volume using ambient noise level + * Driver monitoring icon shows driver's head pose + * German translation thanks to Vrabetz and CzokNorris! +* Cadillac Escalade 2017 support thanks to rickygilleland! +* Chevrolet Bolt EV 2022-23 support thanks to JasonJShuler! +* Genesis GV60 2023 support thanks to sunnyhaibin! +* Hyundai Tucson 2022-23 support +* Kia K5 Hybrid 2020 support thanks to sunnyhaibin! +* Kia Niro Hybrid 2023 support thanks to sunnyhaibin! +* Kia Sorento 2022-23 support thanks to sunnyhaibin! +* Kia Sorento Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Toyota C-HR 2021 support thanks to eFiniLan! +* Toyota C-HR Hybrid 2022 support thanks to Korben00! +* Volkswagen Crafter and MAN TGE 2017-23 support thanks to jyoung8607! + +Version 0.9.0 (2022-11-21) +======================== +* New driving model + * Internal feature space information content increased tenfold during training to ~700 bits, which makes the model dramatically more accurate + * Less reliance on previous frames makes model more reactive and snappy + * Trained in new reprojective simulator + * Trained in 36 hours from scratch, compared to one week for previous releases + * Training now simulates both lateral and longitudinal behavior, which allows openpilot to slow down for turns, stop at traffic lights, and more in experimental mode +* Experimental driving mode + * End-to-end longitudinal control + * Stops for traffic lights and stop signs + * Slows down for turns + * openpilot defaults to chill mode, enable experimental mode in settings +* Driver monitoring updates + * New bigger model with added end-to-end distracted trigger + * Reduced false positives during driver calibration +* Self-tuning torque controller: learns parameters live for each car +* Torque controller used on all Toyota, Lexus, Hyundai, Kia, and Genesis models * UI updates - * Multi-language in navigation * Matched speeds shown on car's dash + * Multi-language in navigation * Improved update experience * Border turns grey while overriding steering - * Added button to bookmark events while driving; view them later in comma connect -* AGNOS 6 + * Bookmark events while driving; view them in comma connect + * New onroad visualization for experimental mode +* tools: new and improved cabana thanks to deanlee! +* Experimental longitudinal support for Volkswagen, CAN-FD Hyundai, and new GM models +* Genesis GV70 2022-23 support thanks to zunichky and sunnyhaibin! +* Hyundai Santa Cruz 2021-22 support thanks to sunnyhaibin! +* Kia Sportage 2023 support thanks to sunnyhaibin! +* Kia Sportage Hybrid 2023 support thanks to sunnyhaibin! +* Kia Stinger 2022 support thanks to sunnyhaibin! Version 0.8.16 (2022-08-26) ======================== diff --git a/SConstruct b/SConstruct index 74680c0598..387d2e76f4 100644 --- a/SConstruct +++ b/SConstruct @@ -71,10 +71,10 @@ if arch == "aarch64" and AGNOS: lenv = { "PATH": os.environ['PATH'], "LD_LIBRARY_PATH": [Dir(f"#third_party/acados/{arch}/lib").abspath], - "PYTHONPATH": Dir("#").abspath + ":" + Dir("#pyextra/").abspath, + "PYTHONPATH": Dir("#").abspath, "ACADOS_SOURCE_DIR": Dir("#third_party/acados/include/acados").abspath, - "ACADOS_PYTHON_INTERFACE_PATH": Dir("#pyextra/acados_template").abspath, + "ACADOS_PYTHON_INTERFACE_PATH": Dir("#third_party/acados/acados_template").abspath, "TERA_PATH": Dir("#").abspath + f"/third_party/acados/{arch}/t_renderer" } @@ -197,10 +197,6 @@ env = Environment( "#third_party/libyuv/include", "#third_party/json11", "#third_party/curl/include", - "#third_party/libgralloc/include", - "#third_party/android_frameworks_native/include", - "#third_party/android_hardware_libhardware/include", - "#third_party/android_system_core/include", "#third_party/linux/include", "#third_party/snpe/include", "#third_party/mapbox-gl-native-qt/include", @@ -282,7 +278,7 @@ Export('envCython') # Qt build environment qt_env = env.Clone() -qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus"] +qt_modules = ["Widgets", "Gui", "Core", "Network", "Concurrent", "Multimedia", "Quick", "Qml", "QuickWidgets", "Location", "Positioning", "DBus", "Xml"] qt_libs = [] if arch == "Darwin": @@ -298,16 +294,20 @@ if arch == "Darwin": qt_env["FRAMEWORKS"] += [f"Qt{m}" for m in qt_modules] + ["OpenGL"] qt_env.AppendENVPath('PATH', os.path.join(qt_env['QTDIR'], "bin")) else: - qt_env['QTDIR'] = "/usr" + qt_install_prefix = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_PREFIX'], encoding='utf8').strip() + qt_install_headers = subprocess.check_output(['qmake', '-query', 'QT_INSTALL_HEADERS'], encoding='utf8').strip() + + qt_env['QTDIR'] = qt_install_prefix qt_dirs = [ - f"/usr/include/{real_arch}-linux-gnu/qt5", - f"/usr/include/{real_arch}-linux-gnu/qt5/QtGui/5.12.8/QtGui", + f"{qt_install_headers}", + f"{qt_install_headers}/QtGui/5.12.8/QtGui", ] - qt_dirs += [f"/usr/include/{real_arch}-linux-gnu/qt5/Qt{m}" for m in qt_modules] + qt_dirs += [f"{qt_install_headers}/Qt{m}" for m in qt_modules] qt_libs = [f"Qt5{m}" for m in qt_modules] if arch == "larch64": qt_libs += ["GLESv2", "wayland-client"] + qt_env.PrependENVPath('PATH', Dir("#third_party/qt5/larch64/bin/").abspath) elif arch != "Darwin": qt_libs += ["GL"] @@ -384,10 +384,10 @@ rednose_config = { if arch != "larch64": rednose_config['to_build'].update({ 'loc_4': ('#selfdrive/locationd/models/loc_kf.py', True, [], rednose_deps), + 'lane': ('#selfdrive/locationd/models/lane_kf.py', True, [], rednose_deps), 'pos_computer_4': ('#rednose/helpers/lst_sq_computer.py', False, [], []), 'pos_computer_5': ('#rednose/helpers/lst_sq_computer.py', False, [], []), 'feature_handler_5': ('#rednose/helpers/feature_handler.py', False, [], []), - 'lane': ('#xx/pipeline/lib/ekf/lane_kf.py', True, [], rednose_deps), }) Export('rednose_config') @@ -398,6 +398,7 @@ SConscript([ 'system/camerad/SConscript', 'system/clocksd/SConscript', 'system/proclogd/SConscript', + 'system/ubloxd/SConscript', ]) if arch != "Darwin": SConscript(['system/logcatd/SConscript']) @@ -406,10 +407,10 @@ if arch != "Darwin": # build submodules SConscript([ - 'cereal/SConscript', 'body/board/SConscript', - 'panda/board/SConscript', + 'cereal/SConscript', 'opendbc/can/SConscript', + 'panda/SConscript', ]) SConscript(['third_party/SConscript']) @@ -419,16 +420,15 @@ SConscript(['common/transformations/SConscript']) SConscript(['selfdrive/modeld/SConscript']) -SConscript(['selfdrive/controls/lib/cluster/SConscript']) SConscript(['selfdrive/controls/lib/lateral_mpc_lib/SConscript']) SConscript(['selfdrive/controls/lib/longitudinal_mpc_lib/SConscript']) SConscript(['selfdrive/boardd/SConscript']) -SConscript(['selfdrive/loggerd/SConscript']) +SConscript(['system/loggerd/SConscript']) SConscript(['selfdrive/locationd/SConscript']) -SConscript(['selfdrive/sensord/SConscript']) +SConscript(['system/sensord/SConscript']) SConscript(['selfdrive/ui/SConscript']) SConscript(['selfdrive/navd/SConscript']) @@ -439,9 +439,6 @@ if arch in ['x86_64', 'Darwin'] or GetOption('extras'): Export('opendbc') SConscript(['tools/cabana/SConscript']) -if GetOption('test'): - SConscript('panda/tests/safety/SConscript') - external_sconscript = GetOption('external_sconscript') if external_sconscript: SConscript([external_sconscript]) diff --git a/body b/body index 04aeb30ce0..e1805f65ee 160000 --- a/body +++ b/body @@ -1 +1 @@ -Subproject commit 04aeb30ce0bb14759989cd374158233877e1e151 +Subproject commit e1805f65ee75fab4454c21eda8b42b49d4bdc48f diff --git a/cereal b/cereal index 1e3dd70a39..c516d5e985 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 1e3dd70a391bc1bbe437d3eea8be30947f929a75 +Subproject commit c516d5e9856fdb1f5ca856e430ac69aebb2ff017 diff --git a/common/SConscript b/common/SConscript index 8aee6f42a7..5d6170611f 100644 --- a/common/SConscript +++ b/common/SConscript @@ -10,11 +10,13 @@ common_libs = [ 'statlog.cc', 'swaglog.cc', 'util.cc', - 'gpio.cc', 'i2c.cc', 'watchdog.cc', ] +if arch != "Darwin": + common_libs.append('gpio.cc') + _common = fxn('common', common_libs, LIBS="json11") files = [ diff --git a/common/clutil.cc b/common/clutil.cc index 9d3447d807..3cfc8a8c8c 100644 --- a/common/clutil.cc +++ b/common/clutil.cc @@ -5,6 +5,7 @@ #include #include "common/util.h" +#include "common/swaglog.h" namespace { // helper functions @@ -31,14 +32,14 @@ void cl_print_info(cl_platform_id platform, cl_device_id device) { case CL_DEVICE_TYPE_ACCELERATOR: type_str = "CL_DEVICE_TYPE_ACCELERATOR"; break; } - std::cout << "vendor: " << get_platform_info(platform, CL_PLATFORM_VENDOR) << std::endl - << "platform version: " << get_platform_info(platform, CL_PLATFORM_VERSION) << std::endl - << "profile: " << get_platform_info(platform, CL_PLATFORM_PROFILE) << std::endl - << "extensions: " << get_platform_info(platform, CL_PLATFORM_EXTENSIONS) << std::endl - << "name :" << get_device_info(device, CL_DEVICE_NAME) << std::endl - << "device version :" << get_device_info(device, CL_DEVICE_VERSION) << std::endl - << "max work group size :" << work_group_size << std::endl - << "type = " << device_type << " = " << type_str << std::endl; + LOGD("vendor: %s", get_platform_info(platform, CL_PLATFORM_VENDOR).c_str()); + LOGD("platform version: %s", get_platform_info(platform, CL_PLATFORM_VERSION).c_str()); + LOGD("profile: %s", get_platform_info(platform, CL_PLATFORM_PROFILE).c_str()); + LOGD("extensions: %s", get_platform_info(platform, CL_PLATFORM_EXTENSIONS).c_str()); + LOGD("name: %s", get_device_info(device, CL_DEVICE_NAME).c_str()); + LOGD("device version: %s", get_device_info(device, CL_DEVICE_VERSION).c_str()); + LOGD("max work group size: %d", work_group_size); + LOGD("type = %d = ", device_type, type_str); } void cl_print_build_errors(cl_program program, cl_device_id device) { @@ -49,7 +50,7 @@ void cl_print_build_errors(cl_program program, cl_device_id device) { std::string log(log_size, '\0'); clGetProgramBuildInfo(program, device, CL_PROGRAM_BUILD_LOG, log_size, &log[0], NULL); - std::cout << "build failed; status=" << status << ", log:" << std::endl << log << std::endl; + LOGE("build failed; status=%d, log: %s", status, log.c_str()); } } // namespace @@ -61,14 +62,15 @@ cl_device_id cl_get_device_id(cl_device_type device_type) { CL_CHECK(clGetPlatformIDs(num_platforms, &platform_ids[0], NULL)); for (size_t i = 0; i < num_platforms; ++i) { - std::cout << "platform[" << i << "] CL_PLATFORM_NAME: " << get_platform_info(platform_ids[i], CL_PLATFORM_NAME) << std::endl; + LOGD("platform[%d] CL_PLATFORM_NAME: %s", i, get_platform_info(platform_ids[i], CL_PLATFORM_NAME).c_str()); + // Get first device if (cl_device_id device_id = NULL; clGetDeviceIDs(platform_ids[i], device_type, 1, &device_id, NULL) == 0 && device_id) { cl_print_info(platform_ids[i], device_id); return device_id; } } - std::cout << "No valid openCL platform found" << std::endl; + LOGE("No valid openCL platform found"); assert(0); return nullptr; } diff --git a/common/gpio.cc b/common/gpio.cc index 9f5c211a4b..aa65c0bd9d 100644 --- a/common/gpio.cc +++ b/common/gpio.cc @@ -1,5 +1,20 @@ #include "common/gpio.h" +#ifdef __APPLE__ +int gpio_init(int pin_nr, bool output) { + return 0; +} + +int gpio_set(int pin_nr, bool high) { + return 0; +} + +int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int pin_nr) { + return 0; +} + +#else + #include #include @@ -63,3 +78,5 @@ int gpiochip_get_ro_value_fd(const char* consumer_label, int gpiochiop_id, int p close(fd); return rq.fd; } + +#endif diff --git a/common/params.cc b/common/params.cc index 155bc88487..428830a112 100644 --- a/common/params.cc +++ b/common/params.cc @@ -98,18 +98,20 @@ std::unordered_map keys = { {"CarVin", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"CompletedTrainingVersion", PERSISTENT}, {"ControlsReady", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"CurrentBootlog", PERSISTENT}, {"CurrentRoute", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, - {"DashcamOverride", PERSISTENT}, {"DisableLogging", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"DisablePowerDown", PERSISTENT}, - {"EndToEndLong", PERSISTENT}, - {"ExperimentalLongitudinalEnabled", PERSISTENT}, // WARNING: THIS MAY DISABLE AEB + {"ExperimentalMode", PERSISTENT}, + {"ExperimentalModeConfirmed", PERSISTENT}, + {"ExperimentalLongitudinalEnabled", PERSISTENT}, {"DisableUpdates", PERSISTENT}, {"DisengageOnAccelerator", PERSISTENT}, {"DongleId", PERSISTENT}, {"DoReboot", CLEAR_ON_MANAGER_START}, {"DoShutdown", CLEAR_ON_MANAGER_START}, {"DoUninstall", CLEAR_ON_MANAGER_START}, + {"FirmwareQueryDone", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"ForcePowerDown", CLEAR_ON_MANAGER_START}, {"GitBranch", PERSISTENT}, {"GitCommit", PERSISTENT}, @@ -133,9 +135,10 @@ std::unordered_map keys = { {"IsRhdDetected", PERSISTENT}, {"IsTakingSnapshot", CLEAR_ON_MANAGER_START}, {"IsTestedBranch", CLEAR_ON_MANAGER_START}, + {"IsReleaseBranch", CLEAR_ON_MANAGER_START}, {"IsUpdateAvailable", CLEAR_ON_MANAGER_START}, {"JoystickDebugMode", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, - {"LaikadEphemeris", PERSISTENT | DONT_LOG}, + {"LaikadEphemerisV3", PERSISTENT | DONT_LOG}, {"LanguageSetting", PERSISTENT}, {"LastAthenaPingTime", CLEAR_ON_MANAGER_START}, {"LastGPSPosition", PERSISTENT}, @@ -148,9 +151,12 @@ std::unordered_map keys = { {"LiveTorqueCarParams", PERSISTENT}, {"LiveTorqueParameters", PERSISTENT | DONT_LOG}, {"NavDestination", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, + {"NavDestinationWaypoints", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"NavSettingTime24h", PERSISTENT}, {"NavSettingLeftSide", PERSISTENT}, {"NavdRender", PERSISTENT}, + {"ObdMultiplexingChanged", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, + {"ObdMultiplexingEnabled", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"OpenpilotEnabledToggle", PERSISTENT}, {"PandaHeartbeatLost", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_OFF}, {"PandaSignatures", CLEAR_ON_MANAGER_START}, @@ -167,7 +173,7 @@ std::unordered_map keys = { {"Timezone", PERSISTENT}, {"TrainingVersion", PERSISTENT}, {"UbloxAvailable", PERSISTENT}, - {"UpdateAvailable", CLEAR_ON_MANAGER_START}, + {"UpdateAvailable", CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON}, {"UpdateFailedCount", CLEAR_ON_MANAGER_START}, {"UpdaterState", CLEAR_ON_MANAGER_START}, {"UpdaterFetchAvailable", CLEAR_ON_MANAGER_START}, diff --git a/common/prefix.h b/common/prefix.h new file mode 100644 index 0000000000..f5abe14b2b --- /dev/null +++ b/common/prefix.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "common/params.h" +#include "common/util.h" + +class OpenpilotPrefix { +public: + OpenpilotPrefix(std::string prefix = {}) { + if (prefix.empty()) { + prefix = util::random_string(15); + } + msgq_path = "/dev/shm/" + prefix; + bool ret = util::create_directories(msgq_path, 0777); + assert(ret); + setenv("OPENPILOT_PREFIX", prefix.c_str(), 1); + } + + ~OpenpilotPrefix() { + auto param_path = Params().getParamPath(); + if (util::file_exists(param_path)) { + std::string real_path = util::readlink(param_path); + system(util::string_format("rm %s -rf", real_path.c_str()).c_str()); + unlink(param_path.c_str()); + } + system(util::string_format("rm %s -rf", msgq_path.c_str()).c_str()); + unsetenv("OPENPILOT_PREFIX"); + } + +private: + std::string msgq_path; +}; diff --git a/common/util.cc b/common/util.cc index b6a8322a27..10dff6a9ea 100644 --- a/common/util.cc +++ b/common/util.cc @@ -1,5 +1,6 @@ #include "common/util.h" +#include #include #include @@ -9,6 +10,7 @@ #include #include #include +#include #include #ifdef __linux__ @@ -149,6 +151,14 @@ int safe_fflush(FILE *stream) { return ret; } +int safe_ioctl(int fd, unsigned long request, void *argp) { + int ret; + do { + ret = ioctl(fd, request, argp); + } while ((ret == -1) && (errno == EINTR)); + return ret; +} + std::string readlink(const std::string &path) { char buff[4096]; ssize_t len = ::readlink(path.c_str(), buff, sizeof(buff)-1); @@ -219,6 +229,18 @@ std::string hexdump(const uint8_t* in, const size_t size) { return ss.str(); } +std::string random_string(std::string::size_type length) { + const char* chrs = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + std::mt19937 rg{std::random_device{}()}; + std::uniform_int_distribution pick(0, sizeof(chrs) - 2); + std::string s; + s.reserve(length); + while (length--) { + s += chrs[pick(rg)]; + } + return s; +} + std::string dir_name(std::string const &path) { size_t pos = path.find_last_of("/"); if (pos == std::string::npos) return ""; diff --git a/common/util.h b/common/util.h index e13f4dc130..028074384e 100644 --- a/common/util.h +++ b/common/util.h @@ -75,6 +75,7 @@ int getenv(const char* key, int default_val); float getenv(const char* key, float default_val); std::string hexdump(const uint8_t* in, const size_t size); +std::string random_string(std::string::size_type length); std::string dir_name(std::string const& path); // **** file fhelpers ***** @@ -86,6 +87,7 @@ int write_file(const char* path, const void* data, size_t size, int flags = O_WR FILE* safe_fopen(const char* filename, const char* mode); size_t safe_fwrite(const void * ptr, size_t size, size_t count, FILE * stream); int safe_fflush(FILE *stream); +int safe_ioctl(int fd, unsigned long request, void *argp); std::string readlink(const std::string& path); bool file_exists(const std::string& fn); diff --git a/common/version.h b/common/version.h index 0a109c1faa..5637029cf4 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.8.17" +#define COMMA_VERSION "0.9.2" diff --git a/docs/CARS.md b/docs/CARS.md index f0b653667a..644e7386ee 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,228 +4,261 @@ A supported vehicle is one that just works when you install a comma three. All supported cars provide a better experience than any stock system. -# 208 Supported Cars +# 239 Supported Cars -|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness| -|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:| -|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Acura|RDX 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|Q3 2020-21|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Cadillac|Escalade ESV 2016[1](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Silverado 1500 2020-21|Safety Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Chevrolet|Volt 2017-18[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None| -|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Genesis|G70 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Genesis|G80 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Genesis|G90 2017-18|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|GMC|Acadia 2018[1](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II| -|GMC|Sierra 1500 2020-21|Driver Alert Package II|Stock|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM| -|Honda|Accord 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Accord Hybrid 2018-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Civic 2019-21|All|openpilot|0 mph|2 mph[2](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| -|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B| -|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|CR-V 2017-22|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|e 2020|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Insight 2019-22|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Inspire 2018|All|openpilot|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A| -|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec| -|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Hyundai|Elantra 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J| -|Hyundai|Ioniq 5 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q| -|Hyundai|Ioniq 5 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K| -|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O| -|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I| -|Hyundai|Palisade 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Hyundai|Santa Fe 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D| -|Hyundai|Santa Fe 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Hyundai|Sonata 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Sonata Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Hyundai|Tucson Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N| -|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA| -|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|EV6 (with HDA II) 2022|Highway Driving Assist II|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P| -|Kia|EV6 (without HDA II) 2022|Highway Driving Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L| -|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Kia|Niro EV 2019|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro EV 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro EV 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Niro EV 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F| -|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B| -|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G| -|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A| -|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E| -|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C| -|Kia|Telluride 2020|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H| -|Lexus|CT Hybrid 2017-18|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|ES Hybrid 2017-18|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|ES Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX 2018-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|NX Hybrid 2018-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RC 2017-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|RX Hybrid 2016|Lexus Safety System+|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX Hybrid 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Mazda|CX-5 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| -|Mazda|CX-9 2021-22|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda| -|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B| -|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A| -|Ram|1500 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram| -|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Crosstrek 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| -|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B| -|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A| -|Škoda|Kamiq 2021[5](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Karoq 2019-21[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Kodiaq 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Škoda|Superb 2015-18|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon 2016|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2017-18|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2019-21|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Avalon Hybrid 2019-21|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|C-HR 2017-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|C-HR Hybrid 2017-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2018-20|All|Stock|0 mph[4](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry 2021-22|All|openpilot|0 mph[4](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Camry Hybrid 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla 2017-19|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Cross (Non-US only) 2020-21|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Highlander 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Highlander Hybrid 2017-19|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Highlander Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius 2016|Toyota Safety Sense P|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2017-20|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius Prime 2017-20|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|Prius v 2017|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2016|Toyota Safety Sense P|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2017-18|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2017-18|All|Stock[3](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota| -|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Toyota|Sienna 2018-20|All|Stock[3](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota| -|Volkswagen|Arteon 2018-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon eHybrid 2020-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Arteon R 2020-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas 2018-23[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Atlas Cross Sport 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|California 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Caravelle 2020[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|CC 2018-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf 2015-20[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf R 2015-19[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| -|Volkswagen|Jetta 2018-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Jetta GLI 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat 2015-22[6,7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat Alltrack 2015-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Passat GTE 2015-22[7,8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Polo 2020-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Polo GTI 2020-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|T-Cross 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|T-Roc 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Taos 2022[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont 2018-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont Cross Sport 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Teramont X 2021-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Tiguan 2019-22[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533| -|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|VW| +|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Harness|Video| +|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| +|Acura|ILX 2016-19|AcuraWatch Plus|openpilot|25 mph|25 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Acura|RDX 2016-18|AcuraWatch Plus|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Acura|RDX 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Audi|A3 2014-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|A3 Sportback e-tron 2017-18|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|Q2 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|Q3 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|RS3 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Audi|S3 2015-17|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Buick|LaCrosse 2017-19[3](#footnotes)|Driver Confidence Package 2|openpilot|18 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Cadillac|Escalade 2017[3](#footnotes)|Driver Assist Package|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Cadillac|Escalade ESV 2016[3](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Chevrolet|Bolt EUV 2022-23|Premier or Premier Redline Trim without Super Cruise Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Bolt EV 2022-23|2LT Trim with Adaptive Cruise Control Package|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Chevrolet|Volt 2017-18[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|Chrysler|Pacifica 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica 2019-20|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica 2021|All|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica Hybrid 2017-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Chrysler|Pacifica Hybrid 2019-22|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| +|Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|| +|Genesis|G80 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Genesis|G90 2017-18|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Genesis|GV60 (Advanced Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Genesis|GV60 (Performance Trim) 2023[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Genesis|GV70 2022-23[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|GMC|Acadia 2018[3](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|OBD-II|| +|GMC|Sierra 1500 2020-21|Driver Alert Package II|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|GM|| +|Honda|Accord 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Accord Hybrid 2018-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic 2016-18|Honda Sensing|openpilot|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Civic 2019-21|All|openpilot available[1](#footnotes)|0 mph|2 mph[4](#footnotes)|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|Civic Hatchback 2017-21|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Civic Hatchback 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch B|| +|Honda|CR-V 2015-16|Touring Trim|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|CR-V 2017-22|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|CR-V Hybrid 2017-19|Honda Sensing|openpilot available[1](#footnotes)|0 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|e 2020|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Fit 2018-20|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Freed 2020|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|HR-V 2019-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Insight 2019-22|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Inspire 2018|All|openpilot available[1](#footnotes)|0 mph|3 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Bosch A|| +|Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Passport 2019-21|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Honda|Ridgeline 2017-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Honda Nidec|| +|Hyundai|Elantra 2017-19|Smart Cruise Control (SCC)|Stock|19 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Hyundai|Elantra 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Elantra GT 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Elantra Hybrid 2021-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Genesis 2015-16|Smart Cruise Control (SCC)|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai J|| +|Hyundai|i30 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Ioniq 5 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|| +|Hyundai|Ioniq 5 (with HDA II) 2022-23[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai Q|| +|Hyundai|Ioniq 5 (without HDA II) 2022-23[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Hyundai|Ioniq Electric 2019|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Electric 2020|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Ioniq Hybrid 2017-19|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Hybrid 2020-22|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Ioniq Plug-in Hybrid 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Hyundai|Ioniq Plug-in Hybrid 2020-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Hyundai|Kona Electric 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai O|| +|Hyundai|Kona Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai I|| +|Hyundai|Palisade 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Hyundai|Santa Cruz 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Santa Fe 2019-20|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|| +|Hyundai|Santa Fe 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Santa Fe Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Santa Fe Plug-in Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Hyundai|Sonata Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Tucson 2022[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Tucson 2023[5](#footnotes)|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Hyundai|Tucson Hybrid 2022[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|FCA|| +|Kia|Ceed 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Kia|EV6 (Southeast Asia only) 2022-23[5](#footnotes)|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|| +|Kia|EV6 (with HDA II) 2022[5](#footnotes)|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai P|| +|Kia|EV6 (without HDA II) 2022[5](#footnotes)|Highway Driving Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai L|| +|Kia|Forte 2019-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Kia|K5 2021-22|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|K5 Hybrid 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Niro EV 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro EV 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Kia|Niro EV 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Niro EV 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai F|| +|Kia|Niro Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Kia|Niro Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Niro Plug-in Hybrid 2018-19|All|openpilot available[1](#footnotes)|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Niro Plug-in Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai D|| +|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai B|| +|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai G|| +|Kia|Seltos 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Sorento 2018|Advanced Smart Cruise Control|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Sorento 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai E|| +|Kia|Sorento 2021-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Kia|Sorento Plug-in Hybrid 2022-23[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai A|| +|Kia|Sportage 2023[5](#footnotes)|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Kia|Sportage Hybrid 2023[5](#footnotes)|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai N|| +|Kia|Stinger 2018-20|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai C|| +|Kia|Stinger 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai K|| +|Kia|Telluride 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Hyundai H|| +|Lexus|CT Hybrid 2017-18|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|ES 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|ES Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|ES Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|NX Hybrid 2018-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|NX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|RC 2018-20|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2016|Lexus Safety System+|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Lexus|RX Hybrid 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Lexus|UX Hybrid 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Mazda|CX-5 2022-23|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|| +|Mazda|CX-9 2021-23|All|Stock|0 mph|28 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Mazda|| +|Nissan|Altima 2019-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan B|| +|Nissan|Leaf 2018-22|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Nissan|Rogue 2018-20|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Nissan|X-Trail 2017|ProPILOT Assist|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Nissan A|| +|Ram|1500 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Ram|| +|SEAT|Ateca 2018|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|SEAT|Leon 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Subaru|Ascent 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Crosstrek 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Crosstrek 2020-23|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Forester 2019-21|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Impreza 2017-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Impreza 2020-22|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|Legacy 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|| +|Subaru|Outback 2020-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru B|| +|Subaru|XV 2018-19|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Subaru|XV 2020-21|EyeSight Driver Assistance|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Subaru A|| +|Škoda|Fabia 2022-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Škoda|Kamiq 2021[7](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Škoda|Karoq 2019-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Kodiaq 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Octavia 2015, 2018-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Octavia RS 2016|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Škoda|Scala 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Škoda|Superb 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Toyota|Alphard 2019-20|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Alphard Hybrid 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Avalon 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Avalon Hybrid 2019-21|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry 2018-20|All|Stock|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry 2021-23|All|openpilot|0 mph[6](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Camry Hybrid 2021-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla 2017-19|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Corolla 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Cross (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Cross Hybrid (Non-US only) 2020-22|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hatchback 2019-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Corolla Hybrid (Non-US only) 2020-23|All|openpilot|17 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander Hybrid 2017-19|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Highlander Hybrid 2020-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Mirai 2021|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius Prime 2017-20|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Prius Prime 2021-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|Prius v 2017|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|Toyota|| +|Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|Toyota|| +|Volkswagen|Arteon 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Arteon eHybrid 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Arteon R 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Atlas 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Atlas Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|California 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Caravelle 2020|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|CC 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Crafter 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|e-Crafter 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|e-Golf 2014-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf Alltrack 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTD 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTE 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf GTI 2015-21|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat 2015-22[8](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Polo 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|Polo GTI 2020-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|T-Cross 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|T-Roc 2021|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533[10](#footnotes)|| +|Volkswagen|Taos 2022|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont Cross Sport 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Teramont X 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Tiguan 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| +|Volkswagen|Touran 2017|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,9](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|J533|| -1Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
-22019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
-3When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
-4openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
-5Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
-6Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
-7Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black (older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway inside the dashboard, choose "VW J533 Development" from the vehicle drop-down for a suitable harness. (Some newer models are also observed to not have a J533 connector.)
-8Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)
+1Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`.
+2By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).
+3Requires a community built ASCM harness. NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).
+42019 Honda Civic 1.6L Diesel Sedan does not have ALC below 12mph.
+5Requires a red panda for this CAN FD car. All the hardware needed is sold in the CAN FD kit.
+6openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.
+7Not including the China market Kamiq, which is based on the (currently) unsupported PQ34 platform.
+8Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.
+9Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness are limited to using stock ACC.
+10Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot in software, but doesn't yet have a harness available from the comma store.
## Community Maintained Cars Although they're not upstream, the community has openpilot running on other makes and models. See the 'Community Supported Models' section of each make [on our wiki](https://wiki.comma.ai/). diff --git a/docs/INTEGRATION.md b/docs/INTEGRATION.md index 97b72e39d3..ba6291c1e3 100644 --- a/docs/INTEGRATION.md +++ b/docs/INTEGRATION.md @@ -8,4 +8,4 @@ Additionally, on specific supported cars (see ACC column in [supported cars](CAR * Stock ACC is replaced by openpilot ACC. * openpilot FCW operates in addition to stock FCW. -openpilot should preserve all other vehicle's stock features, including, but are not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. +openpilot should preserve all other vehicle's stock features, including, but not limited to: FCW, Automatic Emergency Braking (AEB), auto high-beam, blind spot warning, and side collision warning. diff --git a/docs/Makefile b/docs/Makefile index d0aa841c4d..dee660f770 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -41,7 +41,7 @@ clean: @echo "Building rst files..." sphinx-apidoc -o "$(DOCSBUILDDIR)" ../ \ - ../xx ../laika_repo ../rednose_repo ../pyextra ../notebooks ../panda_jungle \ + ../xx ../laika_repo ../rednose_repo ../notebooks ../panda_jungle \ ../third_party \ ../panda/examples \ ../scripts \ diff --git a/docs/assets/icon-youtube.svg b/docs/assets/icon-youtube.svg new file mode 100644 index 0000000000..4e2c9fdfa9 --- /dev/null +++ b/docs/assets/icon-youtube.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/c_docs.rst b/docs/c_docs.rst index 77be7e51d8..5619cc8a51 100644 --- a/docs/c_docs.rst +++ b/docs/c_docs.rst @@ -83,7 +83,7 @@ common sensorsd ^^^^^^^^ .. autodoxygenindex:: - :project: selfdrive_sensord_sensors + :project: system_sensord_sensors boardd ^^^^^^ diff --git a/docs/docker/Dockerfile b/docs/docker/Dockerfile index cefe9be855..a1cdbbeaf0 100644 --- a/docs/docker/Dockerfile +++ b/docs/docker/Dockerfile @@ -4,19 +4,13 @@ ENV PYTHONUNBUFFERED 1 ENV OPENPILOT_PATH /home/batman/openpilot/ ENV PYTHONPATH ${OPENPILOT_PATH}:${PYTHONPATH} +ENV POETRY_VIRUALENVS_CREATE false RUN mkdir -p ${OPENPILOT_PATH} WORKDIR ${OPENPILOT_PATH} -COPY Pipfile Pipfile.lock $OPENPILOT_PATH -RUN pip install --no-cache-dir pipenv==2021.5.29 pip==21.3.1 && \ - pipenv install --system --deploy --dev --clear && \ - pip uninstall -y pipenv - - COPY SConstruct ${OPENPILOT_PATH} -COPY ./pyextra ${OPENPILOT_PATH}/pyextra COPY ./third_party ${OPENPILOT_PATH}/third_party COPY ./site_scons ${OPENPILOT_PATH}/site_scons COPY ./laika ${OPENPILOT_PATH}/laika diff --git a/docs/overview.rst b/docs/overview.rst index cda51ba3d4..b9a5700528 100644 --- a/docs/overview.rst +++ b/docs/overview.rst @@ -5,7 +5,7 @@ openpilot :maxdepth: 4 Debugging - selfdrive/loggerd/README.md + system/loggerd/README.md Driver Monitoring Process Replay @@ -77,3 +77,4 @@ tools Simulator tools/ssh/README.md Webcam + tools/cabana/README.md diff --git a/laika_repo b/laika_repo index e1049cde0a..6fadabd860 160000 --- a/laika_repo +++ b/laika_repo @@ -1 +1 @@ -Subproject commit e1049cde0a68f7d4a70b1ebd76befdc0e163ad55 +Subproject commit 6fadabd86043ee19e06c6ed59aa4e688c14fa8e4 diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 911774a4eb..9fe9b1bd15 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -74,7 +74,7 @@ function launch { # handle pythonpath ln -sfn $(pwd) /data/pythonpath - export PYTHONPATH="$PWD:$PWD/pyextra" + export PYTHONPATH="$PWD" # hardware specific init agnos_init diff --git a/launch_env.sh b/launch_env.sh index 88e1f2a9c5..3059ec268e 100755 --- a/launch_env.sh +++ b/launch_env.sh @@ -7,7 +7,7 @@ export OPENBLAS_NUM_THREADS=1 export VECLIB_MAXIMUM_THREADS=1 if [ -z "$AGNOS_VERSION" ]; then - export AGNOS_VERSION="6.1" + export AGNOS_VERSION="6.2" fi if [ -z "$PASSIVE" ]; then diff --git a/lgtm.yml b/lgtm.yml deleted file mode 100644 index 6ce9353562..0000000000 --- a/lgtm.yml +++ /dev/null @@ -1,19 +0,0 @@ -path_classifiers: - library: - - external - - third_party - - pyextra - - tools/lib/mkvparse -extraction: - cpp: - after_prepare: - - "pip3 install --upgrade --user pkgconfig cython setuptools wheel" - - "pip3 install --upgrade --user jinja2 pyyaml cython pycapnp numpy sympy tqdm\ - \ cffi logentries zmq scons" - - "export PATH=/opt/work/.local/bin:$PATH" - index: - build_command: "scons" - javascript: - index: - filters: - - exclude: "*" diff --git a/mypy.ini b/mypy.ini index 39b1b007a7..9b6d00d2c4 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2,7 +2,7 @@ python_version = 3.8 plugins = numpy.typing.mypy_plugin files = body, common, docs, scripts, selfdrive, site_scons, system, tools -exclude = ^(pyextra/)|(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/) +exclude = ^(cereal/)|(opendbc/)|(panda/)|(laika/)|(laika_repo/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(xx/) ; third-party packages ignore_missing_imports = True diff --git a/opendbc b/opendbc index 8f245e6ef5..b7d4a6e271 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 8f245e6ef5e25814d8e6e1f096221f6dfeefe86b +Subproject commit b7d4a6e2718d9ec3cf436441696528bedb1d44cf diff --git a/panda b/panda index 638d94d7c8..d7e3f1680f 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit 638d94d7c890dc57a0077747b40c8176f39dc294 +Subproject commit d7e3f1680fce8042071f3f56ce203c8ca7c5f9fb diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000000..b7554ac03a --- /dev/null +++ b/poetry.lock @@ -0,0 +1,8702 @@ +[[package]] +name = "adal" +version = "1.2.7" +description = "Note: This library is already replaced by MSAL Python, available here: https://pypi.org/project/msal/ .ADAL Python remains available here as a legacy. The ADAL for Python library makes it easy for python application to authenticate to Azure Active Directory (AAD) in order to access AAD protected web resources." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +cryptography = ">=1.1.0" +PyJWT = ">=1.0.0,<3" +python-dateutil = ">=2.1.0,<3" +requests = ">=2.0.0,<3" + +[[package]] +name = "aenum" +version = "3.1.11" +description = "Advanced Enumerations (compatible with Python's stdlib Enum), NamedTuples, and NamedConstants" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "aiohttp" +version = "3.8.3" +description = "Async http client/server framework (asyncio)" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +aiosignal = ">=1.1.2" +async-timeout = ">=4.0.0a3,<5.0" +attrs = ">=17.3.0" +charset-normalizer = ">=2.0,<3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns", "cchardet"] + +[[package]] +name = "aiosignal" +version = "1.2.0" +description = "aiosignal: a list of registered asynchronous callbacks" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alabaster" +version = "0.7.12" +description = "A configurable sidebar-enabled Sphinx theme" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "albumentations" +version = "1.3.0" +description = "Fast image augmentation library and easy to use wrapper around other libraries" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = ">=1.11.1" +opencv-python-headless = ">=4.1.1" +PyYAML = "*" +qudida = ">=0.0.4" +scikit-image = ">=0.16.1" +scipy = "*" + +[package.extras] +develop = ["imgaug (>=0.4.0)", "pytest"] +imgaug = ["imgaug (>=0.4.0)"] +tests = ["pytest"] + +[[package]] +name = "antlr4-python3-runtime" +version = "4.9.3" +description = "ANTLR 4.9.3 runtime for Python 3.7" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "anyio" +version = "3.6.2" +description = "High level compatibility layer for multiple asynchronous event loop implementations" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +idna = ">=2.8" +sniffio = ">=1.1" + +[package.extras] +doc = ["packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] +test = ["contextlib2", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (<0.15)", "uvloop (>=0.15)"] +trio = ["trio (>=0.16,<0.22)"] + +[[package]] +name = "apex" +version = "0.1" +description = "PyTorch Extensions written by NVIDIA" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +packaging = ">20.6" + +[package.source] +type = "url" +url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" + +[[package]] +name = "appdirs" +version = "1.4.4" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "applicationinsights" +version = "0.11.10" +description = "This project extends the Application Insights API surface to support Python." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "appnope" +version = "0.1.3" +description = "Disable App Nap on macOS >= 10.9" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "argcomplete" +version = "1.12.3" +description = "Bash tab completion for argparse" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["coverage", "flake8", "pexpect", "wheel"] + +[[package]] +name = "argon2-cffi" +version = "21.3.0" +description = "The secure Argon2 password hashing algorithm." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +argon2-cffi-bindings = "*" + +[package.extras] +dev = ["cogapp", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "pre-commit", "pytest", "sphinx", "sphinx-notfound-page", "tomli"] +docs = ["furo", "sphinx", "sphinx-notfound-page"] +tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pytest"] + +[[package]] +name = "argon2-cffi-bindings" +version = "21.2.0" +description = "Low-level CFFI bindings for Argon2" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.0.1" + +[package.extras] +dev = ["cogapp", "pre-commit", "pytest", "wheel"] +tests = ["pytest"] + +[[package]] +name = "astroid" +version = "2.12.12" +description = "An abstract syntax tree for Python with inference support." +category = "main" +optional = false +python-versions = ">=3.7.2" + +[package.dependencies] +lazy-object-proxy = ">=1.4.0" +typing-extensions = {version = ">=3.10", markers = "python_version < \"3.10\""} +wrapt = {version = ">=1.11,<2", markers = "python_version < \"3.11\""} + +[[package]] +name = "asttokens" +version = "2.0.8" +description = "Annotate AST trees with source code positions" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +test = ["astroid (<=2.5.3)", "pytest"] + +[[package]] +name = "async-timeout" +version = "4.0.2" +description = "Timeout context manager for asyncio programs" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "atomicwrites" +version = "1.4.1" +description = "Atomic file writes." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "attrs" +version = "22.1.0" +description = "Classes Without Boilerplate" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.extras] +dev = ["cloudpickle", "coverage[toml] (>=5.0.2)", "furo", "hypothesis", "mypy (>=0.900,!=0.940)", "pre-commit", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "sphinx", "sphinx-notfound-page", "zope.interface"] +docs = ["furo", "sphinx", "sphinx-notfound-page", "zope.interface"] +tests = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "zope.interface"] +tests-no-zope = ["cloudpickle", "coverage[toml] (>=5.0.2)", "hypothesis", "mypy (>=0.900,!=0.940)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins"] + +[[package]] +name = "av" +version = "9.2.0" +description = "Pythonic bindings for FFmpeg's libraries." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "azure-cli-core" +version = "2.41.0" +description = "Microsoft Azure Command-Line Tools Core Module" +category = "dev" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +argcomplete = ">=1.8,<2.0" +azure-cli-telemetry = ">=1.0.0,<1.1.0" +azure-mgmt-core = ">=1.2.0,<2" +cryptography = "*" +humanfriendly = ">=10.0,<11.0" +jmespath = "*" +knack = ">=0.10.0,<0.11.0" +msal = {version = "1.20.0b1", extras = ["broker"]} +msal-extensions = ">=1.0.0,<1.1.0" +msrestazure = ">=0.6.4,<0.7.0" +packaging = ">=20.9,<22.0" +paramiko = ">=2.0.8,<3.0.0" +pkginfo = ">=1.5.0.1" +psutil = {version = ">=5.9,<6.0", markers = "sys_platform != \"cygwin\""} +PyJWT = ">=2.1.0" +pyopenssl = ">=17.1.0" +requests = {version = "*", extras = ["socks"]} + +[[package]] +name = "azure-cli-telemetry" +version = "1.0.8" +description = "Microsoft Azure CLI Telemetry Package" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +applicationinsights = ">=0.11.1,<0.12" +portalocker = ">=1.6,<3" + +[[package]] +name = "azure-common" +version = "1.1.28" +description = "Microsoft Azure Client Library for Python (Common)" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "azure-core" +version = "1.26.0" +description = "Microsoft Azure Core Library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +requests = ">=2.18.4" +six = ">=1.11.0" +typing-extensions = ">=4.0.1" + +[package.extras] +aio = ["aiohttp (>=3.0)"] + +[[package]] +name = "azure-mgmt-core" +version = "1.3.2" +description = "Microsoft Azure Management Core Library for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +azure-core = ">=1.24.0,<2.0.0" + +[[package]] +name = "azure-nspkg" +version = "3.0.2" +description = "Microsoft Azure Namespace Package [Internal]" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "azure-storage-blob" +version = "2.1.0" +description = "Microsoft Azure Storage Blob Client Library for Python" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +azure-common = ">=1.1.5" +azure-storage-common = ">=2.1,<3.0" + +[[package]] +name = "azure-storage-common" +version = "2.1.0" +description = "Microsoft Azure Storage Common Client Library for Python" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +azure-common = ">=1.1.5" +cryptography = "*" +python-dateutil = "*" +requests = "*" + +[[package]] +name = "azure-storage-nspkg" +version = "3.1.0" +description = "Microsoft Azure Storage Namespace Package [Internal]" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +azure-nspkg = ">=2.0.0" + +[[package]] +name = "babel" +version = "2.10.3" +description = "Internationalization utilities" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pytz = ">=2015.7" + +[[package]] +name = "backcall" +version = "0.2.0" +description = "Specifications for callback functions passed in to an API" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "bcrypt" +version = "4.0.1" +description = "Modern password hashing for your software and your servers" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +tests = ["pytest (>=3.2.1,!=3.3.0)"] +typecheck = ["mypy"] + +[[package]] +name = "beautifulsoup4" +version = "4.11.1" +description = "Screen-scraping library" +category = "dev" +optional = false +python-versions = ">=3.6.0" + +[package.dependencies] +soupsieve = ">1.2" + +[package.extras] +html5lib = ["html5lib"] +lxml = ["lxml"] + +[[package]] +name = "bidict" +version = "0.22.0" +description = "The bidirectional mapping library for Python." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "bleach" +version = "5.0.1" +description = "An easy safelist-based HTML-sanitizing tool." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +six = ">=1.9.0" +webencodings = "*" + +[package.extras] +css = ["tinycss2 (>=1.1.0,<1.2)"] +dev = ["Sphinx (==4.3.2)", "black (==22.3.0)", "build (==0.8.0)", "flake8 (==4.0.1)", "hashin (==0.17.0)", "mypy (==0.961)", "pip-tools (==6.6.2)", "pytest (==7.1.2)", "tox (==3.25.0)", "twine (==4.0.1)", "wheel (==0.37.1)"] + +[[package]] +name = "blosc" +version = "1.9.2" +description = "Blosc data compressor" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "breathe" +version = "4.34.0" +description = "Sphinx Doxygen renderer" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +docutils = ">=0.12" +Sphinx = ">=4.0,<5.0.0 || >5.0.0,<6" + +[[package]] +name = "brotli" +version = "1.0.9" +description = "Python bindings for the Brotli compression library" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "cachecontrol" +version = "0.12.11" +description = "httplib2 caching for requests" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +lockfile = {version = ">=0.9", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2" +requests = "*" + +[package.extras] +filecache = ["lockfile (>=0.9)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cachy" +version = "0.3.0" +description = "Cachy provides a simple yet effective caching library." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +memcached = ["python-memcached (>=1.59,<2.0)"] +msgpack = ["msgpack-python (>=0.5,<0.6)"] +redis = ["redis (>=3.3.6,<4.0.0)"] + +[[package]] +name = "carla" +version = "0.9.13" +description = "Python API for communicating with the CARLA server." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "casadi" +version = "3.5.5" +description = "CasADi -- framework for algorithmic differentiation and numeric optimization" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "certifi" +version = "2022.9.24" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cffi" +version = "1.15.1" +description = "Foreign Function Interface for Python calling C code." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.3.1" +description = "Validate configuration and produce human readable error messages." +category = "dev" +optional = false +python-versions = ">=3.6.1" + +[[package]] +name = "charset-normalizer" +version = "2.1.1" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.6.0" + +[package.extras] +unicode-backport = ["unicodedata2"] + +[[package]] +name = "cleo" +version = "1.0.0a5" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +crashtest = ">=0.3.1,<0.4.0" +pylev = ">=1.3.0,<2.0.0" + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "click-plugins" +version = "1.1.1" +description = "An extension module for click to enable registering CLI commands via setuptools entry-points." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +dev = ["coveralls", "pytest (>=3.6)", "pytest-cov", "wheel"] + +[[package]] +name = "cligj" +version = "0.7.2" +description = "Click params for commmand line interfaces to GeoJSON" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, <4" + +[package.dependencies] +click = ">=4.0" + +[package.extras] +test = ["pytest-cov"] + +[[package]] +name = "cloudpickle" +version = "2.2.0" +description = "Extended pickling support for Python objects" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cmake" +version = "3.26.0" +description = "CMake is an open-source, cross-platform family of tools designed to build, test and package software" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["codecov (>=2.0.5)", "coverage (>=4.2)", "flake8 (>=3.0.4)", "path.py (>=11.5.0)", "pytest (>=3.0.3)", "pytest-cov (>=2.4.0)", "pytest-runner (>=2.9)", "pytest-virtualenv (>=1.7.0)", "scikit-build (>=0.10.0)", "setuptools (>=28.0.0)", "virtualenv (>=15.0.3)", "wheel"] + +[[package]] +name = "colorama" +version = "0.4.5" +description = "Cross-platform colored terminal text." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "coloredlogs" +version = "15.0.1" +description = "Colored terminal output for Python's logging module" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +humanfriendly = ">=9.1" + +[package.extras] +cron = ["capturer (>=2.4)"] + +[[package]] +name = "configargparse" +version = "1.5.3" +description = "A drop-in replacement for argparse that allows options to also be set via config files and/or environment variables." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +test = ["PyYAML", "mock", "pytest"] +yaml = ["PyYAML"] + +[[package]] +name = "contourpy" +version = "1.0.5" +description = "Python library for calculating contours of 2D quadrilateral grids" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = ">=1.16" + +[package.extras] +bokeh = ["bokeh", "selenium"] +docs = ["docutils (<0.18)", "sphinx", "sphinx-rtd-theme"] +test = ["Pillow", "flake8", "isort", "matplotlib", "pytest"] +test-minimal = ["pytest"] +test-no-codebase = ["Pillow", "matplotlib", "pytest"] + +[[package]] +name = "control" +version = "0.9.2" +description = "Python Control Systems Library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +matplotlib = "*" +numpy = "*" +scipy = "*" + +[package.extras] +slycot = ["slycot (>=0.4.0)"] +test = ["pytest", "pytest-timeout"] + +[[package]] +name = "coverage" +version = "6.5.0" +description = "Code coverage measurement for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "crashtest" +version = "0.3.1" +description = "Manage Python errors with ease" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "crcmod" +version = "1.7" +description = "CRC Generator" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "cryptography" +version = "37.0.4" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.12" + +[package.extras] +docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx_rtd_theme"] +docstest = ["pyenchant (>=1.6.11)", "sphinxcontrib-spelling (>=4.0.1)", "twine (>=1.12.0)"] +pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] +sdist = ["setuptools_rust (>=0.11.4)"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["hypothesis (>=1.11.4,!=3.79.2)", "iso8601", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-subtests", "pytest-xdist", "pytz"] + +[[package]] +name = "cupy-cuda11x" +version = "11.6.0" +description = "CuPy: NumPy & SciPy for GPU" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +fastrlock = ">=0.5" +numpy = ">=1.20,<1.27" + +[package.extras] +all = ["Cython (>=0.29.22,<3)", "optuna (>=2.0)", "scipy (>=1.6,<1.12)"] +stylecheck = ["autopep8 (==1.5.5)", "flake8 (==3.8.4)", "mypy (==0.950)", "pbr (==5.5.1)", "pycodestyle (==2.6.0)", "types-setuptools (==57.4.14)"] +test = ["hypothesis (>=6.37.2,<6.55.0)", "pytest (>=7.2)"] + +[[package]] +name = "cycler" +version = "0.11.0" +description = "Composable style cycles" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "cython" +version = "0.29.32" +description = "The Cython compiler for writing C extensions for the Python language." +category = "main" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "datadog" +version = "0.44.0" +description = "The Datadog Python library" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" + +[package.dependencies] +requests = ">=2.6.0" + +[[package]] +name = "debugpy" +version = "1.6.3" +description = "An implementation of the Debug Adapter Protocol for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "decorator" +version = "5.1.1" +description = "Decorators for Humans" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "defusedxml" +version = "0.7.1" +description = "XML bomb protection for Python stdlib modules" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "deprecated" +version = "1.2.13" +description = "Python @deprecated decorator to deprecate old python classes, functions or methods." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +wrapt = ">=1.10,<2" + +[package.extras] +dev = ["PyTest", "PyTest (<5)", "PyTest-Cov", "PyTest-Cov (<2.6)", "bump2version (<1)", "configparser (<5)", "importlib-metadata (<3)", "importlib-resources (<4)", "sphinx (<2)", "sphinxcontrib-websupport (<2)", "tox", "zipp (<2)"] + +[[package]] +name = "dictdiffer" +version = "0.9.0" +description = "Dictdiffer is a library that helps you to diff and patch dictionaries." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +all = ["Sphinx (>=3)", "check-manifest (>=0.42)", "mock (>=1.3.0)", "numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "sphinx-rtd-theme (>=0.2)", "tox (>=3.7.0)"] +docs = ["Sphinx (>=3)", "sphinx-rtd-theme (>=0.2)"] +numpy = ["numpy (>=1.13.0)", "numpy (>=1.15.0)", "numpy (>=1.18.0)", "numpy (>=1.20.0)"] +tests = ["check-manifest (>=0.42)", "mock (>=1.3.0)", "pytest (==5.4.3)", "pytest (>=6)", "pytest-cov (>=2.10.1)", "pytest-isort (>=1.2.0)", "pytest-pycodestyle (>=2)", "pytest-pycodestyle (>=2.2.0)", "pytest-pydocstyle (>=2)", "pytest-pydocstyle (>=2.2.0)", "sphinx (>=3)", "tox (>=3.7.0)"] + +[[package]] +name = "dill" +version = "0.3.5.1" +description = "serialize all of python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*" + +[package.extras] +graph = ["objgraph (>=1.7.2)"] + +[[package]] +name = "distlib" +version = "0.3.6" +description = "Distribution utilities" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "docutils" +version = "0.17.1" +description = "Docutils -- Python Documentation Utilities" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "dotmap" +version = "1.3.30" +description = "ordered, dynamically-expandable dot-access dictionary" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "dulwich" +version = "0.20.46" +description = "Python Git Library" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "efficientnet-pytorch" +version = "0.6.3" +description = "EfficientNet implemented in PyTorch." +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.dependencies] +torch = "*" + +[[package]] +name = "einops" +version = "0.5.0" +description = "A new flavour of deep learning operations" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "elastic-transport" +version = "8.4.0" +description = "Transport classes and utilities shared among Python Elastic client libraries" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +certifi = "*" +urllib3 = ">=1.26.2,<2" + +[package.extras] +develop = ["aiohttp", "mock", "pytest", "pytest-asyncio", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests", "trustme"] + +[[package]] +name = "elasticsearch" +version = "8.4.3" +description = "Python client for Elasticsearch" +category = "dev" +optional = false +python-versions = ">=3.6, <4" + +[package.dependencies] +elastic-transport = ">=8,<9" + +[package.extras] +async = ["aiohttp (>=3,<4)"] +requests = ["requests (>=2.4.0,<3.0.0)"] + +[[package]] +name = "entrypoints" +version = "0.4" +description = "Discover and load entry points from installed packages." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "execnet" +version = "1.9.0" +description = "execnet: rapid multi-Python deployment" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +testing = ["pre-commit"] + +[[package]] +name = "executing" +version = "1.1.1" +description = "Get the currently executing AST node of a frame, and other information" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +tests = ["asttokens", "littleutils", "pytest", "rich"] + +[[package]] +name = "fastcluster" +version = "1.2.6" +description = "Fast hierarchical clustering routines for R and Python." +category = "dev" +optional = false +python-versions = ">=3" + +[package.dependencies] +numpy = ">=1.9" + +[package.extras] +test = ["scipy (>=1.6.3)"] + +[[package]] +name = "fastjsonschema" +version = "2.16.2" +description = "Fastest Python implementation of JSON schema" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "fastrlock" +version = "0.8" +description = "Fast, re-entrant optimistic lock implemented in Cython" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "filelock" +version = "3.8.0" +description = "A platform independent file lock." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2022.6.21)", "sphinx (>=5.1.1)", "sphinx-autodoc-typehints (>=1.19.1)"] +testing = ["covdefaults (>=2.2)", "coverage (>=6.4.2)", "pytest (>=7.1.2)", "pytest-cov (>=3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "fiona" +version = "1.9.1" +description = "Fiona reads and writes spatial data files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +certifi = "*" +click = ">=8.0,<9.0" +click-plugins = ">=1.0" +cligj = ">=0.5" +munch = ">=2.3.2" +setuptools = "*" + +[package.extras] +all = ["Fiona[calc,s3,test]"] +calc = ["shapely"] +s3 = ["boto3 (>=1.3.1)"] +test = ["Fiona[s3]", "pytest (>=7)", "pytest-cov", "pytz"] + +[[package]] +name = "flake8" +version = "4.0.1" +description = "the modular source code checker: pep8 pyflakes and co" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mccabe = ">=0.6.0,<0.7.0" +pycodestyle = ">=2.8.0,<2.9.0" +pyflakes = ">=2.4.0,<2.5.0" + +[[package]] +name = "flask" +version = "2.2.2" +description = "A simple framework for building complex web applications." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +click = ">=8.0" +importlib-metadata = {version = ">=3.6.0", markers = "python_version < \"3.10\""} +itsdangerous = ">=2.0" +Jinja2 = ">=3.0" +Werkzeug = ">=2.2.2" + +[package.extras] +async = ["asgiref (>=3.2)"] +dotenv = ["python-dotenv"] + +[[package]] +name = "flask-cors" +version = "3.0.10" +description = "A Flask extension adding a decorator for CORS support" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +Flask = ">=0.9" +Six = "*" + +[[package]] +name = "flask-socketio" +version = "5.3.1" +description = "Socket.IO integration for Flask applications" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +Flask = ">=0.9" +python-socketio = ">=5.0.2" + +[[package]] +name = "flatbuffers" +version = "22.9.24" +description = "The FlatBuffers serialization format for Python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "fonttools" +version = "4.37.4" +description = "Tools to manipulate font files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0,<5)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=14.0.0)", "xattr", "zopfli (>=0.1.4)"] +graphite = ["lz4 (>=1.7.4.2)"] +interpolatable = ["munkres", "scipy"] +lxml = ["lxml (>=4.0,<5)"] +pathops = ["skia-pathops (>=0.5.0)"] +plot = ["matplotlib"] +repacker = ["uharfbuzz (>=0.23.0)"] +symfont = ["sympy"] +type1 = ["xattr"] +ufo = ["fs (>=2.2.0,<3)"] +unicode = ["unicodedata2 (>=14.0.0)"] +woff = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "zopfli (>=0.1.4)"] + +[[package]] +name = "frozenlist" +version = "1.3.1" +description = "A list-like structure which implements collections.abc.MutableSequence" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "ft4222" +version = "1.6.0" +description = "Python wrapper around libFT4222." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "future" +version = "0.18.2" +description = "Clean single-source support for Python 3 and 2" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "future-fstrings" +version = "1.2.0" +description = "A backport of fstrings to python<3.6" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +rewrite = ["tokenize-rt (>=3)"] + +[[package]] +name = "geoalchemy2" +version = "0.12.5" +description = "Using SQLAlchemy with Spatial Databases" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = "*" +SQLAlchemy = ">=1.4" + +[[package]] +name = "geopandas" +version = "0.12.2" +description = "Geographic pandas extensions" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +fiona = ">=1.8" +packaging = "*" +pandas = ">=1.0.0" +pyproj = ">=2.6.1.post1" +shapely = ">=1.7" + +[[package]] +name = "gevent" +version = "22.10.1" +description = "Coroutine-based network library" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5" + +[package.dependencies] +cffi = {version = ">=1.12.2", markers = "platform_python_implementation == \"CPython\" and sys_platform == \"win32\""} +greenlet = {version = ">=1.1.3,<2.0", markers = "platform_python_implementation == \"CPython\""} +setuptools = "*" +"zope.event" = "*" +"zope.interface" = "*" + +[package.extras] +dnspython = ["dnspython (>=1.16.0,<2.0)", "idna"] +docs = ["repoze.sphinx.autointerface", "sphinxcontrib-programoutput", "zope.schema"] +monitor = ["psutil (>=5.7.0)"] +recommended = ["backports.socketpair", "cffi (>=1.12.2)", "dnspython (>=1.16.0,<2.0)", "idna", "psutil (>=5.7.0)", "selectors2"] +test = ["backports.socketpair", "cffi (>=1.12.2)", "contextvars (==2.4)", "coverage (>=5.0)", "coveralls (>=1.7.0)", "dnspython (>=1.16.0,<2.0)", "futures", "idna", "mock", "objgraph", "psutil (>=5.7.0)", "requests", "selectors2"] + +[[package]] +name = "geventhttpclient" +version = "2.0.2" +description = "http client library for gevent" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +brotli = "*" +certifi = "*" +gevent = ">=0.13" +six = "*" + +[[package]] +name = "greenlet" +version = "1.1.3.post0" +description = "Lightweight in-process concurrent programming" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*" + +[package.extras] +docs = ["Sphinx"] + +[[package]] +name = "gunicorn" +version = "20.1.0" +description = "WSGI HTTP Server for UNIX" +category = "main" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +setuptools = ">=3.0" + +[package.extras] +eventlet = ["eventlet (>=0.24.1)"] +gevent = ["gevent (>=1.4.0)"] +setproctitle = ["setproctitle"] +tornado = ["tornado (>=0.2)"] + +[[package]] +name = "h3" +version = "3.7.4" +description = "Hierarchical hexagonal geospatial indexing system" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +all = ["flake8", "numpy", "pylint", "pytest", "pytest-cov"] +numpy = ["numpy"] +test = ["flake8", "pylint", "pytest", "pytest-cov"] + +[[package]] +name = "hatanaka" +version = "2.4.0" +description = "Effortlessly compress / decompress any RINEX file" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +importlib-resources = "*" +ncompress = "*" + +[package.extras] +dev = ["pytest"] +tests = ["pytest"] + +[[package]] +name = "hexdump" +version = "3.3" +description = "dump binary data to hex format and restore from there" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "html5lib" +version = "1.1" +description = "HTML parser based on the WHATWG HTML specification" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +six = ">=1.9" +webencodings = "*" + +[package.extras] +all = ["chardet (>=2.2)", "genshi", "lxml"] +chardet = ["chardet (>=2.2)"] +genshi = ["genshi"] +lxml = ["lxml"] + +[[package]] +name = "humanfriendly" +version = "10.0" +description = "Human friendly output for text interfaces using Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +pyreadline3 = {version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\""} + +[[package]] +name = "hypothesis" +version = "6.46.7" +description = "A library for property-based testing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +sortedcontainers = ">=2.1.0,<3.0.0" + +[package.extras] +all = ["backports.zoneinfo (>=0.2.1)", "black (>=19.10b0)", "click (>=7.0)", "django (>=2.2)", "dpcontracts (>=0.4)", "importlib-metadata (>=3.6)", "lark-parser (>=0.6.5)", "libcst (>=0.3.16)", "numpy (>=1.9.0)", "pandas (>=0.25)", "pytest (>=4.6)", "python-dateutil (>=1.4)", "pytz (>=2014.1)", "redis (>=3.0.0)", "rich (>=9.0.0)", "tzdata (>=2022.1)"] +cli = ["black (>=19.10b0)", "click (>=7.0)", "rich (>=9.0.0)"] +codemods = ["libcst (>=0.3.16)"] +dateutil = ["python-dateutil (>=1.4)"] +django = ["django (>=2.2)"] +dpcontracts = ["dpcontracts (>=0.4)"] +ghostwriter = ["black (>=19.10b0)"] +lark = ["lark-parser (>=0.6.5)"] +numpy = ["numpy (>=1.9.0)"] +pandas = ["pandas (>=0.25)"] +pytest = ["pytest (>=4.6)"] +pytz = ["pytz (>=2014.1)"] +redis = ["redis (>=3.0.0)"] +zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] + +[[package]] +name = "identify" +version = "2.5.6" +description = "File identification library for Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.4" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "imageio" +version = "2.22.2" +description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +numpy = "*" +pillow = ">=8.3.2" + +[package.extras] +all-plugins = ["astropy", "av", "imageio-ffmpeg", "opencv-python", "psutil", "tifffile"] +all-plugins-pypy = ["av", "imageio-ffmpeg", "psutil", "tifffile"] +build = ["wheel"] +dev = ["black", "flake8", "fsspec[github]", "invoke", "pytest", "pytest-cov"] +docs = ["numpydoc", "pydata-sphinx-theme", "sphinx"] +ffmpeg = ["imageio-ffmpeg", "psutil"] +fits = ["astropy"] +full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "invoke", "itk", "numpydoc", "opencv-python", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "sphinx", "tifffile", "wheel"] +gdal = ["gdal"] +itk = ["itk"] +linting = ["black", "flake8"] +opencv = ["opencv-python"] +pyav = ["av"] +test = ["fsspec[github]", "invoke", "pytest", "pytest-cov"] +tifffile = ["tifffile"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "importlib-metadata" +version = "4.13.0" +description = "Read metadata from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +perf = ["ipython"] +testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] + +[[package]] +name = "importlib-resources" +version = "5.10.0" +description = "Read resources from Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "influxdb-client" +version = "1.33.0" +description = "InfluxDB 2.0 Python client library" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = ">=14.05.14" +python-dateutil = ">=2.5.3" +reactivex = ">=4.0.4" +setuptools = ">=21.0.0" +urllib3 = ">=1.26.0" + +[package.extras] +async = ["aiocsv (>=1.2.2)", "aiohttp (>=3.8.1)"] +ciso = ["ciso8601 (>=2.1.1)"] +extra = ["numpy", "pandas (>=0.25.3)"] +test = ["aioresponses (>=0.7.3)", "coverage (>=4.0.3)", "flake8 (>=5.0.3)", "httpretty (==1.0.5)", "jinja2 (==3.1.2)", "nose (>=1.3.7)", "pluggy (>=0.3.1)", "psutil (>=5.6.3)", "py (>=1.4.31)", "pytest (>=5.0.0)", "pytest-cov (>=3.0.0)", "randomize (>=0.13)", "sphinx (==1.8.5)", "sphinx-rtd-theme"] + +[[package]] +name = "iniconfig" +version = "1.1.1" +description = "iniconfig: brain-dead simple config-ini parsing" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "inputs" +version = "0.5" +description = "Cross-platform Python support for keyboards, mice and gamepads." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "ioctl-opt" +version = "1.2.2" +description = "Functions to compute fnctl.ioctl's opt argument" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "ipykernel" +version = "6.16.1" +description = "IPython Kernel for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +appnope = {version = "*", markers = "platform_system == \"Darwin\""} +debugpy = ">=1.0" +ipython = ">=7.23.1" +jupyter-client = ">=6.1.12" +matplotlib-inline = ">=0.1" +nest-asyncio = "*" +packaging = "*" +psutil = "*" +pyzmq = ">=17" +tornado = ">=6.1" +traitlets = ">=5.1.0" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt"] +test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "ipython" +version = "8.5.0" +description = "IPython: Productive Interactive Computing" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +appnope = {version = "*", markers = "sys_platform == \"darwin\""} +backcall = "*" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +decorator = "*" +jedi = ">=0.16" +matplotlib-inline = "*" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} +pickleshare = "*" +prompt-toolkit = ">3.0.1,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5" + +[package.extras] +all = ["Sphinx (>=1.3)", "black", "curio", "ipykernel", "ipyparallel", "ipywidgets", "matplotlib (!=3.2.0)", "nbconvert", "nbformat", "notebook", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "qtconsole", "testpath", "trio"] +black = ["black"] +doc = ["Sphinx (>=1.3)"] +kernel = ["ipykernel"] +nbconvert = ["nbconvert"] +nbformat = ["nbformat"] +notebook = ["ipywidgets", "notebook"] +parallel = ["ipyparallel"] +qtconsole = ["qtconsole"] +test = ["pytest (<7.1)", "pytest-asyncio", "testpath"] +test-extra = ["curio", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.19)", "pandas", "pytest (<7.1)", "pytest-asyncio", "testpath", "trio"] + +[[package]] +name = "ipython-genutils" +version = "0.2.0" +description = "Vestigial utilities from IPython" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "ipywidgets" +version = "8.0.2" +description = "Jupyter interactive widgets" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipykernel = ">=4.5.1" +ipython = ">=6.1.0" +jupyterlab-widgets = ">=3.0,<4.0" +traitlets = ">=4.3.1" +widgetsnbextension = ">=4.0,<5.0" + +[package.extras] +test = ["jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "main" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +colors = ["colorama (>=0.4.3,<0.5.0)"] +pipfile-deprecated-finder = ["pipreqs", "requirementslib"] +plugins = ["setuptools"] +requirements-deprecated-finder = ["pip-api", "pipreqs"] + +[[package]] +name = "itsdangerous" +version = "2.1.2" +description = "Safely pass data to untrusted environments and back." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jaraco-classes" +version = "3.2.3" +description = "Utility functions for Python class constructs" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "jedi" +version = "0.18.1" +description = "An autocompletion tool for Python that can be used for text editors." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +parso = ">=0.8.0,<0.9.0" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["Django (<3.1)", "colorama", "docopt", "pytest (<7.0.0)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.2" +description = "A very fast and expressive template engine." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jmespath" +version = "1.0.1" +description = "JSON Matching Expressions" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "joblib" +version = "1.2.0" +description = "Lightweight pipelining with Python functions" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "json-logging-py" +version = "0.2" +description = "JSON / Logstash formatters for Python logging" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "json-rpc" +version = "1.13.0" +description = "JSON-RPC transport implementation" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "json5" +version = "0.9.10" +description = "A Python implementation of the JSON5 data format." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["hypothesis"] + +[[package]] +name = "jsonschema" +version = "4.16.0" +description = "An implementation of JSON Schema validation for Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=17.4.0" +importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} +pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} +pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" + +[package.extras] +format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] +format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] + +[[package]] +name = "jupyter" +version = "1.0.0" +description = "Jupyter metapackage. Install all the Jupyter components in one go." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +ipykernel = "*" +ipywidgets = "*" +jupyter-console = "*" +nbconvert = "*" +notebook = "*" +qtconsole = "*" + +[[package]] +name = "jupyter-client" +version = "7.4.3" +description = "Jupyter protocol implementation and client libraries" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +entrypoints = "*" +jupyter-core = ">=4.9.2" +nest-asyncio = ">=1.5.4" +python-dateutil = ">=2.8.2" +pyzmq = ">=23.0" +tornado = ">=6.2" +traitlets = "*" + +[package.extras] +doc = ["ipykernel", "myst-parser", "sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-commit", "pytest", "pytest-asyncio (>=0.18)", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-console" +version = "6.4.4" +description = "Jupyter terminal console" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipykernel = "*" +ipython = "*" +jupyter-client = ">=7.0.0" +prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" +pygments = "*" + +[package.extras] +test = ["pexpect"] + +[[package]] +name = "jupyter-core" +version = "4.11.2" +description = "Jupyter core package. A base package on which Jupyter projects rely." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} +traitlets = "*" + +[package.extras] +test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "jupyter-server" +version = "1.21.0" +description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +anyio = ">=3.1.0,<4" +argon2-cffi = "*" +jinja2 = "*" +jupyter-client = ">=6.1.12" +jupyter-core = ">=4.7.0" +nbconvert = ">=6.4.4" +nbformat = ">=5.2.0" +packaging = "*" +prometheus-client = "*" +pywinpty = {version = "*", markers = "os_name == \"nt\""} +pyzmq = ">=17" +Send2Trash = "*" +terminado = ">=0.8.3" +tornado = ">=6.1.0" +traitlets = ">=5.1" +websocket-client = "*" + +[package.extras] +test = ["coverage", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-mock", "pytest-timeout", "pytest-tornasync", "requests"] + +[[package]] +name = "jupyterlab" +version = "3.4.8" +description = "JupyterLab computational environment" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ipython = "*" +jinja2 = ">=2.1" +jupyter-core = "*" +jupyter-server = ">=1.16,<2.0" +jupyterlab-server = ">=2.10,<3.0" +nbclassic = "*" +notebook = "<7" +packaging = "*" +tomli = "*" +tornado = ">=6.1.0" + +[package.extras] +test = ["check-manifest", "coverage", "jupyterlab-server[test]", "pre-commit", "pytest (>=6.0)", "pytest-check-links (>=0.5)", "pytest-console-scripts", "pytest-cov", "requests", "requests-cache", "virtualenv"] +ui-tests = ["build"] + +[[package]] +name = "jupyterlab-pygments" +version = "0.2.2" +description = "Pygments theme using JupyterLab CSS variables" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "jupyterlab-server" +version = "2.16.1" +description = "A set of server components for JupyterLab and JupyterLab like applications." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +babel = "*" +importlib-metadata = {version = ">=4.8.3", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0.3" +json5 = "*" +jsonschema = ">=3.0.1" +jupyter-server = ">=1.8,<3" +packaging = "*" +requests = "*" + +[package.extras] +docs = ["autodoc-traits", "docutils (<0.19)", "jinja2 (<3.1.0)", "mistune (<1)", "myst-parser", "pydata-sphinx-theme", "sphinx", "sphinx-copybutton", "sphinxcontrib-openapi"] +openapi = ["openapi-core (>=0.14.2)", "ruamel-yaml"] +test = ["codecov", "ipykernel", "jupyter-server[test]", "openapi-core (>=0.14.2,<0.15.0)", "openapi-spec-validator (<0.5)", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "ruamel-yaml", "strict-rfc3339"] + +[[package]] +name = "jupyterlab-vim" +version = "0.15.1" +description = "Code cell vim bindings" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "jupyterlab-widgets" +version = "3.0.3" +description = "Jupyter interactive widgets for JupyterLab" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "keyring" +version = "23.9.3" +description = "Store and access your passwords safely." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +docs = ["jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx"] +testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "kiwisolver" +version = "1.4.4" +description = "A fast implementation of the Cassowary constraint solver" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "knack" +version = "0.10.0" +description = "A Command-Line Interface framework" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +argcomplete = "*" +jmespath = "*" +pygments = "*" +pyyaml = "*" +tabulate = "*" + +[[package]] +name = "lazy-object-proxy" +version = "1.7.1" +description = "A fast and thorough lazy object proxy." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "libusb1" +version = "3.0.0" +description = "Pure-python wrapper for libusb-1.0" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "lit" +version = "15.0.7" +description = "A Software Testing Tool" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "lockfile" +version = "0.12.2" +description = "Platform-independent file locking module" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "lru-dict" +version = "1.1.8" +description = "An Dict like LRU container." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "lxml" +version = "4.9.1" +description = "Powerful and Pythonic XML processing library combining libxml2/libxslt with the ElementTree API." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, != 3.4.*" + +[package.extras] +cssselect = ["cssselect (>=0.7)"] +html5 = ["html5lib"] +htmlsoup = ["BeautifulSoup4"] +source = ["Cython (>=0.29.7)"] + +[[package]] +name = "mako" +version = "1.2.3" +description = "A super-fast templating language that borrows the best ideas from the existing templating languages." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=0.9.2" + +[package.extras] +babel = ["Babel"] +lingua = ["lingua"] +testing = ["pytest"] + +[[package]] +name = "markdown" +version = "3.4.1" +description = "Python implementation of Markdown." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} + +[package.extras] +testing = ["coverage", "pyyaml"] + +[[package]] +name = "markdown-it-py" +version = "2.1.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark (>=3.2,<4.0)"] +code-style = ["pre-commit (==2.6)"] +compare = ["commonmark (>=0.9.1,<0.10.0)", "markdown (>=3.3.6,<3.4.0)", "mistletoe (>=0.8.1,<0.9.0)", "mistune (>=2.0.2,<2.1.0)", "panflute (>=2.1.3,<2.2.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "markupsafe" +version = "2.1.1" +description = "Safely add untrusted strings to HTML/XML markup." +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "matplotlib" +version = "3.6.1" +description = "Python plotting package" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +contourpy = ">=1.0.1" +cycler = ">=0.10" +fonttools = ">=4.22.0" +kiwisolver = ">=1.0.1" +numpy = ">=1.19" +packaging = ">=20.0" +pillow = ">=6.2.0" +pyparsing = ">=2.2.1" +python-dateutil = ">=2.7" +setuptools_scm = ">=7" + +[[package]] +name = "matplotlib-inline" +version = "0.1.6" +description = "Inline Matplotlib backend for Jupyter" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +traitlets = "*" + +[[package]] +name = "mccabe" +version = "0.6.1" +description = "McCabe checker, plugin for flake8" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "mdit-py-plugins" +version = "0.3.1" +description = "Collection of plugins for markdown-it-py" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +markdown-it-py = ">=1.0.0,<3.0.0" + +[package.extras] +code-style = ["pre-commit"] +rtd = ["attrs", "myst-parser (>=0.16.1,<0.17.0)", "sphinx-book-theme (>=0.1.0,<0.2.0)"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mistune" +version = "2.0.4" +description = "A sane Markdown parser with useful plugins and renderers" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "more-itertools" +version = "9.0.0" +description = "More routines for operating on iterables, beyond itertools" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "mpld3" +version = "0.5.8" +description = "D3 Viewer for Matplotlib" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +jinja2 = "*" +matplotlib = "*" + +[[package]] +name = "mpmath" +version = "1.2.1" +description = "Python library for arbitrary-precision floating-point arithmetic" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +develop = ["codecov", "pycodestyle", "pytest (>=4.6)", "pytest-cov", "wheel"] +tests = ["pytest (>=4.6)"] + +[[package]] +name = "msal" +version = "1.20.0b1" +description = "The Microsoft Authentication Library (MSAL) for Python library enables your app to access the Microsoft Cloud by supporting authentication of users with Microsoft Azure Active Directory accounts (AAD) and Microsoft Accounts (MSA) using industry standard OAuth2 and OpenID Connect." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +cryptography = ">=0.6,<40" +PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} +pymsalruntime = {version = ">=0.11.2,<0.12", optional = true, markers = "python_version >= \"3.6\" and platform_system == \"Windows\" and extra == \"broker\""} +requests = ">=2.0.0,<3" + +[package.extras] +broker = ["pymsalruntime (>=0.11.2,<0.12)"] + +[[package]] +name = "msal-extensions" +version = "1.0.0" +description = "Microsoft Authentication Library extensions (MSAL EX) provides a persistence API that can save your data on disk, encrypted on Windows, macOS and Linux. Concurrent data access will be coordinated by a file lock mechanism." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +msal = ">=0.4.1,<2.0.0" +portalocker = [ + {version = ">=1.0,<3", markers = "python_version >= \"3.5\" and platform_system != \"Windows\""}, + {version = ">=1.6,<3", markers = "python_version >= \"3.5\" and platform_system == \"Windows\""}, +] + +[[package]] +name = "msgpack" +version = "1.0.4" +description = "MessagePack serializer" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "msgpack-numpy" +version = "0.4.8" +description = "Numpy data serialization using msgpack" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +msgpack = ">=0.5.2" +numpy = ">=1.9.0" + +[[package]] +name = "msgpack-python" +version = "0.5.6" +description = "MessagePack (de)serializer." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "msrest" +version = "0.7.1" +description = "AutoRest swagger generator Python client runtime." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +azure-core = ">=1.24.0" +certifi = ">=2017.4.17" +isodate = ">=0.6.0" +requests = ">=2.16,<3.0" +requests-oauthlib = ">=0.5.0" + +[package.extras] +async = ["aiodns", "aiohttp (>=3.0)"] + +[[package]] +name = "msrestazure" +version = "0.6.4" +description = "AutoRest swagger generator Python client runtime. Azure-specific module." +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +adal = ">=0.6.0,<2.0.0" +msrest = ">=0.6.0,<2.0.0" +six = "*" + +[[package]] +name = "multidict" +version = "6.0.2" +description = "multidict implementation" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "munch" +version = "2.5.0" +description = "A dot-accessible dictionary (a la JavaScript objects)" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" + +[package.extras] +testing = ["astroid (>=1.5.3,<1.6.0)", "astroid (>=2.0)", "coverage", "pylint (>=1.7.2,<1.8.0)", "pylint (>=2.3.1,<2.4.0)", "pytest"] +yaml = ["PyYAML (>=5.1.0)"] + +[[package]] +name = "mypy" +version = "0.961" +description = "Optional static typing for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +mypy-extensions = ">=0.4.3" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = ">=3.10" + +[package.extras] +dmypy = ["psutil (>=4.0)"] +python2 = ["typed-ast (>=1.4.0,<2)"] +reports = ["lxml"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "myst-parser" +version = "0.18.1" +description = "An extended commonmark compliant parser, with bridges to docutils & sphinx." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +docutils = ">=0.15,<0.20" +jinja2 = "*" +markdown-it-py = ">=1.0.0,<3.0.0" +mdit-py-plugins = ">=0.3.1,<0.4.0" +pyyaml = "*" +sphinx = ">=4,<6" +typing-extensions = "*" + +[package.extras] +code-style = ["pre-commit (>=2.12,<3.0)"] +linkify = ["linkify-it-py (>=1.0,<2.0)"] +rtd = ["ipython", "sphinx-book-theme", "sphinx-design", "sphinxcontrib.mermaid (>=0.7.1,<0.8.0)", "sphinxext-opengraph (>=0.6.3,<0.7.0)", "sphinxext-rediraffe (>=0.2.7,<0.3.0)"] +testing = ["beautifulsoup4", "coverage[toml]", "pytest (>=6,<7)", "pytest-cov", "pytest-param-files (>=0.3.4,<0.4.0)", "pytest-regressions", "sphinx (<5.2)", "sphinx-pytest"] + +[[package]] +name = "natsort" +version = "8.2.0" +description = "Simple yet flexible natural sorting in Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +fast = ["fastnumbers (>=2.0.0)"] +icu = ["PyICU (>=1.0.0)"] + +[[package]] +name = "nbclassic" +version = "0.4.7" +description = "A web-based notebook environment for interactive computing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +argon2-cffi = "*" +ipykernel = "*" +ipython-genutils = "*" +jinja2 = "*" +jupyter-client = ">=6.1.1" +jupyter-core = ">=4.6.1" +jupyter-server = ">=1.8" +nbconvert = ">=5" +nbformat = "*" +nest-asyncio = ">=1.5" +notebook-shim = ">=0.1.0" +prometheus-client = "*" +pyzmq = ">=17" +Send2Trash = ">=1.8.0" +terminado = ">=0.8.3" +tornado = ">=6.1" +traitlets = ">=4.2.1" + +[package.extras] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-tornasync", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"] + +[[package]] +name = "nbclient" +version = "0.7.0" +description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." +category = "dev" +optional = false +python-versions = ">=3.7.0" + +[package.dependencies] +jupyter-client = ">=6.1.5" +nbformat = ">=5.0" +nest-asyncio = "*" +traitlets = ">=5.2.2" + +[package.extras] +sphinx = ["Sphinx (>=1.7)", "autodoc-traits", "mock", "moto", "myst-parser", "sphinx-book-theme"] +test = ["black", "check-manifest", "flake8", "ipykernel", "ipython", "ipywidgets", "mypy", "nbconvert", "pip (>=18.1)", "pre-commit", "pytest (>=4.1)", "pytest-asyncio", "pytest-cov (>=2.6.1)", "setuptools (>=60.0)", "testpath", "twine (>=1.11.0)", "xmltodict"] + +[[package]] +name = "nbconvert" +version = "7.2.2" +description = "Converting Jupyter Notebooks" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +beautifulsoup4 = "*" +bleach = "*" +defusedxml = "*" +importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} +jinja2 = ">=3.0" +jupyter-core = ">=4.7" +jupyterlab-pygments = "*" +markupsafe = ">=2.0" +mistune = ">=2.0.3,<3" +nbclient = ">=0.5.0" +nbformat = ">=5.1" +packaging = "*" +pandocfilters = ">=1.4.1" +pygments = ">=2.4.1" +tinycss2 = "*" +traitlets = ">=5.0" + +[package.extras] +all = ["ipykernel", "ipython", "ipywidgets (>=7)", "myst-parser", "nbsphinx (>=0.2.12)", "pre-commit", "pyppeteer (>=1,<1.1)", "pyqtwebengine (>=5.15)", "pytest", "pytest-cov", "pytest-dependency", "sphinx (==5.0.2)", "sphinx-rtd-theme", "tornado (>=6.1)"] +docs = ["ipython", "myst-parser", "nbsphinx (>=0.2.12)", "sphinx (==5.0.2)", "sphinx-rtd-theme"] +qtpdf = ["pyqtwebengine (>=5.15)"] +qtpng = ["pyqtwebengine (>=5.15)"] +serve = ["tornado (>=6.1)"] +test = ["ipykernel", "ipywidgets (>=7)", "pre-commit", "pyppeteer (>=1,<1.1)", "pytest", "pytest-cov", "pytest-dependency"] +webpdf = ["pyppeteer (>=1,<1.1)"] + +[[package]] +name = "nbformat" +version = "5.7.0" +description = "The Jupyter Notebook format" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +fastjsonschema = "*" +jsonschema = ">=2.6" +jupyter-core = "*" +traitlets = ">=5.1" + +[package.extras] +test = ["check-manifest", "pep440", "pre-commit", "pytest", "testpath"] + +[[package]] +name = "ncompress" +version = "1.0.0" +description = "LZW compression and decompression" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "nest-asyncio" +version = "1.5.6" +description = "Patch asyncio to allow nested event loops" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "networkx" +version = "2.8.8" +description = "Python package for creating and manipulating graphs and networks" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.extras] +default = ["matplotlib (>=3.4)", "numpy (>=1.19)", "pandas (>=1.3)", "scipy (>=1.8)"] +developer = ["mypy (>=0.982)", "pre-commit (>=2.20)"] +doc = ["nb2plots (>=0.6)", "numpydoc (>=1.5)", "pillow (>=9.2)", "pydata-sphinx-theme (>=0.11)", "sphinx (>=5.2)", "sphinx-gallery (>=0.11)", "texext (>=0.6.6)"] +extra = ["lxml (>=4.6)", "pydot (>=1.4.2)", "pygraphviz (>=1.9)", "sympy (>=1.10)"] +test = ["codecov (>=2.1)", "pytest (>=7.2)", "pytest-cov (>=4.0)"] + +[[package]] +name = "nodeenv" +version = "1.7.0" +description = "Node.js virtual environment builder" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "nose" +version = "1.3.7" +description = "nose extends unittest to make testing easier" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "notebook" +version = "6.4.12" +description = "A web-based notebook environment for interactive computing" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +argon2-cffi = "*" +ipykernel = "*" +ipython-genutils = "*" +jinja2 = "*" +jupyter-client = ">=5.3.4" +jupyter-core = ">=4.6.1" +nbconvert = ">=5" +nbformat = "*" +nest-asyncio = ">=1.5" +prometheus-client = "*" +pyzmq = ">=17" +Send2Trash = ">=1.8.0" +terminado = ">=0.8.3" +tornado = ">=6.1" +traitlets = ">=4.2.1" + +[package.extras] +docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] +json-logging = ["json-logging"] +test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium", "testpath"] + +[[package]] +name = "notebook-shim" +version = "0.2.0" +description = "A shim layer for notebook traits and config" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +jupyter-server = ">=1.8,<3" + +[package.extras] +test = ["pytest", "pytest-console-scripts", "pytest-tornasync"] + +[[package]] +name = "numpy" +version = "1.23.4" +description = "NumPy is the fundamental package for array computing with Python." +category = "main" +optional = false +python-versions = ">=3.8" + +[[package]] +name = "nvidia-ml-py3" +version = "7.352.0" +description = "Python Bindings for the NVIDIA Management Library" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "omegaconf" +version = "2.3.0" +description = "A flexible configuration library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +antlr4-python3-runtime = ">=4.9.0,<4.10.0" +PyYAML = ">=5.1.0" + +[[package]] +name = "onnx" +version = "1.12.0" +description = "Open Neural Network Exchange" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.16.6" +protobuf = ">=3.12.2,<=3.20.1" +typing-extensions = ">=3.6.2.1" + +[package.extras] +lint = ["clang-format (==13.0.0)", "flake8", "mypy (==0.782)", "types-protobuf (==3.18.4)"] + +[[package]] +name = "onnx2torch" +version = "1.5.4" +description = "Nice Onnx to Pytorch converter" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.16.4" +onnx = ">=1.9.0" +torch = ">=1.8.0" +torchvision = ">=0.9.0" + +[[package]] +name = "onnxoptimizer" +version = "0.3.1" +description = "Open Neural Network Exchange" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +onnx = "*" + +[package.extras] +mypy = ["mypy (==0.600)"] + +[[package]] +name = "onnxruntime-gpu" +version = "1.12.1" +description = "ONNX Runtime is a runtime accelerator for Machine Learning models" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +coloredlogs = "*" +flatbuffers = "*" +numpy = ">=1.21.0" +packaging = "*" +protobuf = "*" +sympy = "*" + +[[package]] +name = "opencv-python-headless" +version = "4.5.5.64" +description = "Wrapper package for OpenCV python bindings." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +numpy = [ + {version = ">=1.21.2", markers = "python_version >= \"3.6\" and platform_system == \"Darwin\" and platform_machine == \"arm64\""}, + {version = ">=1.19.3", markers = "python_version >= \"3.6\" and platform_system == \"Linux\" and platform_machine == \"aarch64\""}, + {version = ">=1.14.5", markers = "python_version >= \"3.7\""}, + {version = ">=1.17.3", markers = "python_version >= \"3.8\""}, +] + +[package.source] +type = "url" +url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl" + +[[package]] +name = "osmium" +version = "3.4.1" +description = "Python bindings for libosmium, the data processing library for OSM data" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +requests = "*" + +[[package]] +name = "osmnx" +version = "1.2.2" +description = "Retrieve, model, analyze, and visualize OpenStreetMap street networks and other spatial data" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +geopandas = ">=0.11" +matplotlib = ">=3.5" +networkx = ">=2.8" +numpy = ">=1.22" +pandas = ">=1.4" +pyproj = ">=3.3" +requests = ">=2.28" +Rtree = ">=1.0" +Shapely = ">=1.8,<2.0" + +[package.extras] +entropy = ["scipy"] +nearest-neighbor = ["scikit-learn", "scipy"] +raster = ["gdal", "rasterio"] +web-map = ["folium"] + +[[package]] +name = "packaging" +version = "21.3" +description = "Core utilities for Python packages" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" + +[[package]] +name = "pandas" +version = "1.5.1" +description = "Powerful data structures for data analysis, time series, and statistics" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = {version = ">=1.20.3", markers = "python_version < \"3.10\""} +python-dateutil = ">=2.8.1" +pytz = ">=2020.1" + +[package.extras] +test = ["hypothesis (>=5.5.3)", "pytest (>=6.0)", "pytest-xdist (>=1.31)"] + +[[package]] +name = "pandocfilters" +version = "1.5.0" +description = "Utilities for writing pandoc filters in python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "parameterized" +version = "0.8.1" +description = "Parameterized testing with any Python test framework" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +dev = ["jinja2"] + +[[package]] +name = "paramiko" +version = "2.11.0" +description = "SSH2 protocol library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +bcrypt = ">=3.1.3" +cryptography = ">=2.5" +pynacl = ">=1.0.1" +six = "*" + +[package.extras] +all = ["bcrypt (>=3.1.3)", "gssapi (>=1.4.1)", "invoke (>=1.3)", "pyasn1 (>=0.1.7)", "pynacl (>=1.0.1)", "pywin32 (>=2.1.8)"] +ed25519 = ["bcrypt (>=3.1.3)", "pynacl (>=1.0.1)"] +gssapi = ["gssapi (>=1.4.1)", "pyasn1 (>=0.1.7)", "pywin32 (>=2.1.8)"] +invoke = ["invoke (>=1.3)"] + +[[package]] +name = "parso" +version = "0.8.3" +description = "A Python Parser" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] +testing = ["docopt", "pytest (<6.0.0)"] + +[[package]] +name = "pexpect" +version = "4.8.0" +description = "Pexpect allows easy control of interactive console applications." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pickleshare" +version = "0.7.5" +description = "Tiny 'shelve'-like database with concurrency support" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pillow" +version = "9.2.0" +description = "Python Imaging Library (Fork)" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-issues (>=3.0.1)", "sphinx-removed-in", "sphinxext-opengraph"] +tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] + +[[package]] +name = "pillow-avif-plugin" +version = "1.2.2" +description = "A pillow plugin that adds avif support via libavif" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pipenv" +version = "2022.10.12" +description = "Python Development Workflow for Humans." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +certifi = "*" +setuptools = ">=36.2.1" +virtualenv = "*" +virtualenv-clone = ">=0.2.5" + +[package.extras] +dev = ["black", "bs4", "flake8 (>=3.3.0,<4.0)", "invoke", "parver", "sphinx", "towncrier"] +tests = ["flaky", "mock", "pytest (>=5.0)", "pytest-timeout", "pytest-xdist"] + +[[package]] +name = "pkginfo" +version = "1.8.3" +description = "Query metadatdata from sdists / bdists / installed packages." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.extras] +testing = ["coverage", "nose"] + +[[package]] +name = "pkgutil-resolve-name" +version = "1.3.10" +description = "Resolve a name to an object." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx (>=4)", "sphinx-autodoc-typehints (>=1.12)"] +test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] + +[[package]] +name = "plotly" +version = "5.10.0" +description = "An open-source, interactive data visualization library for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +tenacity = ">=6.2.0" + +[[package]] +name = "pluggy" +version = "1.0.0" +description = "plugin and hook calling mechanisms for python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry" +version = "1.2.2" +description = "Python dependency management and packaging made easy." +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +cachecontrol = {version = ">=0.12.9,<0.13.0", extras = ["filecache"]} +cachy = ">=0.3.0,<0.4.0" +cleo = ">=1.0.0a5,<2.0.0" +crashtest = ">=0.3.0,<0.4.0" +dulwich = ">=0.20.46,<0.21.0" +html5lib = ">=1.0,<2.0" +importlib-metadata = {version = ">=4.4,<5.0", markers = "python_version < \"3.10\""} +jsonschema = ">=4.10.0,<5.0.0" +keyring = ">=21.2.0" +packaging = ">=20.4" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.5,<2.0" +platformdirs = ">=2.5.2,<3.0.0" +poetry-core = "1.3.2" +poetry-plugin-export = ">=1.1.2,<2.0.0" +requests = ">=2.18,<3.0" +requests-toolbelt = ">=0.9.1,<0.10.0" +shellingham = ">=1.5,<2.0" +tomlkit = ">=0.11.1,<0.11.2 || >0.11.2,<0.11.3 || >0.11.3,<1.0.0" +urllib3 = ">=1.26.0,<2.0.0" +virtualenv = ">=20.4.3,<20.4.5 || >20.4.5,<20.4.6 || >20.4.6" +xattr = {version = ">=0.9.7,<0.10.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.3.2" +description = "Poetry PEP 517 Build Backend" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[[package]] +name = "poetry-plugin-export" +version = "1.1.2" +description = "Poetry plugin to export the dependencies to various formats" +category = "main" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +poetry = ">=1.2.0,<2.0.0" +poetry-core = ">=1.1.0,<2.0.0" + +[[package]] +name = "polyline" +version = "1.4.0" +description = "A Python implementation of Google's Encoded Polyline Algorithm Format." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +six = ">=1.8.0" + +[[package]] +name = "portalocker" +version = "2.6.0" +description = "Wraps the portalocker recipe for easy usage" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.dependencies] +pywin32 = {version = ">=226", markers = "platform_system == \"Windows\""} + +[package.extras] +docs = ["sphinx (>=1.7.1)"] +redis = ["redis"] +tests = ["pytest (>=5.4.1)", "pytest-cov (>=2.8.1)", "pytest-mypy (>=0.8.0)", "pytest-timeout (>=2.1.0)", "redis", "sphinx (>=3.0.3)"] + +[[package]] +name = "pprofile" +version = "2.1.0" +description = "Line-granularity, thread-aware deterministic and statistic pure-python profiler" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pre-commit" +version = "2.20.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +toml = "*" +virtualenv = ">=20.0.8" + +[[package]] +name = "pretrainedmodels" +version = "0.7.4" +description = "Pretrained models for Pytorch" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +munch = "*" +torch = "*" +torchvision = "*" +tqdm = "*" + +[[package]] +name = "prometheus-client" +version = "0.15.0" +description = "Python client for the Prometheus monitoring system." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +twisted = ["twisted"] + +[[package]] +name = "prompt-toolkit" +version = "3.0.31" +description = "Library for building powerful interactive command lines in Python" +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +wcwidth = "*" + +[[package]] +name = "protobuf" +version = "3.20.1" +description = "Protocol Buffers" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "psutil" +version = "5.9.3" +description = "Cross-platform lib for process and system monitoring in Python." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.extras] +test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pure-eval" +version = "0.2.2" +description = "Safely evaluate AST nodes without side effects" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +tests = ["pytest"] + +[[package]] +name = "py" +version = "1.11.0" +description = "library with cross-python path, ini-parsing, io, code, log facilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycapnp" +version = "1.1.0" +description = "A cython wrapping of the C++ Cap'n Proto library" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pycodestyle" +version = "2.8.0" +description = "Python style guide checker" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycparser" +version = "2.21" +description = "C parser in Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pycryptodome" +version = "3.15.0" +description = "Cryptographic library for Python" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "pycuda" +version = "2022.1" +description = "Python wrapper for Nvidia CUDA" +category = "dev" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +appdirs = ">=1.4.0" +mako = "*" +pytools = ">=2011.2" + +[[package]] +name = "pycurl" +version = "7.45.1" +description = "PycURL -- A Python Interface To The cURL library" +category = "dev" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "pyflakes" +version = "2.4.0" +description = "passive checker of Python programs" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pygame" +version = "2.1.2" +description = "Python Game Development" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pygments" +version = "2.13.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +plugins = ["importlib-metadata"] + +[[package]] +name = "pyjwt" +version = "2.6.0" +description = "JSON Web Token implementation in Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +cryptography = {version = ">=3.4.0", optional = true, markers = "extra == \"crypto\""} + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + +[[package]] +name = "pylev" +version = "1.4.0" +description = "A pure Python Levenshtein implementation that's not freaking GPL'd." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pylint" +version = "2.15.4" +description = "python code static checker" +category = "main" +optional = false +python-versions = ">=3.7.2" + +[package.dependencies] +astroid = ">=2.12.11,<=2.14.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = ">=0.2" +isort = ">=4.2.5,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pymsalruntime" +version = "0.11.2" +description = "The MSALRuntime Python Interop Package" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pymysql" +version = "0.9.3" +description = "Pure Python MySQL Driver" +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +rsa = ["cryptography"] + +[[package]] +name = "pynacl" +version = "1.5.0" +description = "Python binding to the Networking and Cryptography (NaCl) library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = ">=1.4.1" + +[package.extras] +docs = ["sphinx (>=1.6.5)", "sphinx_rtd_theme"] +tests = ["hypothesis (>=3.27.0)", "pytest (>=3.2.1,!=3.3.0)"] + +[[package]] +name = "pyopencl" +version = "2022.2.4" +description = "Python wrapper for OpenCL" +category = "main" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +numpy = "*" +platformdirs = ">=2.2.0" +pytools = ">=2021.2.7" + +[package.extras] +oclgrind = ["oclgrind-binary-distribution (>=18.3)"] +pocl = ["pocl-binary-distribution (>=1.2)"] +test = ["Mako", "pytest (>=7.0.0)"] + +[[package]] +name = "pyopenssl" +version = "22.0.0" +description = "Python wrapper module around the OpenSSL library" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=35.0" + +[package.extras] +docs = ["sphinx", "sphinx-rtd-theme"] +test = ["flaky", "pretend", "pytest (>=3.0.1)"] + +[[package]] +name = "pyparsing" +version = "3.0.9" +description = "pyparsing module - Classes and methods to define and execute parsing grammars" +category = "main" +optional = false +python-versions = ">=3.6.8" + +[package.extras] +diagrams = ["jinja2", "railroad-diagrams"] + +[[package]] +name = "pyprof2calltree" +version = "1.4.5" +description = "Help visualize profiling data from cProfile with kcachegrind and qcachegrind" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pyproj" +version = "3.4.0" +description = "Python interface to PROJ (cartographic projections and coordinate transformations library)" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +certifi = "*" + +[[package]] +name = "pyreadline3" +version = "3.4.1" +description = "A python implementation of GNU readline." +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pyrsistent" +version = "0.18.1" +description = "Persistent/Functional/Immutable data structures" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pyserial" +version = "3.5" +description = "Python Serial Port Extension" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +cp2110 = ["hidapi"] + +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "pytest" +version = "7.1.3" +description = "pytest: simple powerful testing with Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +attrs = ">=19.2.0" +colorama = {version = "*", markers = "sys_platform == \"win32\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=0.12,<2.0" +py = ">=1.8.2" +tomli = ">=1.0.0" + +[package.extras] +testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] + +[[package]] +name = "pytest-forked" +version = "1.4.0" +description = "run tests in isolated forked subprocesses" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +py = "*" +pytest = ">=3.10" + +[[package]] +name = "pytest-xdist" +version = "2.5.0" +description = "pytest xdist plugin for distributed testing and loop-on-failing modes" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +execnet = ">=1.1" +pytest = ">=6.2.0" +pytest-forked = "*" + +[package.extras] +psutil = ["psutil (>=3.0)"] +setproctitle = ["setproctitle"] +testing = ["filelock"] + +[[package]] +name = "python-dateutil" +version = "2.8.2" +description = "Extensions to the standard Python datetime module" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "python-engineio" +version = "4.3.4" +description = "Engine.IO server and client for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "python-logstash" +version = "0.4.8" +description = "Python logging handler for Logstash." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "python-rapidjson" +version = "1.10" +description = "Python wrapper around rapidjson" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "python-socketio" +version = "5.7.2" +description = "Socket.IO server and client for Python" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +bidict = ">=0.21.0" +python-engineio = ">=4.3.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] + +[[package]] +name = "pytools" +version = "2022.1.12" +description = "A collection of tools for Python" +category = "main" +optional = false +python-versions = "~=3.6" + +[package.dependencies] +platformdirs = ">=2.2.0" +typing_extensions = {version = ">=4.0", markers = "python_version < \"3.11\""} + +[package.extras] +numpy = ["numpy (>=1.6.0)"] + +[[package]] +name = "pytz" +version = "2022.5" +description = "World timezone definitions, modern and historical" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pywavelets" +version = "1.4.1" +description = "PyWavelets, wavelet transform module" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = ">=1.17.3" + +[[package]] +name = "pywin32" +version = "304" +description = "Python for Window Extensions" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pywin32-ctypes" +version = "0.2.0" +description = "" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "pywinpty" +version = "2.0.8" +description = "Pseudo terminal support for Windows from Python." +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "pyyaml" +version = "6.0" +description = "YAML parser and emitter for Python" +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "pyzmq" +version = "23.2.1" +description = "Python bindings for 0MQ" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cffi = {version = "*", markers = "implementation_name == \"pypy\""} +py = {version = "*", markers = "implementation_name == \"pypy\""} + +[[package]] +name = "qtconsole" +version = "5.3.2" +description = "Jupyter Qt console" +category = "dev" +optional = false +python-versions = ">= 3.7" + +[package.dependencies] +ipykernel = ">=4.1" +ipython-genutils = "*" +jupyter-client = ">=4.1" +jupyter-core = "*" +pygments = "*" +pyzmq = ">=17.1" +qtpy = ">=2.0.1" +traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" + +[package.extras] +doc = ["Sphinx (>=1.3)"] +test = ["flaky", "pytest", "pytest-qt"] + +[[package]] +name = "qtpy" +version = "2.2.1" +description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = "*" + +[package.extras] +test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] + +[[package]] +name = "qudida" +version = "0.0.4" +description = "QUick and DIrty Domain Adaptation" +category = "dev" +optional = false +python-versions = ">=3.5.0" + +[package.dependencies] +numpy = ">=0.18.0" +opencv-python-headless = ">=4.0.1" +scikit-learn = ">=0.19.1" +typing-extensions = "*" + +[[package]] +name = "reactivex" +version = "4.0.4" +description = "ReactiveX (Rx) for Python" +category = "dev" +optional = false +python-versions = ">=3.7,<4.0" + +[package.dependencies] +typing-extensions = ">=4.1.1,<5.0.0" + +[[package]] +name = "redis" +version = "4.3.4" +description = "Python client for Redis database and key-value store" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +async-timeout = ">=4.0.2" +deprecated = ">=1.2.3" +packaging = ">=20.4" + +[package.extras] +hiredis = ["hiredis (>=1.0.0)"] +ocsp = ["cryptography (>=36.0.1)", "pyopenssl (==20.0.1)", "requests (>=2.26.0)"] + +[[package]] +name = "requests" +version = "2.28.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=3.7, <4" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<3" +idna = ">=2.5,<4" +PySocks = {version = ">=1.5.6,<1.5.7 || >1.5.7", optional = true, markers = "extra == \"socks\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "1.3.1" +description = "OAuthlib authentication support for Requests." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "requests-toolbelt" +version = "0.9.1" +description = "A utility belt for advanced users of python-requests" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "reverse-geocoder" +version = "1.5.1" +description = "Fast, offline reverse geocoder" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +numpy = ">=1.11.0" +scipy = ">=0.17.1" + +[[package]] +name = "rtree" +version = "1.0.1" +description = "R-Tree spatial index for Python GIS" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "s2sphere" +version = "0.2.5" +description = "Python implementation of the S2 Geometry Library" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +future = ">=0.15" + +[package.extras] +docs = ["Sphinx (>=1.6.5)", "sphinx-rtd-theme (>=0.1.9)"] +tests = ["flake8 (>=2.5.4)", "hacking (>=0.11.0)", "nose (>=1.3.4)", "numpy (>=1.11.0)"] + +[[package]] +name = "scikit-image" +version = "0.19.3" +description = "Image processing in Python" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +imageio = ">=2.4.1" +networkx = ">=2.2" +numpy = ">=1.17.0" +packaging = ">=20.0" +pillow = ">=6.1.0,<7.1.0 || >7.1.0,<7.1.1 || >7.1.1,<8.3.0 || >8.3.0" +PyWavelets = ">=1.1.1" +scipy = ">=1.4.1" +tifffile = ">=2019.7.26" + +[package.extras] +data = ["pooch (>=1.3.0)"] +docs = ["cloudpickle (>=0.2.1)", "dask[array] (>=0.15.0,!=2.17.0)", "ipywidgets", "kaleido", "matplotlib (>=3.3)", "myst-parser", "numpydoc (>=1.0)", "pandas (>=0.23.0)", "plotly (>=4.14.0)", "pooch (>=1.3.0)", "pytest-runner", "scikit-learn", "seaborn (>=0.7.1)", "sphinx (>=1.8)", "sphinx-copybutton", "sphinx-gallery (>=0.10.1)", "tifffile (>=2020.5.30)"] +optional = ["SimpleITK", "astropy (>=3.1.2)", "cloudpickle (>=0.2.1)", "dask[array] (>=1.0.0,!=2.17.0)", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pyamg", "qtpy"] +test = ["asv", "codecov", "flake8", "matplotlib (>=3.0.3)", "pooch (>=1.3.0)", "pytest (>=5.2.0)", "pytest-cov (>=2.7.0)", "pytest-faulthandler", "pytest-localserver"] + +[[package]] +name = "scikit-learn" +version = "1.1.2" +description = "A set of python modules for machine learning and data mining" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +joblib = ">=1.0.0" +numpy = ">=1.17.3" +scipy = ">=1.3.2" +threadpoolctl = ">=2.0.0" + +[package.extras] +benchmark = ["matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "pandas (>=1.0.5)"] +docs = ["Pillow (>=7.1.2)", "matplotlib (>=3.1.2)", "memory-profiler (>=0.57.0)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)", "sphinx (>=4.0.1)", "sphinx-gallery (>=0.7.0)", "sphinx-prompt (>=1.3.0)", "sphinxext-opengraph (>=0.4.2)"] +examples = ["matplotlib (>=3.1.2)", "pandas (>=1.0.5)", "scikit-image (>=0.16.2)", "seaborn (>=0.9.0)"] +tests = ["black (>=22.3.0)", "flake8 (>=3.8.2)", "matplotlib (>=3.1.2)", "mypy (>=0.961)", "numpydoc (>=1.2.0)", "pandas (>=1.0.5)", "pyamg (>=4.0.0)", "pytest (>=5.0.1)", "pytest-cov (>=2.9.0)", "scikit-image (>=0.16.2)"] + +[[package]] +name = "scipy" +version = "1.9.3" +description = "Fundamental algorithms for scientific computing in Python" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = ">=1.18.5,<1.26.0" + +[package.extras] +dev = ["flake8", "mypy", "pycodestyle", "typing_extensions"] +doc = ["matplotlib (>2)", "numpydoc", "pydata-sphinx-theme (==0.9.0)", "sphinx (!=4.1.0)", "sphinx-panels (>=0.5.2)", "sphinx-tabs"] +test = ["asv", "gmpy2", "mpmath", "pytest", "pytest-cov", "pytest-xdist", "scikit-umfpack", "threadpoolctl"] + +[[package]] +name = "scons" +version = "4.4.0" +description = "Open Source next-generation build tool." +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "segmentation-models-pytorch" +version = "0.2.1" +description = "Image segmentation models with pre-trained backbones. PyTorch." +category = "dev" +optional = false +python-versions = ">=3.0.0" + +[package.dependencies] +efficientnet-pytorch = "0.6.3" +pretrainedmodels = "0.7.4" +timm = "0.4.12" +torchvision = ">=0.5.0" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "send2trash" +version = "1.8.0" +description = "Send file to trash natively under Mac OS X, Windows and Linux." +category = "dev" +optional = false +python-versions = "*" + +[package.extras] +nativelib = ["pyobjc-framework-Cocoa", "pywin32"] +objc = ["pyobjc-framework-Cocoa"] +win32 = ["pywin32"] + +[[package]] +name = "sentry-sdk" +version = "1.10.0" +description = "Python client for Sentry (https://sentry.io)" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +certifi = "*" +urllib3 = {version = ">=1.26.11", markers = "python_version >= \"3.6\""} + +[package.extras] +aiohttp = ["aiohttp (>=3.5)"] +beam = ["apache-beam (>=2.12)"] +bottle = ["bottle (>=0.12.13)"] +celery = ["celery (>=3)"] +chalice = ["chalice (>=1.16.0)"] +django = ["django (>=1.8)"] +falcon = ["falcon (>=1.4)"] +fastapi = ["fastapi (>=0.79.0)"] +flask = ["blinker (>=1.1)", "flask (>=0.11)"] +httpx = ["httpx (>=0.16.0)"] +pure-eval = ["asttokens", "executing", "pure-eval"] +pyspark = ["pyspark (>=2.4.4)"] +quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] +rq = ["rq (>=0.6)"] +sanic = ["sanic (>=0.8)"] +sqlalchemy = ["sqlalchemy (>=1.2)"] +starlette = ["starlette (>=0.19.1)"] +tornado = ["tornado (>=5)"] + +[[package]] +name = "setproctitle" +version = "1.3.2" +description = "A Python module to customize the process title" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "setuptools" +version = "65.5.0" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8 (<5)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mock", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "setuptools-scm" +version = "7.0.5" +description = "the blessed package to manage your versions by scm tags" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +packaging = ">=20.0" +setuptools = "*" +tomli = ">=1.0.0" +typing-extensions = "*" + +[package.extras] +test = ["pytest (>=6.2)", "virtualenv (>20)"] +toml = ["setuptools (>=42)"] + +[[package]] +name = "shapely" +version = "1.8.5.post1" +description = "Geometric objects, predicates, and operations" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +all = ["numpy", "pytest", "pytest-cov"] +test = ["pytest", "pytest-cov"] +vectorized = ["numpy"] + +[[package]] +name = "shellingham" +version = "1.5.0" +description = "Tool to Detect Surrounding Shell" +category = "main" +optional = false +python-versions = ">=3.4" + +[[package]] +name = "simplejson" +version = "3.17.6" +description = "Simple, fast, extensible JSON encoder/decoder for Python" +category = "dev" +optional = false +python-versions = ">=2.5, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "smbus2" +version = "0.4.2" +description = "smbus2 is a drop-in replacement for smbus-cffi/smbus-python in pure Python" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +docs = ["sphinx (>=1.5.3)"] +qa = ["flake8"] +test = ["mock", "nose"] + +[[package]] +name = "sniffio" +version = "1.3.0" +description = "Sniff out which async library your code is running under" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "sounddevice" +version = "0.4.5" +description = "Play and Record Sound with Python" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +CFFI = ">=1.0" + +[package.extras] +numpy = ["NumPy"] + +[[package]] +name = "soupsieve" +version = "2.3.2.post1" +description = "A modern CSS selector implementation for Beautiful Soup." +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "sphinx" +version = "5.3.0" +description = "Python documentation generator" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.14,<0.20" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.12" +requests = ">=2.5.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-bugbear", "flake8-comprehensions", "flake8-simplify", "isort", "mypy (>=0.981)", "sphinx-lint", "types-requests", "types-typed-ast"] +test = ["cython", "html5lib", "pytest (>=4.6)", "typed_ast"] + +[[package]] +name = "sphinx-rtd-theme" +version = "1.0.0" +description = "Read the Docs theme for Sphinx" +category = "dev" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*" + +[package.dependencies] +docutils = "<0.18" +sphinx = ">=1.6" + +[package.extras] +dev = ["bump2version", "sphinxcontrib-httpdomain", "transifex-client"] + +[[package]] +name = "sphinx-sitemap" +version = "2.2.0" +description = "Sitemap generator for Sphinx" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +six = "*" +sphinx = ">=1.2" + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.2" +description = "sphinxcontrib-applehelp is a sphinx extension which outputs Apple help books" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.0" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +category = "dev" +optional = false +python-versions = ">=3.5" + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "spidev" +version = "3.6" +description = "Python bindings for Linux SPI access through spidev" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "spidev2" +version = "0.9.0" +description = "Pure-python interface to Linux spidev." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +ioctl-opt = "*" + +[[package]] +name = "sqlalchemy" +version = "1.4.42" +description = "Database Abstraction Library" +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" + +[package.dependencies] +greenlet = {version = "!=0.4.17", markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\")"} + +[package.extras] +aiomysql = ["aiomysql", "greenlet (!=0.4.17)"] +aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"] +asyncio = ["greenlet (!=0.4.17)"] +asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"] +mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"] +mssql = ["pyodbc"] +mssql-pymssql = ["pymssql"] +mssql-pyodbc = ["pyodbc"] +mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"] +mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"] +mysql-connector = ["mysql-connector-python"] +oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"] +postgresql = ["psycopg2 (>=2.7)"] +postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"] +postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"] +postgresql-psycopg2binary = ["psycopg2-binary"] +postgresql-psycopg2cffi = ["psycopg2cffi"] +pymysql = ["pymysql", "pymysql (<1)"] +sqlcipher = ["sqlcipher3_binary"] + +[[package]] +name = "stack-data" +version = "0.5.1" +description = "Extract data from python stack frames and tracebacks for informative displays" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +asttokens = "*" +executing = "*" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + +[[package]] +name = "subprocess32" +version = "3.5.4" +description = "A backport of the subprocess module from Python 3 for use on 2.x." +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*, <4" + +[[package]] +name = "sympy" +version = "1.11.1" +description = "Computer algebra system (CAS) in Python" +category = "main" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +mpmath = ">=0.19" + +[[package]] +name = "tabulate" +version = "0.8.10" +description = "Pretty-print tabular data" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tenacity" +version = "8.1.0" +description = "Retry code until it succeeds" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.extras] +doc = ["reno", "sphinx", "tornado (>=4.5)"] + +[[package]] +name = "terminado" +version = "0.16.0" +description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +ptyprocess = {version = "*", markers = "os_name != \"nt\""} +pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} +tornado = ">=6.1.0" + +[package.extras] +test = ["pre-commit", "pytest (>=6.0)", "pytest-timeout"] + +[[package]] +name = "threadpoolctl" +version = "3.1.0" +description = "threadpoolctl" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "tifffile" +version = "2022.10.10" +description = "Read and write TIFF files" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = ">=1.19.2" + +[package.extras] +all = ["fsspec", "imagecodecs (>=2022.2.22)", "lxml", "matplotlib (>=3.3)", "zarr"] + +[[package]] +name = "timezonefinder" +version = "6.1.3" +description = "fast python package for finding the timezone of any point on earth (coordinates) offline" +category = "main" +optional = false +python-versions = ">=3.8,<3.11" + +[package.dependencies] +cffi = ">=1.15.1,<2.0.0" +h3 = ">=3.7.3,<4.0.0" +numpy = ">=1.22,<2.0" +setuptools = ">=65.3.0,<66.0.0" + +[package.extras] +numba = ["numba (>=0.55.2,<0.56.0)"] + +[[package]] +name = "timm" +version = "0.4.12" +description = "(Unofficial) PyTorch Image Models" +category = "dev" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +torch = ">=1.4" +torchvision = "*" + +[[package]] +name = "tinycss2" +version = "1.2.1" +description = "A tiny CSS parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +webencodings = ">=0.4" + +[package.extras] +doc = ["sphinx", "sphinx_rtd_theme"] +test = ["flake8", "isort", "pytest"] + +[[package]] +name = "toml" +version = "0.10.2" +description = "Python Library for Tom's Obvious, Minimal Language" +category = "dev" +optional = false +python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "tomlkit" +version = "0.11.5" +description = "Style preserving TOML library" +category = "main" +optional = false +python-versions = ">=3.6,<4.0" + +[[package]] +name = "torch" +version = "2.0.0+cu118" +description = "Tensors and Dynamic neural networks in Python with strong GPU acceleration" +category = "dev" +optional = false +python-versions = ">=3.8.0" + +[package.dependencies] +filelock = "*" +jinja2 = "*" +networkx = "*" +sympy = "*" +triton = {version = "2.0.0", markers = "platform_system == \"Linux\" and platform_machine == \"x86_64\""} +typing-extensions = "*" + +[package.extras] +opt-einsum = ["opt-einsum (>=3.3)"] + +[package.source] +type = "url" +url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" + +[[package]] +name = "torchsummary" +version = "1.5.1" +description = "Model summary in PyTorch similar to `model.summary()` in Keras" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "torchvision" +version = "0.15.1+cu118" +description = "image and video datasets and models for torch deep learning" +category = "dev" +optional = false +python-versions = ">=3.8" + +[package.dependencies] +numpy = "*" +pillow = ">=5.3.0,<8.3.0 || >=8.4.0" +requests = "*" +torch = "2.0.0" + +[package.extras] +scipy = ["scipy"] + +[package.source] +type = "url" +url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" + +[[package]] +name = "tornado" +version = "6.2" +description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." +category = "dev" +optional = false +python-versions = ">= 3.7" + +[[package]] +name = "tqdm" +version = "4.64.1" +description = "Fast, Extensible Progress Meter" +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[package.extras] +dev = ["py-make (>=0.1.0)", "twine", "wheel"] +notebook = ["ipywidgets (>=6)"] +slack = ["slack-sdk"] +telegram = ["requests"] + +[[package]] +name = "traitlets" +version = "5.5.0" +description = "" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] +test = ["pre-commit", "pytest"] + +[[package]] +name = "triton" +version = "2.0.0" +description = "A language and compiler for custom Deep Learning operations" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +cmake = "*" +filelock = "*" +lit = "*" +torch = "*" + +[package.extras] +tests = ["autopep8", "flake8", "isort", "numpy", "pytest", "scipy (>=1.7.1)"] +tutorials = ["matplotlib", "pandas", "tabulate"] + +[[package]] +name = "tritonclient" +version = "2.28.0" +description = "Python client library and utilities for communicating with Triton Inference Server" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +aiohttp = {version = ">=3.8.1", optional = true, markers = "extra == \"http\""} +geventhttpclient = {version = ">=1.4.4,<=2.0.2", optional = true, markers = "extra == \"http\""} +numpy = ">=1.19.1" +python-rapidjson = ">=0.9.1" + +[package.extras] +all = ["aiohttp (>=3.8.1)", "geventhttpclient (>=1.4.4,<=2.0.2)", "grpcio (==1.41.0)", "numpy (>=1.19.1)", "protobuf (>=3.5.0,<3.20)", "python-rapidjson (>=0.9.1)"] +grpc = ["grpcio (==1.41.0)", "numpy (>=1.19.1)", "protobuf (>=3.5.0,<3.20)", "python-rapidjson (>=0.9.1)"] +http = ["aiohttp (>=3.8.1)", "geventhttpclient (>=1.4.4,<=2.0.2)", "numpy (>=1.19.1)", "python-rapidjson (>=0.9.1)"] + +[[package]] +name = "types-atomicwrites" +version = "1.4.5.1" +description = "Typing stubs for atomicwrites" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-certifi" +version = "2021.10.8.3" +description = "Typing stubs for certifi" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-pycurl" +version = "7.45.1" +description = "Typing stubs for pycurl" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-pyyaml" +version = "6.0.12" +description = "Typing stubs for PyYAML" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-requests" +version = "2.28.11.2" +description = "Typing stubs for requests" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +types-urllib3 = "<1.27" + +[[package]] +name = "types-tabulate" +version = "0.8.11" +description = "Typing stubs for tabulate" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "types-urllib3" +version = "1.26.25.1" +description = "Typing stubs for urllib3" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "typing-extensions" +version = "4.4.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "main" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.12" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, <4" + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] +secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "utm" +version = "0.7.0" +description = "Bidirectional UTM-WGS84 converter for python" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "virtualenv" +version = "20.16.5" +description = "Virtual Python Environment builder" +category = "main" +optional = false +python-versions = ">=3.6" + +[package.dependencies] +distlib = ">=0.3.5,<1" +filelock = ">=3.4.1,<4" +platformdirs = ">=2.4,<3" + +[package.extras] +docs = ["proselint (>=0.13)", "sphinx (>=5.1.1)", "sphinx-argparse (>=0.3.1)", "sphinx-rtd-theme (>=1)", "towncrier (>=21.9)"] +testing = ["coverage (>=6.2)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=21.3)", "pytest (>=7.0.1)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.2)", "pytest-mock (>=3.6.1)", "pytest-randomly (>=3.10.3)", "pytest-timeout (>=2.1)"] + +[[package]] +name = "virtualenv-clone" +version = "0.5.7" +description = "script to clone virtualenvs." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" + +[[package]] +name = "wcwidth" +version = "0.2.5" +description = "Measures the displayed width of unicode strings in a terminal" +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "webencodings" +version = "0.5.1" +description = "Character encoding aliases for legacy web content" +category = "main" +optional = false +python-versions = "*" + +[[package]] +name = "websocket-client" +version = "1.4.1" +description = "WebSocket client for Python with low level API options" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "werkzeug" +version = "2.2.2" +description = "The comprehensive WSGI web application library." +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +MarkupSafe = ">=2.1.1" + +[package.extras] +watchdog = ["watchdog"] + +[[package]] +name = "widgetsnbextension" +version = "4.0.3" +description = "Jupyter interactive widgets for Jupyter Notebook" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "wrapt" +version = "1.14.1" +description = "Module for decorators, wrappers and monkey patching." +category = "main" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "xattr" +version = "0.9.9" +description = "Python wrapper for extended filesystem attributes" +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +cffi = ">=1.0" + +[[package]] +name = "yarl" +version = "1.8.1" +description = "Yet another URL library" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" + +[[package]] +name = "zerorpc" +version = "0.6.3" +description = "zerorpc is a flexible RPC based on zeromq." +category = "dev" +optional = false +python-versions = "*" +develop = false + +[package.dependencies] +future = "*" +gevent = ">=1.1" +msgpack = ">=0.5.2" +msgpack-numpy = ">=0.4.3" +pyzmq = ">=13.1.0" + +[package.source] +type = "git" +url = "https://github.com/commaai/zerorpc-python.git" +reference = "master" +resolved_reference = "0e27fd795bb9a7ec9cab81a771a2e35a91e397c9" + +[[package]] +name = "zipp" +version = "3.9.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)"] +testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] + +[[package]] +name = "zope-event" +version = "4.5.0" +description = "Very basic event publishing system" +category = "dev" +optional = false +python-versions = "*" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx"] +test = ["zope.testrunner"] + +[[package]] +name = "zope-interface" +version = "5.5.0" +description = "Interfaces for Python" +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[package.dependencies] +setuptools = "*" + +[package.extras] +docs = ["Sphinx", "repoze.sphinx.autointerface"] +test = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] +testing = ["coverage (>=5.0.3)", "zope.event", "zope.testing"] + +[metadata] +lock-version = "1.1" +python-versions = "~3.8" +content-hash = "b7cd75dfa0dcddff224696ccc7f41d87aac64652f744ab386321c1eee920fbe9" + +[metadata.files] +adal = [ + {file = "adal-1.2.7-py2.py3-none-any.whl", hash = "sha256:2a7451ed7441ddbc57703042204a3e30ef747478eea022c70f789fc7f084bc3d"}, + {file = "adal-1.2.7.tar.gz", hash = "sha256:d74f45b81317454d96e982fd1c50e6fb5c99ac2223728aea8764433a39f566f1"}, +] +aenum = [ + {file = "aenum-3.1.11-py2-none-any.whl", hash = "sha256:525b4870a27d0b471c265bda692bc657f1e0dd7597ad4186d072c59f9db666f6"}, + {file = "aenum-3.1.11-py3-none-any.whl", hash = "sha256:12ae89967f2e25c0ce28c293955d643f891603488bc3d9946158ba2b35203638"}, + {file = "aenum-3.1.11.tar.gz", hash = "sha256:aed2c273547ae72a0d5ee869719c02a643da16bf507c80958faadc7e038e3f73"}, +] +aiohttp = [ + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ba71c9b4dcbb16212f334126cc3d8beb6af377f6703d9dc2d9fb3874fd667ee9"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d24b8bb40d5c61ef2d9b6a8f4528c2f17f1c5d2d31fed62ec860f6006142e83e"}, + {file = "aiohttp-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f88df3a83cf9df566f171adba39d5bd52814ac0b94778d2448652fc77f9eb491"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97decbb3372d4b69e4d4c8117f44632551c692bb1361b356a02b97b69e18a62"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:309aa21c1d54b8ef0723181d430347d7452daaff93e8e2363db8e75c72c2fb2d"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad5383a67514e8e76906a06741febd9126fc7c7ff0f599d6fcce3e82b80d026f"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20acae4f268317bb975671e375493dbdbc67cddb5f6c71eebdb85b34444ac46b"}, + {file = "aiohttp-3.8.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:05a3c31c6d7cd08c149e50dc7aa2568317f5844acd745621983380597f027a18"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d6f76310355e9fae637c3162936e9504b4767d5c52ca268331e2756e54fd4ca5"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:256deb4b29fe5e47893fa32e1de2d73c3afe7407738bd3c63829874661d4822d"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:5c59fcd80b9049b49acd29bd3598cada4afc8d8d69bd4160cd613246912535d7"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:059a91e88f2c00fe40aed9031b3606c3f311414f86a90d696dd982e7aec48142"}, + {file = "aiohttp-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2feebbb6074cdbd1ac276dbd737b40e890a1361b3cc30b74ac2f5e24aab41f7b"}, + {file = "aiohttp-3.8.3-cp310-cp310-win32.whl", hash = "sha256:5bf651afd22d5f0c4be16cf39d0482ea494f5c88f03e75e5fef3a85177fecdeb"}, + {file = "aiohttp-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:653acc3880459f82a65e27bd6526e47ddf19e643457d36a2250b85b41a564715"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:86fc24e58ecb32aee09f864cb11bb91bc4c1086615001647dbfc4dc8c32f4008"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:75e14eac916f024305db517e00a9252714fce0abcb10ad327fb6dcdc0d060f1d"}, + {file = "aiohttp-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d1fde0f44029e02d02d3993ad55ce93ead9bb9b15c6b7ccd580f90bd7e3de476"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ab94426ddb1ecc6a0b601d832d5d9d421820989b8caa929114811369673235c"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:89d2e02167fa95172c017732ed7725bc8523c598757f08d13c5acca308e1a061"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:02f9a2c72fc95d59b881cf38a4b2be9381b9527f9d328771e90f72ac76f31ad8"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c7149272fb5834fc186328e2c1fa01dda3e1fa940ce18fded6d412e8f2cf76d"}, + {file = "aiohttp-3.8.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:512bd5ab136b8dc0ffe3fdf2dfb0c4b4f49c8577f6cae55dca862cd37a4564e2"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7018ecc5fe97027214556afbc7c502fbd718d0740e87eb1217b17efd05b3d276"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88c70ed9da9963d5496d38320160e8eb7e5f1886f9290475a881db12f351ab5d"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:da22885266bbfb3f78218dc40205fed2671909fbd0720aedba39b4515c038091"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:e65bc19919c910127c06759a63747ebe14f386cda573d95bcc62b427ca1afc73"}, + {file = "aiohttp-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:08c78317e950e0762c2983f4dd58dc5e6c9ff75c8a0efeae299d363d439c8e34"}, + {file = "aiohttp-3.8.3-cp311-cp311-win32.whl", hash = "sha256:45d88b016c849d74ebc6f2b6e8bc17cabf26e7e40c0661ddd8fae4c00f015697"}, + {file = "aiohttp-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:96372fc29471646b9b106ee918c8eeb4cca423fcbf9a34daa1b93767a88a2290"}, + {file = "aiohttp-3.8.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:c971bf3786b5fad82ce5ad570dc6ee420f5b12527157929e830f51c55dc8af77"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ff25f48fc8e623d95eca0670b8cc1469a83783c924a602e0fbd47363bb54aaca"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e381581b37db1db7597b62a2e6b8b57c3deec95d93b6d6407c5b61ddc98aca6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db19d60d846283ee275d0416e2a23493f4e6b6028825b51290ac05afc87a6f97"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25892c92bee6d9449ffac82c2fe257f3a6f297792cdb18ad784737d61e7a9a85"}, + {file = "aiohttp-3.8.3-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:398701865e7a9565d49189f6c90868efaca21be65c725fc87fc305906be915da"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4a4fbc769ea9b6bd97f4ad0b430a6807f92f0e5eb020f1e42ece59f3ecfc4585"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:b29bfd650ed8e148f9c515474a6ef0ba1090b7a8faeee26b74a8ff3b33617502"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_ppc64le.whl", hash = "sha256:1e56b9cafcd6531bab5d9b2e890bb4937f4165109fe98e2b98ef0dcfcb06ee9d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_s390x.whl", hash = "sha256:ec40170327d4a404b0d91855d41bfe1fe4b699222b2b93e3d833a27330a87a6d"}, + {file = "aiohttp-3.8.3-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2df5f139233060578d8c2c975128fb231a89ca0a462b35d4b5fcf7c501ebdbe1"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win32.whl", hash = "sha256:f973157ffeab5459eefe7b97a804987876dd0a55570b8fa56b4e1954bf11329b"}, + {file = "aiohttp-3.8.3-cp36-cp36m-win_amd64.whl", hash = "sha256:437399385f2abcd634865705bdc180c8314124b98299d54fe1d4c8990f2f9494"}, + {file = "aiohttp-3.8.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:09e28f572b21642128ef31f4e8372adb6888846f32fecb288c8b0457597ba61a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f3553510abdbec67c043ca85727396ceed1272eef029b050677046d3387be8d"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e168a7560b7c61342ae0412997b069753f27ac4862ec7867eff74f0fe4ea2ad9"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:db4c979b0b3e0fa7e9e69ecd11b2b3174c6963cebadeecfb7ad24532ffcdd11a"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e164e0a98e92d06da343d17d4e9c4da4654f4a4588a20d6c73548a29f176abe2"}, + {file = "aiohttp-3.8.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e8a78079d9a39ca9ca99a8b0ac2fdc0c4d25fc80c8a8a82e5c8211509c523363"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:21b30885a63c3f4ff5b77a5d6caf008b037cb521a5f33eab445dc566f6d092cc"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4b0f30372cef3fdc262f33d06e7b411cd59058ce9174ef159ad938c4a34a89da"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:8135fa153a20d82ffb64f70a1b5c2738684afa197839b34cc3e3c72fa88d302c"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:ad61a9639792fd790523ba072c0555cd6be5a0baf03a49a5dd8cfcf20d56df48"}, + {file = "aiohttp-3.8.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:978b046ca728073070e9abc074b6299ebf3501e8dee5e26efacb13cec2b2dea0"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win32.whl", hash = "sha256:0d2c6d8c6872df4a6ec37d2ede71eff62395b9e337b4e18efd2177de883a5033"}, + {file = "aiohttp-3.8.3-cp37-cp37m-win_amd64.whl", hash = "sha256:21d69797eb951f155026651f7e9362877334508d39c2fc37bd04ff55b2007091"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ca9af5f8f5812d475c5259393f52d712f6d5f0d7fdad9acdb1107dd9e3cb7eb"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d90043c1882067f1bd26196d5d2db9aa6d268def3293ed5fb317e13c9413ea4"}, + {file = "aiohttp-3.8.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d737fc67b9a970f3234754974531dc9afeea11c70791dcb7db53b0cf81b79784"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebf909ea0a3fc9596e40d55d8000702a85e27fd578ff41a5500f68f20fd32e6c"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5835f258ca9f7c455493a57ee707b76d2d9634d84d5d7f62e77be984ea80b849"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:da37dcfbf4b7f45d80ee386a5f81122501ec75672f475da34784196690762f4b"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87f44875f2804bc0511a69ce44a9595d5944837a62caecc8490bbdb0e18b1342"}, + {file = "aiohttp-3.8.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:527b3b87b24844ea7865284aabfab08eb0faf599b385b03c2aa91fc6edd6e4b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5ba88df9aa5e2f806650fcbeedbe4f6e8736e92fc0e73b0400538fd25a4dd96"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e7b8813be97cab8cb52b1375f41f8e6804f6507fe4660152e8ca5c48f0436017"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:2dea10edfa1a54098703cb7acaa665c07b4e7568472a47f4e64e6319d3821ccf"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:713d22cd9643ba9025d33c4af43943c7a1eb8547729228de18d3e02e278472b6"}, + {file = "aiohttp-3.8.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2d252771fc85e0cf8da0b823157962d70639e63cb9b578b1dec9868dd1f4f937"}, + {file = "aiohttp-3.8.3-cp38-cp38-win32.whl", hash = "sha256:66bd5f950344fb2b3dbdd421aaa4e84f4411a1a13fca3aeb2bcbe667f80c9f76"}, + {file = "aiohttp-3.8.3-cp38-cp38-win_amd64.whl", hash = "sha256:84b14f36e85295fe69c6b9789b51a0903b774046d5f7df538176516c3e422446"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16c121ba0b1ec2b44b73e3a8a171c4f999b33929cd2397124a8c7fcfc8cd9e06"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8d6aaa4e7155afaf994d7924eb290abbe81a6905b303d8cb61310a2aba1c68ba"}, + {file = "aiohttp-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:43046a319664a04b146f81b40e1545d4c8ac7b7dd04c47e40bf09f65f2437346"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:599418aaaf88a6d02a8c515e656f6faf3d10618d3dd95866eb4436520096c84b"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:92a2964319d359f494f16011e23434f6f8ef0434acd3cf154a6b7bec511e2fb7"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:73a4131962e6d91109bca6536416aa067cf6c4efb871975df734f8d2fd821b37"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:598adde339d2cf7d67beaccda3f2ce7c57b3b412702f29c946708f69cf8222aa"}, + {file = "aiohttp-3.8.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75880ed07be39beff1881d81e4a907cafb802f306efd6d2d15f2b3c69935f6fb"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:a0239da9fbafd9ff82fd67c16704a7d1bccf0d107a300e790587ad05547681c8"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:4e3a23ec214e95c9fe85a58470b660efe6534b83e6cbe38b3ed52b053d7cb6ad"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:47841407cc89a4b80b0c52276f3cc8138bbbfba4b179ee3acbd7d77ae33f7ac4"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:54d107c89a3ebcd13228278d68f1436d3f33f2dd2af5415e3feaeb1156e1a62c"}, + {file = "aiohttp-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c37c5cce780349d4d51739ae682dec63573847a2a8dcb44381b174c3d9c8d403"}, + {file = "aiohttp-3.8.3-cp39-cp39-win32.whl", hash = "sha256:f178d2aadf0166be4df834c4953da2d7eef24719e8aec9a65289483eeea9d618"}, + {file = "aiohttp-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:88e5be56c231981428f4f506c68b6a46fa25c4123a2e86d156c58a8369d31ab7"}, + {file = "aiohttp-3.8.3.tar.gz", hash = "sha256:3828fb41b7203176b82fe5d699e0d845435f2374750a44b480ea6b930f6be269"}, +] +aiosignal = [ + {file = "aiosignal-1.2.0-py3-none-any.whl", hash = "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a"}, + {file = "aiosignal-1.2.0.tar.gz", hash = "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2"}, +] +alabaster = [ + {file = "alabaster-0.7.12-py2.py3-none-any.whl", hash = "sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359"}, + {file = "alabaster-0.7.12.tar.gz", hash = "sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02"}, +] +albumentations = [ + {file = "albumentations-1.3.0-py3-none-any.whl", hash = "sha256:294165d87d03bc8323e484927f0a5c1a3c64b0e7b9c32a979582a6c93c363bdf"}, + {file = "albumentations-1.3.0.tar.gz", hash = "sha256:be1af36832c8893314f2a5550e8ac19801e04770734c1b70fa3c996b41f37bed"}, +] +antlr4-python3-runtime = [ + {file = "antlr4-python3-runtime-4.9.3.tar.gz", hash = "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b"}, +] +anyio = [ + {file = "anyio-3.6.2-py3-none-any.whl", hash = "sha256:fbbe32bd270d2a2ef3ed1c5d45041250284e31fc0a4df4a5a6071842051a51e3"}, + {file = "anyio-3.6.2.tar.gz", hash = "sha256:25ea0d673ae30af41a0c442f81cf3b38c7e79fdc7b60335a4c14e05eb0947421"}, +] +apex = [] +appdirs = [ + {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, + {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, +] +applicationinsights = [ + {file = "applicationinsights-0.11.10-py2.py3-none-any.whl", hash = "sha256:e89a890db1c6906b6a7d0bcfd617dac83974773c64573147c8d6654f9cf2a6ea"}, + {file = "applicationinsights-0.11.10.tar.gz", hash = "sha256:0b761f3ef0680acf4731906dfc1807faa6f2a57168ae74592db0084a6099f7b3"}, +] +appnope = [ + {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, + {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, +] +argcomplete = [ + {file = "argcomplete-1.12.3-py2.py3-none-any.whl", hash = "sha256:291f0beca7fd49ce285d2f10e4c1c77e9460cf823eef2de54df0c0fec88b0d81"}, + {file = "argcomplete-1.12.3.tar.gz", hash = "sha256:2c7dbffd8c045ea534921e63b0be6fe65e88599990d8dc408ac8c542b72a5445"}, +] +argon2-cffi = [ + {file = "argon2-cffi-21.3.0.tar.gz", hash = "sha256:d384164d944190a7dd7ef22c6aa3ff197da12962bd04b17f64d4e93d934dba5b"}, + {file = "argon2_cffi-21.3.0-py3-none-any.whl", hash = "sha256:8c976986f2c5c0e5000919e6de187906cfd81fb1c72bf9d88c01177e77da7f80"}, +] +argon2-cffi-bindings = [ + {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, + {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, + {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, + {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, + {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, +] +astroid = [ + {file = "astroid-2.12.12-py3-none-any.whl", hash = "sha256:72702205200b2a638358369d90c222d74ebc376787af8fb2f7f2a86f7b5cc85f"}, + {file = "astroid-2.12.12.tar.gz", hash = "sha256:1c00a14f5a3ed0339d38d2e2e5b74ea2591df5861c0936bb292b84ccf3a78d83"}, +] +asttokens = [ + {file = "asttokens-2.0.8-py2.py3-none-any.whl", hash = "sha256:e3305297c744ae53ffa032c45dc347286165e4ffce6875dc662b205db0623d86"}, + {file = "asttokens-2.0.8.tar.gz", hash = "sha256:c61e16246ecfb2cde2958406b4c8ebc043c9e6d73aaa83c941673b35e5d3a76b"}, +] +async-timeout = [ + {file = "async-timeout-4.0.2.tar.gz", hash = "sha256:2163e1640ddb52b7a8c80d0a67a08587e5d245cc9c553a74a847056bc2976b15"}, + {file = "async_timeout-4.0.2-py3-none-any.whl", hash = "sha256:8ca1e4fcf50d07413d66d1a5e416e42cfdf5851c981d679a09851a6853383b3c"}, +] +atomicwrites = [ + {file = "atomicwrites-1.4.1.tar.gz", hash = "sha256:81b2c9071a49367a7f770170e5eec8cb66567cfbbc8c73d20ce5ca4a8d71cf11"}, +] +attrs = [ + {file = "attrs-22.1.0-py2.py3-none-any.whl", hash = "sha256:86efa402f67bf2df34f51a335487cf46b1ec130d02b8d39fd248abfd30da551c"}, + {file = "attrs-22.1.0.tar.gz", hash = "sha256:29adc2665447e5191d0e7c568fde78b21f9672d344281d0c6e1ab085429b22b6"}, +] +av = [ + {file = "av-9.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:29373aa86a055a07eebb14d253cb202033f63ba98c5a4b0233d6d4c07fc7a292"}, + {file = "av-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:343b11d9b03e71da29f3ce56bc0a6c2d40aba448225dcf8296ab53c10527fff0"}, + {file = "av-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ea180bfd89bc0a9e392c32de204cf4e51648aefe2f375d430ce39c04e3ed625"}, + {file = "av-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b07b91f534ee7a096068149404c67c3c0e5b4c373580b016151de0fcb440cd3f"}, + {file = "av-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9983bc45dab65d2416d2f8a63785caa076a751590823fc8c199617d0dbad390"}, + {file = "av-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:0340cc68f3d222bc9438b4fde12e3d68f949eeb5de9e090db182f2cb06e23d53"}, + {file = "av-9.2.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e3e4a28fa0eabd3ab5b0915e9c005e9155039f9e1a4466212434c40eb69a33fb"}, + {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a3c9126d658029b151484b48c656b73af1b145b143c50de5b8b983ac60e095"}, + {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3facfe8dc5ba7f9ec7fd7e4c0466e577b84d5f2a1671428f7e28ebcd2cb0ccd3"}, + {file = "av-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af951271d998f736a20e54fbc0d944f263db7b17592f11cd489947957bf46aa8"}, + {file = "av-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:109526152e658921731018c50a05db802e7c9f3eb04a7a5fcbd8321fb3b73134"}, + {file = "av-9.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:587dd492a2ef3eb20324a0a8d67e6a2e686845d8c1dfdcad058377ac84268d67"}, + {file = "av-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:28d85b8476f7d8fb18e3af9bd6d22bb292f1d810a20f8910fe481f648372e798"}, + {file = "av-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a6a35e6028dec677caed97d19bfab3b66182690d43b0ec3c355778d740ce0509"}, + {file = "av-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a616a6eb46b62f41ff69569cafe12b0005a6dd14389f597dee115340336a910f"}, + {file = "av-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d730f3ed30eda46d06849bd71ad87d480cf0cad9fd064f33a0386dee95461e31"}, + {file = "av-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:6b01fbe8047da81892f8bd2aee5690f00465bf5215e3f6b6372863ac9408d75f"}, + {file = "av-9.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:ba3d9e3fe23fd8a14e810f291386225acbdef1c7e5376cc10c8e85c2d4280771"}, + {file = "av-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b6be9388618af978304b56d1cf6b74c811db4f220dd320da5bd79640aa443358"}, + {file = "av-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e59e4ab0e8832bf87707e5024283b3a24cc01784604f0b0e96fbfbadbd8d9fc0"}, + {file = "av-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b0f124e335561cf4de6b7cdc461283c5eba5f05cccb1a5e1b8ceb1cd15393d8"}, + {file = "av-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:49481c2d5bc296f451ccd3f93b1cb692d7f58a804b794b99c8b7743e058cae71"}, + {file = "av-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:1cbf031f650f89943023eef80e8b2c99588bf9ba26ffef8b3b54bef7102ea3dc"}, + {file = "av-9.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:48819e401cea5be57bd03299d8e5f700082c411746d1ac23eb5e5a931d3d3ced"}, + {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d9cad890e6eccf2697b1c932761bee6f5e1e7faf9b8c03cf10f18f697d29ba3"}, + {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080f34ddfde551de3a5f2d0d06d7518718e3115af81e56182e158cc03111662"}, + {file = "av-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b46b54ddf64409d4455f408b5970f8494c27c0273181b81c2f7d5072c9afb55"}, + {file = "av-9.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf941896b4c800ee707211c802f94c6e0b4642d3000e25d1974d0b6032af4f66"}, + {file = "av-9.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d380925732e7497c1c11545107eabe1f498cab214f49f32d1b5d6abe01a2b36b"}, + {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a1c2c1dcc1947473ea1e2cbbf50549e2655e49e08bdd2a6427a97276d7a92c8"}, + {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e2a50a146b3f33a24ea059af913ad368dbb61ed494234debe140a09f1076950"}, + {file = "av-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45816a39255b39e514a72125e0b6e29eb24fe0994bef3f4f87f3b9d9960b3fa8"}, + {file = "av-9.2.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ab90aa3ac2cbdf1f22087fc0fa439f643e96979f169ecfa1d496e114c3c3a8b3"}, + {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24dac414eafcc20423f2ec7e873706489433648f0e9af08a537996880aa55979"}, + {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:17a7b6617d4201214f3dd5f628041b4fe56f4244dcd48399ed8d0cf324ca24d1"}, + {file = "av-9.2.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8671fa01648ce7aac76e71816c2421ddb1939bf706e2e14684608ab1ce9dbbbb"}, + {file = "av-9.2.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7a5dc26b9df656bed5e1bdeaf8bcc4ff4a2e009ee90b3b3024a86cf8476b2cbf"}, + {file = "av-9.2.0.tar.gz", hash = "sha256:f2a7c226724d7f7745b376b459c500d9d17bd8d0473b7ea6bf8ddb4f7957c69d"}, +] +azure-cli-core = [ + {file = "azure-cli-core-2.41.0.tar.gz", hash = "sha256:f76fa9a44fd1e858397ba2951d260309e70710f367dd609eea03d6c0d8b05d3e"}, + {file = "azure_cli_core-2.41.0-py3-none-any.whl", hash = "sha256:b4cdf765106ff481361d6d84ae0403c46ea6c414ea511b38a316241c03f69252"}, +] +azure-cli-telemetry = [ + {file = "azure-cli-telemetry-1.0.8.tar.gz", hash = "sha256:ca996d162ab689c865f6b60be23b9757c26c3d97928e3319858eea83462df08d"}, + {file = "azure_cli_telemetry-1.0.8-py3-none-any.whl", hash = "sha256:502cbd90723a16603822909befd096ca0b1707de1e70cf730e7b4700ddd7a456"}, +] +azure-common = [ + {file = "azure-common-1.1.28.zip", hash = "sha256:4ac0cd3214e36b6a1b6a442686722a5d8cc449603aa833f3f0f40bda836704a3"}, + {file = "azure_common-1.1.28-py2.py3-none-any.whl", hash = "sha256:5c12d3dcf4ec20599ca6b0d3e09e86e146353d443e7fcc050c9a19c1f9df20ad"}, +] +azure-core = [ + {file = "azure-core-1.26.0.zip", hash = "sha256:b0036a0d256329e08d1278dff7df36be30031d2ec9b16c691bc61e4732f71fe0"}, + {file = "azure_core-1.26.0-py3-none-any.whl", hash = "sha256:578ea3ae56bca48880c96797871b6c954b5ae78d10d54360182c7604dc837f25"}, +] +azure-mgmt-core = [ + {file = "azure-mgmt-core-1.3.2.zip", hash = "sha256:07f4afe823a55d704b048d61edfdc1318c051ed59f244032126350be95e9d501"}, + {file = "azure_mgmt_core-1.3.2-py3-none-any.whl", hash = "sha256:fd829f67086e5cf6f7eb016c9e80bb0fb293cbbbd4d8738dc90af9aa1055fb7b"}, +] +azure-nspkg = [ + {file = "azure-nspkg-3.0.2.zip", hash = "sha256:e7d3cea6af63e667d87ba1ca4f8cd7cb4dfca678e4c55fc1cedb320760e39dd0"}, + {file = "azure_nspkg-3.0.2-py2-none-any.whl", hash = "sha256:1d0bbb2157cf57b1bef6c8c8e5b41133957364456c43b0a43599890023cca0a8"}, + {file = "azure_nspkg-3.0.2-py3-none-any.whl", hash = "sha256:31a060caca00ed1ebd369fc7fe01a56768c927e404ebc92268f4d9d636435e28"}, +] +azure-storage-blob = [ + {file = "azure-storage-blob-2.1.0.tar.gz", hash = "sha256:b90323aad60f207f9f90a0c4cf94c10acc313c20b39403398dfba51f25f7b454"}, + {file = "azure_storage_blob-2.1.0-py2.py3-none-any.whl", hash = "sha256:a8e91a51d4f62d11127c7fd8ba0077385c5b11022f0269f8a2a71b9fc36bef31"}, +] +azure-storage-common = [ + {file = "azure-storage-common-2.1.0.tar.gz", hash = "sha256:ccedef5c67227bc4d6670ffd37cec18fb529a1b7c3a5e53e4096eb0cf23dc73f"}, + {file = "azure_storage_common-2.1.0-py2.py3-none-any.whl", hash = "sha256:b01a491a18839b9d05a4fe3421458a0ddb5ab9443c14e487f40d16f9a1dc2fbe"}, +] +azure-storage-nspkg = [ + {file = "azure-storage-nspkg-3.1.0.tar.gz", hash = "sha256:6f3bbe8652d5f542767d8433e7f96b8df7f518774055ac7c92ed7ca85f653811"}, + {file = "azure_storage_nspkg-3.1.0-py2.py3-none-any.whl", hash = "sha256:7da3bd6c73b8c464a57f53ae9af8328490d2267c66430d8a7621997e52a9703e"}, +] +babel = [ + {file = "Babel-2.10.3-py3-none-any.whl", hash = "sha256:ff56f4892c1c4bf0d814575ea23471c230d544203c7748e8c68f0089478d48eb"}, + {file = "Babel-2.10.3.tar.gz", hash = "sha256:7614553711ee97490f732126dc077f8d0ae084ebc6a96e23db1482afabdb2c51"}, +] +backcall = [ + {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, + {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, +] +bcrypt = [ + {file = "bcrypt-4.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:b1023030aec778185a6c16cf70f359cbb6e0c289fd564a7cfa29e727a1c38f8f"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:08d2947c490093a11416df18043c27abe3921558d2c03e2076ccb28a116cb6d0"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0eaa47d4661c326bfc9d08d16debbc4edf78778e6aaba29c1bc7ce67214d4410"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae88eca3024bb34bb3430f964beab71226e761f51b912de5133470b649d82344"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:a522427293d77e1c29e303fc282e2d71864579527a04ddcfda6d4f8396c6c36a"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:fbdaec13c5105f0c4e5c52614d04f0bca5f5af007910daa8b6b12095edaa67b3"}, + {file = "bcrypt-4.0.1-cp36-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:ca3204d00d3cb2dfed07f2d74a25f12fc12f73e606fcaa6975d1f7ae69cacbb2"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:089098effa1bc35dc055366740a067a2fc76987e8ec75349eb9484061c54f535"}, + {file = "bcrypt-4.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e9a51bbfe7e9802b5f3508687758b564069ba937748ad7b9e890086290d2f79e"}, + {file = "bcrypt-4.0.1-cp36-abi3-win32.whl", hash = "sha256:2caffdae059e06ac23fce178d31b4a702f2a3264c20bfb5ff541b338194d8fab"}, + {file = "bcrypt-4.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:8a68f4341daf7522fe8d73874de8906f3a339048ba406be6ddc1b3ccb16fc0d9"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf4fa8b2ca74381bb5442c089350f09a3f17797829d958fad058d6e44d9eb83c"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:67a97e1c405b24f19d08890e7ae0c4f7ce1e56a712a016746c8b2d7732d65d4b"}, + {file = "bcrypt-4.0.1-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b3b85202d95dd568efcb35b53936c5e3b3600c7cdcc6115ba461df3a8e89f38d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cbb03eec97496166b704ed663a53680ab57c5084b2fc98ef23291987b525cb7d"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:5ad4d32a28b80c5fa6671ccfb43676e8c1cc232887759d1cd7b6f56ea4355215"}, + {file = "bcrypt-4.0.1-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:b57adba8a1444faf784394de3436233728a1ecaeb6e07e8c22c8848f179b893c"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:705b2cea8a9ed3d55b4491887ceadb0106acf7c6387699fca771af56b1cdeeda"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:2b3ac11cf45161628f1f3733263e63194f22664bf4d0c0f3ab34099c02134665"}, + {file = "bcrypt-4.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:3100851841186c25f127731b9fa11909ab7b1df6fc4b9f8353f4f1fd952fbf71"}, + {file = "bcrypt-4.0.1.tar.gz", hash = "sha256:27d375903ac8261cfe4047f6709d16f7d18d39b1ec92aaf72af989552a650ebd"}, +] +beautifulsoup4 = [ + {file = "beautifulsoup4-4.11.1-py3-none-any.whl", hash = "sha256:58d5c3d29f5a36ffeb94f02f0d786cd53014cf9b3b3951d42e0080d8a9498d30"}, + {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, +] +bidict = [ + {file = "bidict-0.22.0-py3-none-any.whl", hash = "sha256:415126d23a0c81e1a8c584a8fb1f6905ea090c772571803aeee0a2242e8e7ba0"}, + {file = "bidict-0.22.0.tar.gz", hash = "sha256:5c826b3e15e97cc6e615de295756847c282a79b79c5430d3bfc909b1ac9f5bd8"}, +] +bleach = [ + {file = "bleach-5.0.1-py3-none-any.whl", hash = "sha256:085f7f33c15bd408dd9b17a4ad77c577db66d76203e5984b1bd59baeee948b2a"}, + {file = "bleach-5.0.1.tar.gz", hash = "sha256:0d03255c47eb9bd2f26aa9bb7f2107732e7e8fe195ca2f64709fcf3b0a4a085c"}, +] +blosc = [ + {file = "blosc-1.9.2.tar.gz", hash = "sha256:89196a2112035836f027a29835ee247b0c7a45cece4cd9e4b740a1428aa174bf"}, +] +breathe = [ + {file = "breathe-4.34.0-py3-none-any.whl", hash = "sha256:48804dcf0e607a89fb6ad88c729ef12743a42db03ae9489be4ef8f7c4011774a"}, + {file = "breathe-4.34.0.tar.gz", hash = "sha256:ac0768a5e84addad3e632028fe67749c567aba2b29088493b64c2c1634bcdba1"}, +] +brotli = [ + {file = "Brotli-1.0.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:268fe94547ba25b58ebc724680609c8ee3e5a843202e9a381f6f9c5e8bdb5c70"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:c2415d9d082152460f2bd4e382a1e85aed233abc92db5a3880da2257dc7daf7b"}, + {file = "Brotli-1.0.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5913a1177fc36e30fcf6dc868ce23b0453952c78c04c266d3149b3d39e1410d6"}, + {file = "Brotli-1.0.9-cp27-cp27m-win32.whl", hash = "sha256:afde17ae04d90fbe53afb628f7f2d4ca022797aa093e809de5c3cf276f61bbfa"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7cb81373984cc0e4682f31bc3d6be9026006d96eecd07ea49aafb06897746452"}, + {file = "Brotli-1.0.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:db844eb158a87ccab83e868a762ea8024ae27337fc7ddcbfcddd157f841fdfe7"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:9744a863b489c79a73aba014df554b0e7a0fc44ef3f8a0ef2a52919c7d155031"}, + {file = "Brotli-1.0.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a72661af47119a80d82fa583b554095308d6a4c356b2a554fdc2799bc19f2a43"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ee83d3e3a024a9618e5be64648d6d11c37047ac48adff25f12fa4226cf23d1c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:19598ecddd8a212aedb1ffa15763dd52a388518c4550e615aed88dc3753c0f0c"}, + {file = "Brotli-1.0.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:44bb8ff420c1d19d91d79d8c3574b8954288bdff0273bf788954064d260d7ab0"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e23281b9a08ec338469268f98f194658abfb13658ee98e2b7f85ee9dd06caa91"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3496fc835370da351d37cada4cf744039616a6db7d13c430035e901443a34daa"}, + {file = "Brotli-1.0.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83bb06a0192cccf1eb8d0a28672a1b79c74c3a8a5f2619625aeb6f28b3a82bb"}, + {file = "Brotli-1.0.9-cp310-cp310-win32.whl", hash = "sha256:26d168aac4aaec9a4394221240e8a5436b5634adc3cd1cdf637f6645cecbf181"}, + {file = "Brotli-1.0.9-cp310-cp310-win_amd64.whl", hash = "sha256:622a231b08899c864eb87e85f81c75e7b9ce05b001e59bbfbf43d4a71f5f32b2"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cc0283a406774f465fb45ec7efb66857c09ffefbe49ec20b7882eff6d3c86d3a"}, + {file = "Brotli-1.0.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:11d3283d89af7033236fa4e73ec2cbe743d4f6a81d41bd234f24bf63dde979df"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c1306004d49b84bd0c4f90457c6f57ad109f5cc6067a9664e12b7b79a9948ad"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1375b5d17d6145c798661b67e4ae9d5496920d9265e2f00f1c2c0b5ae91fbde"}, + {file = "Brotli-1.0.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cab1b5964b39607a66adbba01f1c12df2e55ac36c81ec6ed44f2fca44178bf1a"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8ed6a5b3d23ecc00ea02e1ed8e0ff9a08f4fc87a1f58a2530e71c0f48adf882f"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:cb02ed34557afde2d2da68194d12f5719ee96cfb2eacc886352cb73e3808fc5d"}, + {file = "Brotli-1.0.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:b3523f51818e8f16599613edddb1ff924eeb4b53ab7e7197f85cbc321cdca32f"}, + {file = "Brotli-1.0.9-cp311-cp311-win32.whl", hash = "sha256:ba72d37e2a924717990f4d7482e8ac88e2ef43fb95491eb6e0d124d77d2a150d"}, + {file = "Brotli-1.0.9-cp311-cp311-win_amd64.whl", hash = "sha256:3ffaadcaeafe9d30a7e4e1e97ad727e4f5610b9fa2f7551998471e3736738679"}, + {file = "Brotli-1.0.9-cp35-cp35m-macosx_10_6_intel.whl", hash = "sha256:c83aa123d56f2e060644427a882a36b3c12db93727ad7a7b9efd7d7f3e9cc2c4"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:6b2ae9f5f67f89aade1fab0f7fd8f2832501311c363a21579d02defa844d9296"}, + {file = "Brotli-1.0.9-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:68715970f16b6e92c574c30747c95cf8cf62804569647386ff032195dc89a430"}, + {file = "Brotli-1.0.9-cp35-cp35m-win32.whl", hash = "sha256:defed7ea5f218a9f2336301e6fd379f55c655bea65ba2476346340a0ce6f74a1"}, + {file = "Brotli-1.0.9-cp35-cp35m-win_amd64.whl", hash = "sha256:88c63a1b55f352b02c6ffd24b15ead9fc0e8bf781dbe070213039324922a2eea"}, + {file = "Brotli-1.0.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:503fa6af7da9f4b5780bb7e4cbe0c639b010f12be85d02c99452825dd0feef3f"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:40d15c79f42e0a2c72892bf407979febd9cf91f36f495ffb333d1d04cebb34e4"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:93130612b837103e15ac3f9cbacb4613f9e348b58b3aad53721d92e57f96d46a"}, + {file = "Brotli-1.0.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87fdccbb6bb589095f413b1e05734ba492c962b4a45a13ff3408fa44ffe6479b"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:6d847b14f7ea89f6ad3c9e3901d1bc4835f6b390a9c71df999b0162d9bb1e20f"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:495ba7e49c2db22b046a53b469bbecea802efce200dffb69b93dd47397edc9b6"}, + {file = "Brotli-1.0.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4688c1e42968ba52e57d8670ad2306fe92e0169c6f3af0089be75bbac0c64a3b"}, + {file = "Brotli-1.0.9-cp36-cp36m-win32.whl", hash = "sha256:61a7ee1f13ab913897dac7da44a73c6d44d48a4adff42a5701e3239791c96e14"}, + {file = "Brotli-1.0.9-cp36-cp36m-win_amd64.whl", hash = "sha256:1c48472a6ba3b113452355b9af0a60da5c2ae60477f8feda8346f8fd48e3e87c"}, + {file = "Brotli-1.0.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:3b78a24b5fd13c03ee2b7b86290ed20efdc95da75a3557cc06811764d5ad1126"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:9d12cf2851759b8de8ca5fde36a59c08210a97ffca0eb94c532ce7b17c6a3d1d"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:6c772d6c0a79ac0f414a9f8947cc407e119b8598de7621f39cacadae3cf57d12"}, + {file = "Brotli-1.0.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29d1d350178e5225397e28ea1b7aca3648fcbab546d20e7475805437bfb0a130"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7bbff90b63328013e1e8cb50650ae0b9bac54ffb4be6104378490193cd60f85a"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ec1947eabbaf8e0531e8e899fc1d9876c179fc518989461f5d24e2223395a9e3"}, + {file = "Brotli-1.0.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12effe280b8ebfd389022aa65114e30407540ccb89b177d3fbc9a4f177c4bd5d"}, + {file = "Brotli-1.0.9-cp37-cp37m-win32.whl", hash = "sha256:f909bbbc433048b499cb9db9e713b5d8d949e8c109a2a548502fb9aa8630f0b1"}, + {file = "Brotli-1.0.9-cp37-cp37m-win_amd64.whl", hash = "sha256:97f715cf371b16ac88b8c19da00029804e20e25f30d80203417255d239f228b5"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e16eb9541f3dd1a3e92b89005e37b1257b157b7256df0e36bd7b33b50be73bcb"}, + {file = "Brotli-1.0.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:160c78292e98d21e73a4cc7f76a234390e516afcd982fa17e1422f7c6a9ce9c8"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_i686.whl", hash = "sha256:b663f1e02de5d0573610756398e44c130add0eb9a3fc912a09665332942a2efb"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5b6ef7d9f9c38292df3690fe3e302b5b530999fa90014853dcd0d6902fb59f26"}, + {file = "Brotli-1.0.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8a674ac10e0a87b683f4fa2b6fa41090edfd686a6524bd8dedbd6138b309175c"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e2d9e1cbc1b25e22000328702b014227737756f4b5bf5c485ac1d8091ada078b"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b336c5e9cf03c7be40c47b5fd694c43c9f1358a80ba384a21969e0b4e66a9b17"}, + {file = "Brotli-1.0.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:85f7912459c67eaab2fb854ed2bc1cc25772b300545fe7ed2dc03954da638649"}, + {file = "Brotli-1.0.9-cp38-cp38-win32.whl", hash = "sha256:35a3edbe18e876e596553c4007a087f8bcfd538f19bc116917b3c7522fca0429"}, + {file = "Brotli-1.0.9-cp38-cp38-win_amd64.whl", hash = "sha256:269a5743a393c65db46a7bb982644c67ecba4b8d91b392403ad8a861ba6f495f"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2aad0e0baa04517741c9bb5b07586c642302e5fb3e75319cb62087bd0995ab19"}, + {file = "Brotli-1.0.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5cb1e18167792d7d21e21365d7650b72d5081ed476123ff7b8cac7f45189c0c7"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_i686.whl", hash = "sha256:16d528a45c2e1909c2798f27f7bf0a3feec1dc9e50948e738b961618e38b6a7b"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:56d027eace784738457437df7331965473f2c0da2c70e1a1f6fdbae5402e0389"}, + {file = "Brotli-1.0.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9bf919756d25e4114ace16a8ce91eb340eb57a08e2c6950c3cebcbe3dff2a5e7"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:e4c4e92c14a57c9bd4cb4be678c25369bf7a092d55fd0866f759e425b9660806"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:e48f4234f2469ed012a98f4b7874e7f7e173c167bed4934912a29e03167cf6b1"}, + {file = "Brotli-1.0.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9ed4c92a0665002ff8ea852353aeb60d9141eb04109e88928026d3c8a9e5433c"}, + {file = "Brotli-1.0.9-cp39-cp39-win32.whl", hash = "sha256:cfc391f4429ee0a9370aa93d812a52e1fee0f37a81861f4fdd1f4fb28e8547c3"}, + {file = "Brotli-1.0.9-cp39-cp39-win_amd64.whl", hash = "sha256:854c33dad5ba0fbd6ab69185fec8dab89e13cda6b7d191ba111987df74f38761"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9749a124280a0ada4187a6cfd1ffd35c350fb3af79c706589d98e088c5044267"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:73fd30d4ce0ea48010564ccee1a26bfe39323fde05cb34b5863455629db61dc7"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:02177603aaca36e1fd21b091cb742bb3b305a569e2402f1ca38af471777fb019"}, + {file = "Brotli-1.0.9-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:76ffebb907bec09ff511bb3acc077695e2c32bc2142819491579a695f77ffd4d"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b43775532a5904bc938f9c15b77c613cb6ad6fb30990f3b0afaea82797a402d8"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5bf37a08493232fbb0f8229f1824b366c2fc1d02d64e7e918af40acd15f3e337"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:330e3f10cd01da535c70d09c4283ba2df5fb78e915bea0a28becad6e2ac010be"}, + {file = "Brotli-1.0.9-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:e1abbeef02962596548382e393f56e4c94acd286bd0c5afba756cffc33670e8a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3148362937217b7072cf80a2dcc007f09bb5ecb96dae4617316638194113d5be"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:336b40348269f9b91268378de5ff44dc6fbaa2268194f85177b53463d313842a"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3b8b09a16a1950b9ef495a0f8b9d0a87599a9d1f179e2d4ac014b2ec831f87e7"}, + {file = "Brotli-1.0.9-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:c8e521a0ce7cf690ca84b8cc2272ddaf9d8a50294fd086da67e517439614c755"}, + {file = "Brotli-1.0.9.zip", hash = "sha256:4d1b810aa0ed773f81dceda2cc7b403d01057458730e309856356d4ef4188438"}, +] +cachecontrol = [ + {file = "CacheControl-0.12.11-py2.py3-none-any.whl", hash = "sha256:2c75d6a8938cb1933c75c50184549ad42728a27e9f6b92fd677c3151aa72555b"}, + {file = "CacheControl-0.12.11.tar.gz", hash = "sha256:a5b9fcc986b184db101aa280b42ecdcdfc524892596f606858e0b7a8b4d9e144"}, +] +cachy = [ + {file = "cachy-0.3.0-py2.py3-none-any.whl", hash = "sha256:338ca09c8860e76b275aff52374330efedc4d5a5e45dc1c5b539c1ead0786fe7"}, + {file = "cachy-0.3.0.tar.gz", hash = "sha256:186581f4ceb42a0bbe040c407da73c14092379b1e4c0e327fdb72ae4a9b269b1"}, +] +carla = [ + {file = "carla-0.9.13-cp27-cp27mu-manylinux_2_27_x86_64.whl", hash = "sha256:339fcb1e392f3ade1be82b7258de19c533e2efae111e954a6eb174efb296903d"}, + {file = "carla-0.9.13-cp36-cp36m-manylinux_2_27_x86_64.whl", hash = "sha256:5f065825ce812343bf27a80a19d647b3200b31b44a9e80cea0340e3bd20cdf81"}, + {file = "carla-0.9.13-cp36-cp36m-win_amd64.whl", hash = "sha256:1210cce213e968a644effd4e2e48458a072481459d073424b05725056ba3d77d"}, + {file = "carla-0.9.13-cp37-cp37m-manylinux_2_27_x86_64.whl", hash = "sha256:a95d2d4218ea388c863c66b7c2ab3fe49ffefe53999305cfcb6a8107042f79af"}, + {file = "carla-0.9.13-cp37-cp37m-win_amd64.whl", hash = "sha256:954ca34d5bdd4516ceca353db907fee8cec6630d6b31a732b17dd1554e0f0f94"}, + {file = "carla-0.9.13-cp38-cp38-manylinux_2_27_x86_64.whl", hash = "sha256:d2bfaea2d6824a2d758cbe813856c69420494f5c97d2a2dfb45653ccf976f1ce"}, + {file = "carla-0.9.13-cp38-cp38-win_amd64.whl", hash = "sha256:a64ee78fe91137fa7d4828c7fc06d5824bd7312e29e4ea4f31a5d74dd28bff40"}, +] +casadi = [ + {file = "casadi-3.5.5-cp27-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:d4e49cb46404cef61f83b30bb20ec9597c50ae7f55cfd6b89c17facc74675437"}, + {file = "casadi-3.5.5-cp27-none-manylinux1_i686.whl", hash = "sha256:f08a99e98b0a15083f06b1e221f064a29b3ed9e20617dc55aa8e823f2f732ace"}, + {file = "casadi-3.5.5-cp27-none-manylinux1_x86_64.whl", hash = "sha256:09e103bb597d46aa338fc57bc49270068a1f07be35f9494c9f796dea4b801aeb"}, + {file = "casadi-3.5.5-cp27-none-win32.whl", hash = "sha256:a4ce51e988570160af9ccfbbb1b9679546cbb1865d3a74ef0276f37fd94d91d9"}, + {file = "casadi-3.5.5-cp27-none-win_amd64.whl", hash = "sha256:54d89442058271007ae8573dfa33360bea10e26603545481090b45e8b90c9d10"}, + {file = "casadi-3.5.5-cp34-none-manylinux1_x86_64.whl", hash = "sha256:4143803af909f284400c02f59de4d97e5ba9319de28366215ef55ef261914f9a"}, + {file = "casadi-3.5.5-cp34-none-win32.whl", hash = "sha256:7a624d40c7b5ded7916f6cc65998af4585b4557c9ea65dc1e3a6273ebb2313ec"}, + {file = "casadi-3.5.5-cp34-none-win_amd64.whl", hash = "sha256:3aec6737c282e7fb5be41f6c7d0649e52ce49efb3508f30bada707e809bbbb5f"}, + {file = "casadi-3.5.5-cp35-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6192e2ed81c15a7dab2554f5f69b134df8d1a982f8d9f13e57bdef93364d2120"}, + {file = "casadi-3.5.5-cp35-none-manylinux1_i686.whl", hash = "sha256:49a8b713f0ff0bbc2f2af2e71c515cdced238786e25ef504f5982618c84c67a7"}, + {file = "casadi-3.5.5-cp35-none-manylinux1_x86_64.whl", hash = "sha256:13277151efc76b221de8ca6b5ab7b8bbdd2b0e139f282866840adf88dfe53bc9"}, + {file = "casadi-3.5.5-cp35-none-manylinux2014_aarch64.whl", hash = "sha256:253569c85f881a6a8fe5e1c0758858edb1ecb4c3d8bce4aee4b52e5dc59fc091"}, + {file = "casadi-3.5.5-cp35-none-win32.whl", hash = "sha256:5de5c3c1381ac303e71fdef75dace34af6e1d50b46ac081051cd209b8b933837"}, + {file = "casadi-3.5.5-cp35-none-win_amd64.whl", hash = "sha256:4932b2b5361013420189dbc8d30e970672d036b37cb382f1c09c3b6cfe651a37"}, + {file = "casadi-3.5.5-cp36-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:2322748a8d5e88750fd2fc0abcdc56cfbad1a8cd538fe0e7d7b6d8ce0cb3fa62"}, + {file = "casadi-3.5.5-cp36-none-manylinux1_i686.whl", hash = "sha256:ab6a600a9b2ea27453d56fd4464ad0db0ae69f5cea42595fcbdaabcd40396440"}, + {file = "casadi-3.5.5-cp36-none-manylinux1_x86_64.whl", hash = "sha256:5f6eb8de31735c14ecc777e3ad77b57767b5f2dbea29265909ef696f51e8be92"}, + {file = "casadi-3.5.5-cp36-none-manylinux2014_aarch64.whl", hash = "sha256:adf20c34ba2cec1840a026023d93cc6d9b3581dfda6a044f434fc75b50c9a2ce"}, + {file = "casadi-3.5.5-cp36-none-win32.whl", hash = "sha256:7309a75b27c57f09b00a61815fb38c40da8e62e3004598e55ea1b8f713d96221"}, + {file = "casadi-3.5.5-cp36-none-win_amd64.whl", hash = "sha256:ab85c7cf772ba54f2718ebe366b836fffff868443f7c0c02389ed0a288cbde1f"}, + {file = "casadi-3.5.5-cp37-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:ec26244f9d9047f1bb401f1b86ff4775e1ddf638f4b4992bbc362a27a6f56673"}, + {file = "casadi-3.5.5-cp37-none-manylinux1_i686.whl", hash = "sha256:1c451a07b2440c00d552e040b6285b6e79b677d2978212368b28b86f5d267669"}, + {file = "casadi-3.5.5-cp37-none-manylinux1_x86_64.whl", hash = "sha256:24fbac649ee26572884029dcd0e108b4a2412cad003a84ed915c4e44a94ecae7"}, + {file = "casadi-3.5.5-cp37-none-manylinux2014_aarch64.whl", hash = "sha256:a06c0b96eb9d3bc88c627eec6e465726934ca0394347dc33efc742b8c91db83d"}, + {file = "casadi-3.5.5-cp37-none-win32.whl", hash = "sha256:36db4c84d8f3aad328faaeaeaa454a633c95a854d78ea188791b147888379342"}, + {file = "casadi-3.5.5-cp37-none-win_amd64.whl", hash = "sha256:643e48f92eaf65eb82964816bb7e7064ddb8239959210fa6168e8bce6fe6ef94"}, + {file = "casadi-3.5.5-cp38-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:6ce7ac8a301a145f98d46db0bfd13bc8b3831a5bb92e8054d531a1f233bb4b93"}, + {file = "casadi-3.5.5-cp38-none-manylinux1_i686.whl", hash = "sha256:473bb86fa64ac9703d74a474514703b4665fa9a384221ced620b5025e64532a7"}, + {file = "casadi-3.5.5-cp38-none-manylinux1_x86_64.whl", hash = "sha256:292e2768280393bad406256e0ef9c30ddcd4867dbd42148b36f9d92a32d9e199"}, + {file = "casadi-3.5.5-cp38-none-manylinux2014_aarch64.whl", hash = "sha256:353a79e50aa84ac5e0d9f04bc3b2d78a2cc8edae3b842d757756449682778944"}, + {file = "casadi-3.5.5-cp38-none-win32.whl", hash = "sha256:77f33cb95be6a49b93d8d6b81f05193676ae09857699cedf8f1a14a4285d077e"}, + {file = "casadi-3.5.5-cp38-none-win_amd64.whl", hash = "sha256:fbf39dcd63f1d3b63c300fce59b7ea678bd5ea1d014e1e090a5226600a4132cb"}, + {file = "casadi-3.5.5-cp39-none-macosx_10_13_x86_64.macosx_10_13_intel.whl", hash = "sha256:4086669280b2335d235c664373db46dcd7f6485dba4663ce1944ea01753c5e8b"}, + {file = "casadi-3.5.5-cp39-none-manylinux1_i686.whl", hash = "sha256:c3440c90c31b61ae1df82f6c784643393f723354dc08013f9d5cedf25507c67c"}, + {file = "casadi-3.5.5-cp39-none-manylinux1_x86_64.whl", hash = "sha256:bd94048388b602fc30fdac2fecb986c034110ed8d2d17af7fd13b0de45c58bd7"}, + {file = "casadi-3.5.5-cp39-none-manylinux2014_aarch64.whl", hash = "sha256:cd630a2e6ec6df6a4977af63080fa8d63a0053ff8c06ea0200959b47ae75201c"}, + {file = "casadi-3.5.5-cp39-none-win32.whl", hash = "sha256:ac45b91616e9b8afbe266ca08e80770b28e9e6d7a5852e3677fb37e42bde2047"}, + {file = "casadi-3.5.5-cp39-none-win_amd64.whl", hash = "sha256:55df534d003efdd120c4ebfeb6b252c443d273cdc4b97a394eb0268367477795"}, +] +certifi = [ + {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, + {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, +] +cffi = [ + {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, + {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, + {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, + {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, + {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, + {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, + {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, + {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, + {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, + {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, + {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, + {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, + {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, + {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, + {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, + {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, + {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, + {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, + {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, + {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, + {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, + {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, + {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, + {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, + {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, + {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, + {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, + {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, + {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, + {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, + {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, + {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, + {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, +] +cfgv = [ + {file = "cfgv-3.3.1-py2.py3-none-any.whl", hash = "sha256:c6a0883f3917a037485059700b9e75da2464e6c27051014ad85ba6aaa5884426"}, + {file = "cfgv-3.3.1.tar.gz", hash = "sha256:f5a830efb9ce7a445376bb66ec94c638a9787422f96264c98edc6bdeed8ab736"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.1.1.tar.gz", hash = "sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845"}, + {file = "charset_normalizer-2.1.1-py3-none-any.whl", hash = "sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"}, +] +cleo = [ + {file = "cleo-1.0.0a5-py3-none-any.whl", hash = "sha256:ff53056589300976e960f75afb792dfbfc9c78dcbb5a448e207a17b643826360"}, + {file = "cleo-1.0.0a5.tar.gz", hash = "sha256:097c9d0e0332fd53cc89fc11eb0a6ba0309e6a3933c08f7b38558555486925d3"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +click-plugins = [ + {file = "click-plugins-1.1.1.tar.gz", hash = "sha256:46ab999744a9d831159c3411bb0c79346d94a444df9a3a3742e9ed63645f264b"}, + {file = "click_plugins-1.1.1-py2.py3-none-any.whl", hash = "sha256:5d262006d3222f5057fd81e1623d4443e41dcda5dc815c06b442aa3c02889fc8"}, +] +cligj = [ + {file = "cligj-0.7.2-py3-none-any.whl", hash = "sha256:c1ca117dbce1fe20a5809dc96f01e1c2840f6dcc939b3ddbb1111bf330ba82df"}, + {file = "cligj-0.7.2.tar.gz", hash = "sha256:a4bc13d623356b373c2c27c53dbd9c68cae5d526270bfa71f6c6fa69669c6b27"}, +] +cloudpickle = [ + {file = "cloudpickle-2.2.0-py3-none-any.whl", hash = "sha256:7428798d5926d8fcbfd092d18d01a2a03daf8237d8fcdc8095d256b8490796f0"}, + {file = "cloudpickle-2.2.0.tar.gz", hash = "sha256:3f4219469c55453cfe4737e564b67c2a149109dabf7f242478948b895f61106f"}, +] +cmake = [ + {file = "cmake-3.26.0-py2.py3-none-macosx_10_10_universal2.macosx_10_10_x86_64.macosx_11_0_arm64.macosx_11_0_universal2.whl", hash = "sha256:4881727389325af84e39f1ec646b7249d8910b4ed637205bee6d589cb2b2ebd2"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2010_i686.manylinux_2_12_i686.whl", hash = "sha256:babd1e38c85d38a4bf4164c3126ec8cf4cd8d374072e2a4a181e52e953007f8c"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:1851be29d79bb39505954165e934d31994268d49f566ead6fff840a5092e444d"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:483aaaeb9535deaa2657c928af5d9f0da9329f89bc249f494923495745a03677"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_i686.manylinux_2_17_i686.whl", hash = "sha256:2f78c6194f224d462333e08d3acd571c553b58d04935971b87efbe76f241353c"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:cf129c8b71f1344975f179f30287baa8804c4c61ff1b13003244b4157b676e13"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:fabb82c69223eda27e673c5c6bb02985d60cf0baa631a2e4932eed87e8229928"}, + {file = "cmake-3.26.0-py2.py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:502cbed2335557920b88687c6f3ff4ce433bd416204c928ad489e399e76149f5"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:b81c7775b125786c1696232078e9ceb0c9c66d26fb0a2259f40e33983170f924"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_i686.whl", hash = "sha256:f4cc1dde7613cc813d15fd840a33e18cd07a443aa67205d74b9b05f55f08a459"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_ppc64le.whl", hash = "sha256:ac6ca9d9ff58900138bbbebc55857647fb99f1c40b84d8a232f23b0d27d7f48a"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_s390x.whl", hash = "sha256:85e0bad5aeb3a82919ed7d78b76a0462eafe2f918076a1823a09c6f37910f3e0"}, + {file = "cmake-3.26.0-py2.py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:5523b9701be367572155e77294bf8aa3ac9aa0d73c50dcd6c57903d12bcb308e"}, + {file = "cmake-3.26.0-py2.py3-none-win32.whl", hash = "sha256:96f0e887260255eeb83bcf3465d51bc6c94078251c9312fa142dce6d3e80acac"}, + {file = "cmake-3.26.0-py2.py3-none-win_amd64.whl", hash = "sha256:a0719a6f79cdc4d7b16caf757b8ae13eae1c6ce5a08d594cc09774afe129515d"}, + {file = "cmake-3.26.0-py2.py3-none-win_arm64.whl", hash = "sha256:11159c9b64c6473d84361ab44ef2c85e1bd2db7a2b1b798ce8bf5988f32adf43"}, + {file = "cmake-3.26.0.tar.gz", hash = "sha256:c18185c9cc147d0fa1e9228962aa37901b37866bd5d617e9efa23dfe706f7321"}, +] +colorama = [ + {file = "colorama-0.4.5-py2.py3-none-any.whl", hash = "sha256:854bf444933e37f5824ae7bfc1e98d5bce2ebe4160d46b5edf346a89358e99da"}, + {file = "colorama-0.4.5.tar.gz", hash = "sha256:e6c6b4334fc50988a639d9b98aa429a0b57da6e17b9a44f0451f930b6967b7a4"}, +] +coloredlogs = [ + {file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934"}, + {file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0"}, +] +configargparse = [ + {file = "ConfigArgParse-1.5.3-py3-none-any.whl", hash = "sha256:18f6535a2db9f6e02bd5626cc7455eac3e96b9ab3d969d366f9aafd5c5c00fe7"}, + {file = "ConfigArgParse-1.5.3.tar.gz", hash = "sha256:1b0b3cbf664ab59dada57123c81eff3d9737e0d11d8cf79e3d6eb10823f1739f"}, +] +contourpy = [ + {file = "contourpy-1.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:87121b9428ac568fb84fae4af5e7852fc34f02eadc4e3e91f6c8989327692186"}, + {file = "contourpy-1.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1fb782982c42cee667b892a0b0c52a9f6c7ecf1da5c5f4345845f04eaa862f93"}, + {file = "contourpy-1.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:689d7d2a840619915d0abd1ecc6e399fee202f8ad315acda2807f4ca420d0802"}, + {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d88814befbd1433152c5f6dd536905149ba028d795a22555b149ae0a36024d9e"}, + {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df65f4b2b4e74977f0336bef12a88051ab24e6a16873cd9249f34d67cb3e345d"}, + {file = "contourpy-1.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6b4c0c723664f65c2a47c8cb6ebbf660b0b2e2d936adf2e8503d4e93359465"}, + {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bcc98d397c3dea45d5b262029564b29cb8e945f2607a38bee6163694c0a8b4ef"}, + {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:2bf5c846c257578b03d498b20f54f53551616a507d8e5463511c58bb58e9a9cf"}, + {file = "contourpy-1.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:cdacddb18d55ffec42d1907079cdc04ec4fa8a990cdf5b9d9fe67d281fc0d12e"}, + {file = "contourpy-1.0.5-cp310-cp310-win32.whl", hash = "sha256:434942fa2f9019b9ae525fb752dc523800c49a1a28fbd6d9240b0fa959573dcc"}, + {file = "contourpy-1.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:3b3082ade8849130203d461b98c2a061b382c46074b43b4edd5cefd81af92b8a"}, + {file = "contourpy-1.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:057114f698ffb9e54657e8fda6802e2f5c8fad609845cf6afaf31590ef6a33c0"}, + {file = "contourpy-1.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:218722a29c5c26677d37c44f5f8a372daf6f07870aad793a97d47eb6ad6b3290"}, + {file = "contourpy-1.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6c02e22cf09996194bcb3a4784099975cf527d5c29caf759abadf29ebdb2fe27"}, + {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c0d5ee865b5fd16bf62d72122aadcc90aab296c30c1adb0a32b4b66bd843163e"}, + {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d45822b0a2a452327ab4f95efe368d234d5294bbf89a99968be27c7938a21108"}, + {file = "contourpy-1.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dca5be83a6dfaf933a46e3bc2b9f2685e5ec61b22f6a38ad740aac9c16e9a0ff"}, + {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:3c3f2f6b898a40207843ae01970e57e33d22a26b22f23c6a5e07b4716751085f"}, + {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2b4eab7c12f9cb460509bc34a3b086f9802f0dba27c89a63df4123819ad64af"}, + {file = "contourpy-1.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09ed9b63f4df8a7591b7a4a26c1ad066dcaafda1f846250fdcb534074a411692"}, + {file = "contourpy-1.0.5-cp311-cp311-win32.whl", hash = "sha256:f670686d99c867d0f24b28ce8c6f02429c6eef5e2674aab287850d0ee2d20437"}, + {file = "contourpy-1.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:c51568e94f7f232296de30002f2a50f77a7bd346673da3e4f2aaf9d2b833f2e5"}, + {file = "contourpy-1.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7c9e99aac7b430f6a9f15eebf058c742097cea3369f23a2bfc5e64d374b67e3a"}, + {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3210d93ad2af742b6a96cf39792f7181822edbb8fe11c3ef29d1583fe637a8d8"}, + {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128bd7acf569f8443ad5b2227f30ac909e4f5399ed221727eeacf0c6476187e6"}, + {file = "contourpy-1.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:813c2944e940ef8dccea71305bacc942d4b193a021140874b3e58933ec44f5b6"}, + {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a74afd8d560eaafe0d9e3e1db8c06081282a05ca4de00ee416195085a79d7d3d"}, + {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d0ad9a85f208473b1f3613c45756c7aa6fcc288266a8c7b873f896aaf741b6b"}, + {file = "contourpy-1.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:60f37acd4e4227c5a29f737d9a85ca3145c529a8dd4bf70af7f0637c61b49222"}, + {file = "contourpy-1.0.5-cp37-cp37m-win32.whl", hash = "sha256:b50e481a4317a8efcfffcfddcd4c9b36eacba440440e70cbe0256aeb6fd6abae"}, + {file = "contourpy-1.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:0395ae71164bfeb2dedd136e03c71a2718a5aa9873a46f518f4133be0d63e1d2"}, + {file = "contourpy-1.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:3ca40d7844b391d90b864c6a6d1bb6b88b09035fb4d866d64d43c4d26fb0ab64"}, + {file = "contourpy-1.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3109fa601d2a448cec4643abd3a31f972bf05b7c2f2e83df9d3429878f8c10ae"}, + {file = "contourpy-1.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:06c4d1dde5ee4f909a8a95ba1eb04040c6c26946b4f3b5beaf10d45f14e940ee"}, + {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2f54dcc9bb9390fd0636301ead134d46d5229fe86da0db4d974c0fda349f560e"}, + {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46b8e24813e2fb5a3e598c1f8b9ae403e1438cb846a80cc2b33cddf19dddd7f2"}, + {file = "contourpy-1.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:061e1f066c419ffe25b615a1df031b4832ea1d7f2676937e69e8e00e24512005"}, + {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:19ea64fa0cf389d2ebc10974616acfa1fdecbd73d1fd9c72215b782f3c40f561"}, + {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:dfe924e5a63861c82332a12adeeab955dc8c8009ddbbd80cc2fcca049ff89a49"}, + {file = "contourpy-1.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:bed3a2a823a041e8d249b1a7ec132933e1505299329b5cfe1b2b5ec689ec7675"}, + {file = "contourpy-1.0.5-cp38-cp38-win32.whl", hash = "sha256:0389349875424aa8c5e61f757e894687916bc4e9616cc6afcbd8051aa2428952"}, + {file = "contourpy-1.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:2b5e334330d82866923015b455260173cb3b9e3b4e297052d758abd262031289"}, + {file = "contourpy-1.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:def9a01b73c9e27d70ea03b381fb3e7aadfac1f398dbd63751313c3a46747ef5"}, + {file = "contourpy-1.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:59c827e536bb5e3ef58e06da0faba61fd89a14f30b68bcfeca41f43ca83a1942"}, + {file = "contourpy-1.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f05d311c937da03b0cd26ac3e14cb991f6ff8fc94f98b3df9713537817539795"}, + {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:970a4be7ec84ccda7c27cb4ae74930bbbd477bc8d849ed55ea798084dd5fca8c"}, + {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0f7672148f8fca48e4efc16aba24a7455b40c22d4f8abe42475dec6a12b0bb9a"}, + {file = "contourpy-1.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eba62b7c21a33e72dd8adab2b92dd5610d8527f0b2ac28a8e0770e71b21a13f9"}, + {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:dd084459ecdb224e617e4ab3f1d5ebe4d1c48facb41f24952b76aa6ba9712bb0"}, + {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c5158616ab39d34b76c50f40c81552ee180598f7825dc7a66fd187d29958820f"}, + {file = "contourpy-1.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f856652f9b533c6cd2b9ad6836a7fc0e43917d7ff15be46c5baf1350f8cdc5d9"}, + {file = "contourpy-1.0.5-cp39-cp39-win32.whl", hash = "sha256:f1cc623fd6855b25da52b3275e0c9e51711b86a9dccc75f8c9ab4432fd8e42c7"}, + {file = "contourpy-1.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:e67dcaa34dcd908fcccbf49194211d847c731b6ebaac661c1c889f1bf6af1e44"}, + {file = "contourpy-1.0.5-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bfd634cb9685161b2a51f73a7fc4736fd0d67a56632d52319317afaa27f08243"}, + {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:79908b9d02b1d6c1c71ff3b7ad127f3f82e14a8e091ab44b3c7e34b649fea733"}, + {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b4963cf08f4320d98ae72ec7694291b8ab85cb7da3b0cd824bc32701bc992edf"}, + {file = "contourpy-1.0.5-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3cfc067ddde78b76dcbc9684d82688b7d3c5158fa2254a085f9bcb9586c1e2d8"}, + {file = "contourpy-1.0.5-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:9939796abcadb2810a63dfb26ff8ca4595fe7dd70a3ceae7f607a2639b714307"}, + {file = "contourpy-1.0.5-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d8150579bf30cdf896906baf256aa200cd50dbe6e565c17d6fd3d678e21ff5de"}, + {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed9c91bf4ce614efed5388c3f989a7cfe08728ab871d995a486ea74ff88993db"}, + {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b46a04588ceb7cf132568e0e564a854627ef87a1ed3bf536234540a79ced44b0"}, + {file = "contourpy-1.0.5-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b85553699862c09937a7a5ea14ee6229087971a7d51ae97d5f4b407f571a2c17"}, + {file = "contourpy-1.0.5-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:99a8071e351b50827ad976b92ed91845fb614ac67a3c41109b24f3d8bd3afada"}, + {file = "contourpy-1.0.5-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:fb0458d74726937ead9e2effc91144aea5a58ecee9754242f8539a782bed685a"}, + {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f89f0608a5aa8142ed0e53957916623791a88c7f5e5f07ae530c328beeb888f"}, + {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce763369e646e59e4ca2c09735cd1bdd3048d909ad5f2bc116e83166a9352f3c"}, + {file = "contourpy-1.0.5-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c16fa267740d67883899e054cccb4279e002f3f4872873b752c1ba15045ff49"}, + {file = "contourpy-1.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a30e95274f5c0e007ccc759ec258aa5708c534ec058f153ee25ac700a2f1438b"}, + {file = "contourpy-1.0.5.tar.gz", hash = "sha256:896631cd40222aef3697e4e51177d14c3709fda49d30983269d584f034acc8a4"}, +] +control = [ + {file = "control-0.9.2.tar.gz", hash = "sha256:0891d2d32d6006ac1faa4e238ed8223ca342a4721d202dfeccae24fb02563183"}, +] +coverage = [ + {file = "coverage-6.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ef8674b0ee8cc11e2d574e3e2998aea5df5ab242e012286824ea3c6970580e53"}, + {file = "coverage-6.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:784f53ebc9f3fd0e2a3f6a78b2be1bd1f5575d7863e10c6e12504f240fd06660"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b4a5be1748d538a710f87542f22c2cad22f80545a847ad91ce45e77417293eb4"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83516205e254a0cb77d2d7bb3632ee019d93d9f4005de31dca0a8c3667d5bc04"}, + {file = "coverage-6.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:af4fffaffc4067232253715065e30c5a7ec6faac36f8fc8d6f64263b15f74db0"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:97117225cdd992a9c2a5515db1f66b59db634f59d0679ca1fa3fe8da32749cae"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a1170fa54185845505fbfa672f1c1ab175446c887cce8212c44149581cf2d466"}, + {file = "coverage-6.5.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:11b990d520ea75e7ee8dcab5bc908072aaada194a794db9f6d7d5cfd19661e5a"}, + {file = "coverage-6.5.0-cp310-cp310-win32.whl", hash = "sha256:5dbec3b9095749390c09ab7c89d314727f18800060d8d24e87f01fb9cfb40b32"}, + {file = "coverage-6.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:59f53f1dc5b656cafb1badd0feb428c1e7bc19b867479ff72f7a9dd9b479f10e"}, + {file = "coverage-6.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4a5375e28c5191ac38cca59b38edd33ef4cc914732c916f2929029b4bfb50795"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ed2820d919351f4167e52425e096af41bfabacb1857186c1ea32ff9983ed75"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:33a7da4376d5977fbf0a8ed91c4dffaaa8dbf0ddbf4c8eea500a2486d8bc4d7b"}, + {file = "coverage-6.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8fb6cf131ac4070c9c5a3e21de0f7dc5a0fbe8bc77c9456ced896c12fcdad91"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a6b7d95969b8845250586f269e81e5dfdd8ff828ddeb8567a4a2eaa7313460c4"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1ef221513e6f68b69ee9e159506d583d31aa3567e0ae84eaad9d6ec1107dddaa"}, + {file = "coverage-6.5.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cca4435eebea7962a52bdb216dec27215d0df64cf27fc1dd538415f5d2b9da6b"}, + {file = "coverage-6.5.0-cp311-cp311-win32.whl", hash = "sha256:98e8a10b7a314f454d9eff4216a9a94d143a7ee65018dd12442e898ee2310578"}, + {file = "coverage-6.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:bc8ef5e043a2af066fa8cbfc6e708d58017024dc4345a1f9757b329a249f041b"}, + {file = "coverage-6.5.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4433b90fae13f86fafff0b326453dd42fc9a639a0d9e4eec4d366436d1a41b6d"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4f05d88d9a80ad3cac6244d36dd89a3c00abc16371769f1340101d3cb899fc3"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94e2565443291bd778421856bc975d351738963071e9b8839ca1fc08b42d4bef"}, + {file = "coverage-6.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:027018943386e7b942fa832372ebc120155fd970837489896099f5cfa2890f79"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:255758a1e3b61db372ec2736c8e2a1fdfaf563977eedbdf131de003ca5779b7d"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:851cf4ff24062c6aec510a454b2584f6e998cada52d4cb58c5e233d07172e50c"}, + {file = "coverage-6.5.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:12adf310e4aafddc58afdb04d686795f33f4d7a6fa67a7a9d4ce7d6ae24d949f"}, + {file = "coverage-6.5.0-cp37-cp37m-win32.whl", hash = "sha256:b5604380f3415ba69de87a289a2b56687faa4fe04dbee0754bfcae433489316b"}, + {file = "coverage-6.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:4a8dbc1f0fbb2ae3de73eb0bdbb914180c7abfbf258e90b311dcd4f585d44bd2"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d900bb429fdfd7f511f868cedd03a6bbb142f3f9118c09b99ef8dc9bf9643c3c"}, + {file = "coverage-6.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2198ea6fc548de52adc826f62cb18554caedfb1d26548c1b7c88d8f7faa8f6ba"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c4459b3de97b75e3bd6b7d4b7f0db13f17f504f3d13e2a7c623786289dd670e"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20c8ac5386253717e5ccc827caad43ed66fea0efe255727b1053a8154d952398"}, + {file = "coverage-6.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b07130585d54fe8dff3d97b93b0e20290de974dc8177c320aeaf23459219c0b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:dbdb91cd8c048c2b09eb17713b0c12a54fbd587d79adcebad543bc0cd9a3410b"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:de3001a203182842a4630e7b8d1a2c7c07ec1b45d3084a83d5d227a3806f530f"}, + {file = "coverage-6.5.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e07f4a4a9b41583d6eabec04f8b68076ab3cd44c20bd29332c6572dda36f372e"}, + {file = "coverage-6.5.0-cp38-cp38-win32.whl", hash = "sha256:6d4817234349a80dbf03640cec6109cd90cba068330703fa65ddf56b60223a6d"}, + {file = "coverage-6.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:7ccf362abd726b0410bf8911c31fbf97f09f8f1061f8c1cf03dfc4b6372848f6"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:633713d70ad6bfc49b34ead4060531658dc6dfc9b3eb7d8a716d5873377ab745"}, + {file = "coverage-6.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:95203854f974e07af96358c0b261f1048d8e1083f2de9b1c565e1be4a3a48cfc"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9023e237f4c02ff739581ef35969c3739445fb059b060ca51771e69101efffe"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:265de0fa6778d07de30bcf4d9dc471c3dc4314a23a3c6603d356a3c9abc2dfcf"}, + {file = "coverage-6.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f830ed581b45b82451a40faabb89c84e1a998124ee4212d440e9c6cf70083e5"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7b6be138d61e458e18d8e6ddcddd36dd96215edfe5f1168de0b1b32635839b62"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42eafe6778551cf006a7c43153af1211c3aaab658d4d66fa5fcc021613d02518"}, + {file = "coverage-6.5.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:723e8130d4ecc8f56e9a611e73b31219595baa3bb252d539206f7bbbab6ffc1f"}, + {file = "coverage-6.5.0-cp39-cp39-win32.whl", hash = "sha256:d9ecf0829c6a62b9b573c7bb6d4dcd6ba8b6f80be9ba4fc7ed50bf4ac9aecd72"}, + {file = "coverage-6.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:fc2af30ed0d5ae0b1abdb4ebdce598eafd5b35397d4d75deb341a614d333d987"}, + {file = "coverage-6.5.0-pp36.pp37.pp38-none-any.whl", hash = "sha256:1431986dac3923c5945271f169f59c45b8802a114c8f548d611f2015133df77a"}, + {file = "coverage-6.5.0.tar.gz", hash = "sha256:f642e90754ee3e06b0e7e51bce3379590e76b7f76b708e1a71ff043f87025c84"}, +] +crashtest = [ + {file = "crashtest-0.3.1-py3-none-any.whl", hash = "sha256:300f4b0825f57688b47b6d70c6a31de33512eb2fa1ac614f780939aa0cf91680"}, + {file = "crashtest-0.3.1.tar.gz", hash = "sha256:42ca7b6ce88b6c7433e2ce47ea884e91ec93104a4b754998be498a8e6c3d37dd"}, +] +crcmod = [ + {file = "crcmod-1.7.tar.gz", hash = "sha256:dc7051a0db5f2bd48665a990d3ec1cc305a466a77358ca4492826f41f283601e"}, +] +cryptography = [ + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:549153378611c0cca1042f20fd9c5030d37a72f634c9326e225c9f666d472884"}, + {file = "cryptography-37.0.4-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a958c52505c8adf0d3822703078580d2c0456dd1d27fabfb6f76fe63d2971cd6"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f721d1885ecae9078c3f6bbe8a88bc0786b6e749bf32ccec1ef2b18929a05046"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:3d41b965b3380f10e4611dbae366f6dc3cefc7c9ac4e8842a806b9672ae9add5"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:80f49023dd13ba35f7c34072fa17f604d2f19bf0989f292cedf7ab5770b87a0b"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2dcb0b3b63afb6df7fd94ec6fbddac81b5492513f7b0436210d390c14d46ee8"}, + {file = "cryptography-37.0.4-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:b7f8dd0d4c1f21759695c05a5ec8536c12f31611541f8904083f3dc582604280"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:30788e070800fec9bbcf9faa71ea6d8068f5136f60029759fd8c3efec3c9dcb3"}, + {file = "cryptography-37.0.4-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:190f82f3e87033821828f60787cfa42bff98404483577b591429ed99bed39d59"}, + {file = "cryptography-37.0.4-cp36-abi3-win32.whl", hash = "sha256:b62439d7cd1222f3da897e9a9fe53bbf5c104fff4d60893ad1355d4c14a24157"}, + {file = "cryptography-37.0.4-cp36-abi3-win_amd64.whl", hash = "sha256:f7a6de3e98771e183645181b3627e2563dcde3ce94a9e42a3f427d2255190327"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc95ed67b6741b2607298f9ea4932ff157e570ef456ef7ff0ef4884a134cc4b"}, + {file = "cryptography-37.0.4-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:f8c0a6e9e1dd3eb0414ba320f85da6b0dcbd543126e30fcc546e7372a7fbf3b9"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:e007f052ed10cc316df59bc90fbb7ff7950d7e2919c9757fd42a2b8ecf8a5f67"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bc997818309f56c0038a33b8da5c0bfbb3f1f067f315f9abd6fc07ad359398d"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:d204833f3c8a33bbe11eda63a54b1aad7aa7456ed769a982f21ec599ba5fa282"}, + {file = "cryptography-37.0.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:75976c217f10d48a8b5a8de3d70c454c249e4b91851f6838a4e48b8f41eb71aa"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:7099a8d55cd49b737ffc99c17de504f2257e3787e02abe6d1a6d136574873441"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2be53f9f5505673eeda5f2736bea736c40f051a739bfae2f92d18aed1eb54596"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:91ce48d35f4e3d3f1d83e29ef4a9267246e6a3be51864a5b7d2247d5086fa99a"}, + {file = "cryptography-37.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:4c590ec31550a724ef893c50f9a97a0c14e9c851c85621c5650d699a7b88f7ab"}, + {file = "cryptography-37.0.4.tar.gz", hash = "sha256:63f9c17c0e2474ccbebc9302ce2f07b55b3b3fcb211ded18a42d5764f5c10a82"}, +] +cupy-cuda11x = [ + {file = "cupy_cuda11x-11.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:1b9914f57868a1559e9bfabfbae8c724585914e8e1f277acb9cdb6aa0756eaa4"}, + {file = "cupy_cuda11x-11.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:ac8dd082ddb00996bc4d37cc5765907048f467aadb61bcbff25f3c2a88c50583"}, + {file = "cupy_cuda11x-11.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:bf7496340dfbc2eaa3e0ebfd03c4ab8b1fc36d7d14f68718c33eeb395aaa6eed"}, + {file = "cupy_cuda11x-11.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:e3bbd55c26d60069d7e5af2200fb47e7c42ca6437acac297637a2d3c00f6fe46"}, + {file = "cupy_cuda11x-11.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:0d7ebcfd7234946719ad28890593d9fc78fad5753ef4b073e0d7bdb7da2f5640"}, + {file = "cupy_cuda11x-11.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3bc3f81fbc5a86c7155c6036809f2ada4023fc0870dce158d3d9f6d0b575727f"}, + {file = "cupy_cuda11x-11.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:1d6810568e683cb153972b3019ddb5efc369036511122117d9eda09cf84d1042"}, + {file = "cupy_cuda11x-11.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:31c1ec72ffe9ad6fda9fb0a2aff1fcca38da66a8e521e333bae0d67ebc80ead0"}, + {file = "cupy_cuda11x-11.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:6a4822daadfce0464cb619099eb82c7ba30ae7755d6869ba5eba1c675a9eed67"}, + {file = "cupy_cuda11x-11.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:3d0f44d747b38ce2b213d64270265f953b68cf29860ec5fa65848b9faaea3fc1"}, +] +cycler = [ + {file = "cycler-0.11.0-py3-none-any.whl", hash = "sha256:3a27e95f763a428a739d2add979fa7494c912a32c17c4c38c4d5f082cad165a3"}, + {file = "cycler-0.11.0.tar.gz", hash = "sha256:9c87405839a19696e837b3b818fed3f5f69f16f1eec1a1ad77e043dcea9c772f"}, +] +cython = [ + {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:39afb4679b8c6bf7ccb15b24025568f4f9b4d7f9bf3cbd981021f542acecd75b"}, + {file = "Cython-0.29.32-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:dbee03b8d42dca924e6aa057b836a064c769ddfd2a4c2919e65da2c8a362d528"}, + {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ba622326f2862f9c1f99ca8d47ade49871241920a352c917e16861e25b0e5c3"}, + {file = "Cython-0.29.32-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e6ffa08aa1c111a1ebcbd1cf4afaaec120bc0bbdec3f2545f8bb7d3e8e77a1cd"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:97335b2cd4acebf30d14e2855d882de83ad838491a09be2011745579ac975833"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:06be83490c906b6429b4389e13487a26254ccaad2eef6f3d4ee21d8d3a4aaa2b"}, + {file = "Cython-0.29.32-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:eefd2b9a5f38ded8d859fe96cc28d7d06e098dc3f677e7adbafda4dcdd4a461c"}, + {file = "Cython-0.29.32-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5514f3b4122cb22317122a48e175a7194e18e1803ca555c4c959d7dfe68eaf98"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:656dc5ff1d269de4d11ee8542f2ffd15ab466c447c1f10e5b8aba6f561967276"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:cdf10af3e2e3279dc09fdc5f95deaa624850a53913f30350ceee824dc14fc1a6"}, + {file = "Cython-0.29.32-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:3875c2b2ea752816a4d7ae59d45bb546e7c4c79093c83e3ba7f4d9051dd02928"}, + {file = "Cython-0.29.32-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:79e3bab19cf1b021b613567c22eb18b76c0c547b9bc3903881a07bfd9e7e64cf"}, + {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b0595aee62809ba353cebc5c7978e0e443760c3e882e2c7672c73ffe46383673"}, + {file = "Cython-0.29.32-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0ea8267fc373a2c5064ad77d8ff7bf0ea8b88f7407098ff51829381f8ec1d5d9"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c8e8025f496b5acb6ba95da2fb3e9dacffc97d9a92711aacfdd42f9c5927e094"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:afbce249133a830f121b917f8c9404a44f2950e0e4f5d1e68f043da4c2e9f457"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:513e9707407608ac0d306c8b09d55a28be23ea4152cbd356ceaec0f32ef08d65"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e83228e0994497900af954adcac27f64c9a57cd70a9ec768ab0cb2c01fd15cf1"}, + {file = "Cython-0.29.32-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea1dcc07bfb37367b639415333cfbfe4a93c3be340edf1db10964bc27d42ed64"}, + {file = "Cython-0.29.32-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:8669cadeb26d9a58a5e6b8ce34d2c8986cc3b5c0bfa77eda6ceb471596cb2ec3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:ed087eeb88a8cf96c60fb76c5c3b5fb87188adee5e179f89ec9ad9a43c0c54b3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3f85eb2343d20d91a4ea9cf14e5748092b376a64b7e07fc224e85b2753e9070b"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:63b79d9e1f7c4d1f498ab1322156a0d7dc1b6004bf981a8abda3f66800e140cd"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e1958e0227a4a6a2c06fd6e35b7469de50adf174102454db397cec6e1403cce3"}, + {file = "Cython-0.29.32-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:856d2fec682b3f31583719cb6925c6cdbb9aa30f03122bcc45c65c8b6f515754"}, + {file = "Cython-0.29.32-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:479690d2892ca56d34812fe6ab8f58e4b2e0129140f3d94518f15993c40553da"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:67fdd2f652f8d4840042e2d2d91e15636ba2bcdcd92e7e5ffbc68e6ef633a754"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:4a4b03ab483271f69221c3210f7cde0dcc456749ecf8243b95bc7a701e5677e0"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:40eff7aa26e91cf108fd740ffd4daf49f39b2fdffadabc7292b4b7dc5df879f0"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0bbc27abdf6aebfa1bce34cd92bd403070356f28b0ecb3198ff8a182791d58b9"}, + {file = "Cython-0.29.32-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:cddc47ec746a08603037731f5d10aebf770ced08666100bd2cdcaf06a85d4d1b"}, + {file = "Cython-0.29.32-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca3065a1279456e81c615211d025ea11bfe4e19f0c5650b859868ca04b3fcbd"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d968ffc403d92addf20b68924d95428d523436adfd25cf505d427ed7ba3bee8b"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f3fd44cc362eee8ae569025f070d56208908916794b6ab21e139cea56470a2b3"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_24_i686.whl", hash = "sha256:b6da3063c5c476f5311fd76854abae6c315f1513ef7d7904deed2e774623bbb9"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:061e25151c38f2361bc790d3bcf7f9d9828a0b6a4d5afa56fbed3bd33fb2373a"}, + {file = "Cython-0.29.32-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f9944013588a3543fca795fffb0a070a31a243aa4f2d212f118aa95e69485831"}, + {file = "Cython-0.29.32-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:07d173d3289415bb496e72cb0ddd609961be08fe2968c39094d5712ffb78672b"}, + {file = "Cython-0.29.32-py2.py3-none-any.whl", hash = "sha256:eeb475eb6f0ccf6c039035eb4f0f928eb53ead88777e0a760eccb140ad90930b"}, + {file = "Cython-0.29.32.tar.gz", hash = "sha256:8733cf4758b79304f2a4e39ebfac5e92341bce47bcceb26c1254398b2f8c1af7"}, +] +datadog = [ + {file = "datadog-0.44.0-py2.py3-none-any.whl", hash = "sha256:57c4878d3a8351f652792cdba78050274789dcc44313adec096e87f9d3ca5992"}, + {file = "datadog-0.44.0.tar.gz", hash = "sha256:071170f0c7ef22511dbf7f9bd76c4be500ee2d3d52072900a5c87b5495d2c733"}, +] +debugpy = [ + {file = "debugpy-1.6.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:c4b2bd5c245eeb49824bf7e539f95fb17f9a756186e51c3e513e32999d8846f3"}, + {file = "debugpy-1.6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b8deaeb779699350deeed835322730a3efec170b88927debc9ba07a1a38e2585"}, + {file = "debugpy-1.6.3-cp310-cp310-win32.whl", hash = "sha256:fc233a0160f3b117b20216f1169e7211b83235e3cd6749bcdd8dbb72177030c7"}, + {file = "debugpy-1.6.3-cp310-cp310-win_amd64.whl", hash = "sha256:dda8652520eae3945833e061cbe2993ad94a0b545aebd62e4e6b80ee616c76b2"}, + {file = "debugpy-1.6.3-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:d5c814596a170a0a58fa6fad74947e30bfd7e192a5d2d7bd6a12156c2899e13a"}, + {file = "debugpy-1.6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c4cd6f37e3c168080d61d698390dfe2cd9e74ebf80b448069822a15dadcda57d"}, + {file = "debugpy-1.6.3-cp37-cp37m-win32.whl", hash = "sha256:3c9f985944a30cfc9ae4306ac6a27b9c31dba72ca943214dad4a0ab3840f6161"}, + {file = "debugpy-1.6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:5ad571a36cec137ae6ed951d0ff75b5e092e9af6683da084753231150cbc5b25"}, + {file = "debugpy-1.6.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:adcfea5ea06d55d505375995e150c06445e2b20cd12885bcae566148c076636b"}, + {file = "debugpy-1.6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:daadab4403427abd090eccb38d8901afd8b393e01fd243048fab3f1d7132abb4"}, + {file = "debugpy-1.6.3-cp38-cp38-win32.whl", hash = "sha256:6efc30325b68e451118b795eff6fe8488253ca3958251d5158106d9c87581bc6"}, + {file = "debugpy-1.6.3-cp38-cp38-win_amd64.whl", hash = "sha256:86d784b72c5411c833af1cd45b83d80c252b77c3bfdb43db17c441d772f4c734"}, + {file = "debugpy-1.6.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4e255982552b0edfe3a6264438dbd62d404baa6556a81a88f9420d3ed79b06ae"}, + {file = "debugpy-1.6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cca23cb6161ac89698d629d892520327dd1be9321c0960e610bbcb807232b45d"}, + {file = "debugpy-1.6.3-cp39-cp39-win32.whl", hash = "sha256:7c302095a81be0d5c19f6529b600bac971440db3e226dce85347cc27e6a61908"}, + {file = "debugpy-1.6.3-cp39-cp39-win_amd64.whl", hash = "sha256:34d2cdd3a7c87302ba5322b86e79c32c2115be396f3f09ca13306d8a04fe0f16"}, + {file = "debugpy-1.6.3-py2.py3-none-any.whl", hash = "sha256:84c39940a0cac410bf6aa4db00ba174f973eef521fbe9dd058e26bcabad89c4f"}, + {file = "debugpy-1.6.3.zip", hash = "sha256:e8922090514a890eec99cfb991bab872dd2e353ebb793164d5f01c362b9a40bf"}, +] +decorator = [ + {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, + {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, +] +defusedxml = [ + {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, + {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, +] +deprecated = [ + {file = "Deprecated-1.2.13-py2.py3-none-any.whl", hash = "sha256:64756e3e14c8c5eea9795d93c524551432a0be75629f8f29e67ab8caf076c76d"}, + {file = "Deprecated-1.2.13.tar.gz", hash = "sha256:43ac5335da90c31c24ba028af536a91d41d53f9e6901ddb021bcc572ce44e38d"}, +] +dictdiffer = [ + {file = "dictdiffer-0.9.0-py2.py3-none-any.whl", hash = "sha256:442bfc693cfcadaf46674575d2eba1c53b42f5e404218ca2c2ff549f2df56595"}, + {file = "dictdiffer-0.9.0.tar.gz", hash = "sha256:17bacf5fbfe613ccf1b6d512bd766e6b21fb798822a133aa86098b8ac9997578"}, +] +dill = [ + {file = "dill-0.3.5.1-py2.py3-none-any.whl", hash = "sha256:33501d03270bbe410c72639b350e941882a8b0fd55357580fbc873fba0c59302"}, + {file = "dill-0.3.5.1.tar.gz", hash = "sha256:d75e41f3eff1eee599d738e76ba8f4ad98ea229db8b085318aa2b3333a208c86"}, +] +distlib = [ + {file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"}, + {file = "distlib-0.3.6.tar.gz", hash = "sha256:14bad2d9b04d3a36127ac97f30b12a19268f211063d8f8ee4f47108896e11b46"}, +] +docutils = [ + {file = "docutils-0.17.1-py2.py3-none-any.whl", hash = "sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61"}, + {file = "docutils-0.17.1.tar.gz", hash = "sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125"}, +] +dotmap = [ + {file = "dotmap-1.3.30-py3-none-any.whl", hash = "sha256:bd9fa15286ea2ad899a4d1dc2445ed85a1ae884a42effb87c89a6ecce71243c6"}, + {file = "dotmap-1.3.30.tar.gz", hash = "sha256:5821a7933f075fb47563417c0e92e0b7c031158b4c9a6a7e56163479b658b368"}, +] +dulwich = [ + {file = "dulwich-0.20.46-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:6676196e9cf377cde62aa2f5d741e93207437343e0c62368bd0d784c322a3c49"}, + {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a1ca555a3eafe7388d6cb81bb08f34608a1592500f0bd4c26734c91d208a546"}, + {file = "dulwich-0.20.46-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:769442c9657b10fc35ac625beeaf440540c9288c96fcfaba3e58adf745c5cafd"}, + {file = "dulwich-0.20.46-cp310-cp310-win32.whl", hash = "sha256:de22a54f68c6c4e97f9b924abd46da4618536d7934b9849066be9fc5cd31205d"}, + {file = "dulwich-0.20.46-cp310-cp310-win_amd64.whl", hash = "sha256:42fa5a68908556eb6c40f231a67caf6a4660588aad707a9d6b334fa1d8f04bf7"}, + {file = "dulwich-0.20.46-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:3e16376031466848e44aabf3489fafb054482143744b21167dbd168731041c74"}, + {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:153c7512587384a290c60fef330f1ab397a59559e19e8b02a0169ff21b4c69fb"}, + {file = "dulwich-0.20.46-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b68bd815cd2769c75e5a78708eb0440612df19b370a977aa9e01a056baa9ed"}, + {file = "dulwich-0.20.46-cp311-cp311-win32.whl", hash = "sha256:b1339bca70764eb8e780d80c72e7c1cb4651201dc9e43ec5d616bf51eb3bb3a6"}, + {file = "dulwich-0.20.46-cp311-cp311-win_amd64.whl", hash = "sha256:1162fdafb2abdfe66649617061f3853cb26384fade1f6884f6fe6e9c570a7552"}, + {file = "dulwich-0.20.46-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6826512f778eaa47e2e8c0a46cdc555958f9f5286771490b8642b4b508ea5d25"}, + {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:100d39bc18196a07c521fd5f60f78f397493303daa0b8690216864bbc621cd5d"}, + {file = "dulwich-0.20.46-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c4cd2cd7baa81246bdc8c5272d4e9224e2255da7a0618a220aab5e07b9888e9b"}, + {file = "dulwich-0.20.46-cp36-cp36m-win32.whl", hash = "sha256:6eed5a3194d64112605fc0f638f4fa91771495e8674fa3e6d6b33bf150d297d5"}, + {file = "dulwich-0.20.46-cp36-cp36m-win_amd64.whl", hash = "sha256:9ca4d73987f5b0e2e843497876f9bb39a47384a2e50597a85542285f5c890293"}, + {file = "dulwich-0.20.46-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:b9f49de83911eed7adbe83136229837ef9d102e42dbe6aacb1a18be45c997ace"}, + {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d38be7d3a78d608ecab3348f7920d6b9002e7972dd245206dc8075cfdb91621d"}, + {file = "dulwich-0.20.46-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f4b7a7feb966a4669c254b18385fe0b3c639f3b1f5ddef0d9e083364cc762847"}, + {file = "dulwich-0.20.46-cp37-cp37m-win32.whl", hash = "sha256:f9552ac246bceab1c5cdd1ec3cfe9446fe76b9853eaf59d3244df03eb27fd3fe"}, + {file = "dulwich-0.20.46-cp37-cp37m-win_amd64.whl", hash = "sha256:90a075aeb0fdbad7e18b9db3af161e3d635e2b7697b7a4b467e6844a13b0b210"}, + {file = "dulwich-0.20.46-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:8d6fee82cedb2362942d9ef94061901f7e07d7d8674e4c7b6fceeef7822ae275"}, + {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669c6b3d82996518a7fec4604771bd285e23f0860f41f565fef5987265d431d9"}, + {file = "dulwich-0.20.46-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd3eac228117487a959ac8f49ea2787eac34acc69999fe7adae70b23e3c3571c"}, + {file = "dulwich-0.20.46-cp38-cp38-win32.whl", hash = "sha256:92024f572d32680e021219f77015c8b443c38022e502b7f51ad7cf51a6285a36"}, + {file = "dulwich-0.20.46-cp38-cp38-win_amd64.whl", hash = "sha256:d928de1eba0326a2a8a52ed94c9bf7c315ff4db606a1aa3ae688d39574f93267"}, + {file = "dulwich-0.20.46-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:a5d1b7a3a7d84a5dedbb90092e00097357106b9642ac08a96c2ae89ccd8afd9a"}, + {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b739d759c10e2af7c964dcc97fd4e5dc49e8567d080eed8906fc422c79b7fdcf"}, + {file = "dulwich-0.20.46-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fc7a4f633f5468453d5dd84a753cd99d4433f0397437229a0a8b10347935591"}, + {file = "dulwich-0.20.46-cp39-cp39-win32.whl", hash = "sha256:525115c4d1fbf60a5fe98f340b4ca597ba47b2c75d9c5ec750dd0e9115ef8ec6"}, + {file = "dulwich-0.20.46-cp39-cp39-win_amd64.whl", hash = "sha256:73e2585a9fcf1f8cdad8597a0c384c0b365b2e8346463130c96d9ea1478587ae"}, + {file = "dulwich-0.20.46.tar.gz", hash = "sha256:4f0e88ffff5db1523d93d92f1525fe5fa161318ffbaad502c1b9b3be7a067172"}, +] +efficientnet-pytorch = [ + {file = "efficientnet_pytorch-0.6.3.tar.gz", hash = "sha256:6667459336893e9bf6367de3788ba449fed97f65da3b6782bf2204b6273a319f"}, +] +einops = [ + {file = "einops-0.5.0-py3-none-any.whl", hash = "sha256:055de7eeb3cb9e9710ef3085a811090c6b52e809b7044e8785824ed185f486d1"}, + {file = "einops-0.5.0.tar.gz", hash = "sha256:8b7a83cffc1ea88e306df099b7cbb9c3ba5003bd84d05ae44be5655864abb8d3"}, +] +elastic-transport = [ + {file = "elastic-transport-8.4.0.tar.gz", hash = "sha256:b9ad708ceb7fcdbc6b30a96f886609a109f042c0b9d9f2e44403b3133ba7ff10"}, + {file = "elastic_transport-8.4.0-py3-none-any.whl", hash = "sha256:19db271ab79c9f70f8c43f8f5b5111408781a6176b54ab2e54d713b6d9ceb815"}, +] +elasticsearch = [ + {file = "elasticsearch-8.4.3-py3-none-any.whl", hash = "sha256:14c68a96b7bbbf150dd9fca5ff65da9c50e791c0fdba474a328e43828fdd7f42"}, + {file = "elasticsearch-8.4.3.tar.gz", hash = "sha256:d34d43a6c349d15c9d91840f791eeba80fc50ee070caf6695130f56b7f41a02d"}, +] +entrypoints = [ + {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, + {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, +] +execnet = [ + {file = "execnet-1.9.0-py2.py3-none-any.whl", hash = "sha256:a295f7cc774947aac58dde7fdc85f4aa00c42adf5d8f5468fc630c1acf30a142"}, + {file = "execnet-1.9.0.tar.gz", hash = "sha256:8f694f3ba9cc92cab508b152dcfe322153975c29bda272e2fd7f3f00f36e47c5"}, +] +executing = [ + {file = "executing-1.1.1-py2.py3-none-any.whl", hash = "sha256:236ea5f059a38781714a8bfba46a70fad3479c2f552abee3bbafadc57ed111b8"}, + {file = "executing-1.1.1.tar.gz", hash = "sha256:b0d7f8dcc2bac47ce6e39374397e7acecea6fdc380a6d5323e26185d70f38ea8"}, +] +fastcluster = [ + {file = "fastcluster-1.2.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d0e8faef0437a25fd083df70fb86cc65ce3c9c9780d4ae377cbe6521e7746ce0"}, + {file = "fastcluster-1.2.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c8be01f97bc2bf11a9188537864f8e520e1103cdc6007aa2c5d7979b1363b121"}, + {file = "fastcluster-1.2.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:855ab2b7e6fa9b05f19c4f3023dedfb1a35a88d831933d65d0d9e10a070a9e85"}, + {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72503e727887a61a15f9aaa13178798d3994dfec58aa7a943e42dcfda07c0149"}, + {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fcb0973ca0e6978e3242046338c350cbed1493108929231fae9bd35ad05a6d6"}, + {file = "fastcluster-1.2.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9020899b67fe492d0ed87a3e993ec9962c5a0b51ea2df71d86b1766f065f1cef"}, + {file = "fastcluster-1.2.6-cp310-cp310-win32.whl", hash = "sha256:6cf156d4203708348522393c523c2e61c81f5a6a500e0411dcba2b064551ea2f"}, + {file = "fastcluster-1.2.6-cp310-cp310-win_amd64.whl", hash = "sha256:1801c9daa9aa5bbbb0830efe8bd3034b4b7a417e4b8dd353683999be29797df2"}, + {file = "fastcluster-1.2.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cf5acfe1156849279ebd44a8d1fbcbe8b8e21334f7538eda782ae31e7dade9e2"}, + {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb27c13225f5f77f3c5986a27ca27277cce7db12844330cf535019cd38021257"}, + {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5fe543b6d45da27e84e5af6248722475b88943d8ef40d835cbabbb0ba5ee786b"}, + {file = "fastcluster-1.2.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c12224da0b1f2f9d2b3d715dc82ecb1a3a33b990606f97da075cc46bc6d9576f"}, + {file = "fastcluster-1.2.6-cp36-cp36m-win32.whl", hash = "sha256:86a1ad972e83ba48144884fa849f87626346308b650002157123aee67d3b16fe"}, + {file = "fastcluster-1.2.6-cp36-cp36m-win_amd64.whl", hash = "sha256:8d3c9eab8e69cb36dcdd64c8b3200e008aedf88e34d39e01ae6af98a9605ad18"}, + {file = "fastcluster-1.2.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c61be0bad81a21ee3e5bef91469fdd11968f33d41d142c656accba9b2992babe"}, + {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06df1d97edca68b2ffa43d81c3b5f4e4147bc12ab241c6585fadcdeb0bfa23ca"}, + {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab9337b0a6a9b07b6f86fc724972d1ad729c890e2f539c1b33271c2f1f00af8b"}, + {file = "fastcluster-1.2.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4093d5454bcbe48b30e32da5db43056a08889480a96e4555f28c1f7004fc5323"}, + {file = "fastcluster-1.2.6-cp37-cp37m-win32.whl", hash = "sha256:58958a0333e3dfbad198394e9b7dd6254de0a3e622019b057288405b2a4a6bba"}, + {file = "fastcluster-1.2.6-cp37-cp37m-win_amd64.whl", hash = "sha256:03f8efe6435a7b947fa4a420676942d0267dac0d323ec5ead50f1856cc7cf96f"}, + {file = "fastcluster-1.2.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a5ceb39379327316d34613f7c16c06d7a3816aa38f4437b5e8433aa1bf149e2c"}, + {file = "fastcluster-1.2.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0bb54283b4d5ec96f167c7fd31921f169226c1261637434fdae7a69ee3c69573"}, + {file = "fastcluster-1.2.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6e51db0067e65687a5c46f00a11843d0bb15ca759e8a1767eebac8c4f6e3f4df"}, + {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:11748a4e245c745030e9ddd8c2c37e378f8ad8bd8e869d954c84ff674495499f"}, + {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7254f81dc71cd29ef6f2d9747cf97ff907b569c9ef9d9760352391be5b57118c"}, + {file = "fastcluster-1.2.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa4a4c01c5fbec3623e92bc33a9f712ca416ce93255c402f5c904ac4b890ac3c"}, + {file = "fastcluster-1.2.6-cp38-cp38-win32.whl", hash = "sha256:ffdb00782cd63bbf2c45bb048897531e868326dff5081ab9b752d294b0426c1d"}, + {file = "fastcluster-1.2.6-cp38-cp38-win_amd64.whl", hash = "sha256:a952a84453123db0c2336b9a9c86162e99ad0b897bae8213107c055a64effd41"}, + {file = "fastcluster-1.2.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a085e7e13f1afc517358981b2b7ed774dc9abf95f2be0da9a495d9e6b58c4409"}, + {file = "fastcluster-1.2.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6a7c7f51a6d2f5ab58b1d85e9d0af2af9600ec13bb43bc6aafc9085d2c4ccd93"}, + {file = "fastcluster-1.2.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8bac5cf64691060cf86b0752dd385ef1eccff6d24bdb8b60691cf8cbf0e4f9ef"}, + {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:060c1cb3c84942d8d3618385e2c25998ba690c46ec8c73d64477f808abfac3f2"}, + {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e03a228e018457842eb81de85be7af0b5fe8065d666dd093193e3bdcf1f13d2e"}, + {file = "fastcluster-1.2.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a6f8da329c0032f2acaf4beaef958a2db0dae43d3f946f592dad5c29aa82c832"}, + {file = "fastcluster-1.2.6-cp39-cp39-win32.whl", hash = "sha256:eb3f98791427d5d5d02d023b66bcef61e48954edfadae6527ef72d70cf32ec86"}, + {file = "fastcluster-1.2.6-cp39-cp39-win_amd64.whl", hash = "sha256:4b9cfd426966b8037bec2fc03a0d7a9c87313482c699b36ffa1432b49f84ed2e"}, + {file = "fastcluster-1.2.6.tar.gz", hash = "sha256:aab886efa7b6bba7ac124f4498153d053e5a08b822d2254926b7206cdf5a8aa6"}, +] +fastjsonschema = [ + {file = "fastjsonschema-2.16.2-py3-none-any.whl", hash = "sha256:21f918e8d9a1a4ba9c22e09574ba72267a6762d47822db9add95f6454e51cc1c"}, + {file = "fastjsonschema-2.16.2.tar.gz", hash = "sha256:01e366f25d9047816fe3d288cbfc3e10541daf0af2044763f3d0ade42476da18"}, +] +fastrlock = [ + {file = "fastrlock-0.8-cp27-cp27m-win32.whl", hash = "sha256:4d414a5e97a545fee64437586c70bd295d22ac49614eeb76fb67980e32a0d1ae"}, + {file = "fastrlock-0.8-cp27-cp27m-win_amd64.whl", hash = "sha256:b991766baec0113829c5a10bec4c5ec987cab31dfcb1fabf53e370b83b939ef9"}, + {file = "fastrlock-0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5356a15375fab5a8090f255b54122eead504ebcec8a67ac369489e3e315dfd21"}, + {file = "fastrlock-0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8369ce6d228fc3efbfcff5499468200bd12c35efdbc787eeaf8064153bcf0d72"}, + {file = "fastrlock-0.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:09314c5c0f63fe252a3ee038af855d7fb5668d0ccbae94846a35b32487c6256c"}, + {file = "fastrlock-0.8-cp35-cp35m-win32.whl", hash = "sha256:b724a7f5c1d5f9cdc30a13022e6e8cf319812c40dad0fd722b5ea18d2cfc4c4d"}, + {file = "fastrlock-0.8-cp35-cp35m-win_amd64.whl", hash = "sha256:c7ec22a5726467af53950a8baba92327620c0f73c34fab930ce29b94c8645c7e"}, + {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dcd7a05ba34702d0f0e9751d35afb06be21034fcf00197567ff0dcdf48cde8e7"}, + {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:997bc6c4fafda396449e2410a5d32b69d231fdccc5af6de6cefe9d67e5f7157e"}, + {file = "fastrlock-0.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ba3d3dc0dbb1cff2eee033a1f6369c24a86e8c20b427edfceff3f38b48b391c"}, + {file = "fastrlock-0.8-cp36-cp36m-win32.whl", hash = "sha256:f365fc1de226ce96fedbb475c4523b304ce00f9a59beea620dba34e21239f5f3"}, + {file = "fastrlock-0.8-cp36-cp36m-win_amd64.whl", hash = "sha256:7c9c66ab93877272169b017dc24f63c9c94b3f9d55b0b0b8c3f6d9644280120c"}, + {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5d5fd2a76aa9cd451a8820e2427e512620062ffa62ea5732da0ab8888b6be6e"}, + {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:340ae0734305d780aaad0893dba8185f4dbb0575d5fd696b5720eaf800526bbc"}, + {file = "fastrlock-0.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:719743b5b3c2b214fdf63096ac6a68615b4d9b8cb210e9e6ddf0293b38da2c9a"}, + {file = "fastrlock-0.8-cp37-cp37m-win32.whl", hash = "sha256:1cfb5ac6336ff327359a31b690898adf9a813fd44a76196115a3f0afb2367c81"}, + {file = "fastrlock-0.8-cp37-cp37m-win_amd64.whl", hash = "sha256:5aa6b442b12fa1f9041248506c8ea5ae909a92aedd5a139647dce2ee87e5b944"}, + {file = "fastrlock-0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:af9df4c17c3142c9774cd1b9edea586f709c6421f02bcf7614507937b921ea7f"}, + {file = "fastrlock-0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:951a5c4dc6fbe4481082752ce9f21aabcaa1cf6185f683d7b5cbae254f10bb46"}, + {file = "fastrlock-0.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c75985ba70fb4e8af595a0f67f58b96e0dbc53562768208890b4b2ac25b4b52"}, + {file = "fastrlock-0.8-cp38-cp38-win32.whl", hash = "sha256:0b459511349dc96c8961f1d43635bb94cfc2404a8899792a1f9aaa9998fa0eca"}, + {file = "fastrlock-0.8-cp38-cp38-win_amd64.whl", hash = "sha256:7b612474ddacf808c663aa8020db0a78f54a665ccabd9dbe505d3d4c4b3f6044"}, + {file = "fastrlock-0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe85f98fa66de41f929b8e01e4a9c5a7d20e660f605054b847c4ac54fadc8f79"}, + {file = "fastrlock-0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:d2029fb44eac8ab5f7aafb3fcde9419cbdaa07a08f72dac8cd7790dc0f73e596"}, + {file = "fastrlock-0.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3bba2a866c9f6eb1923ead5e70e1da02b3e57c531830eb4fcbd2d8e9530a2d7e"}, + {file = "fastrlock-0.8-cp39-cp39-win32.whl", hash = "sha256:85c36ac55dc917c7e1f4fb03a841fe84b3fb2669dd0306bb94bf62c5749b1a7f"}, + {file = "fastrlock-0.8-cp39-cp39-win_amd64.whl", hash = "sha256:4bb33625117bc9f1a66a5049a5d03ac4b38a44cb8eefdff1a394f7a435da54c9"}, + {file = "fastrlock-0.8.tar.gz", hash = "sha256:9cc100ed0924b32173d7de705a82fdf1257cdf60af1952a13f64759307b40931"}, +] +filelock = [ + {file = "filelock-3.8.0-py3-none-any.whl", hash = "sha256:617eb4e5eedc82fc5f47b6d61e4d11cb837c56cb4544e39081099fa17ad109d4"}, + {file = "filelock-3.8.0.tar.gz", hash = "sha256:55447caa666f2198c5b6b13a26d2084d26fa5b115c00d065664b2124680c4edc"}, +] +fiona = [ + {file = "Fiona-1.9.1-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:902b67b2d012c5797b5d7d3cb3b46dcf9a342cf90a7f7e53fb12c83738d19926"}, + {file = "Fiona-1.9.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e2f4535ae2c8446e6b328745a44567478d5a077ed63c888b8c212dddb1e11925"}, + {file = "Fiona-1.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c3d7f3286fb59b93cefefb89014b6fa8413126e180e15c576db859ba936cf334"}, + {file = "Fiona-1.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:692fe64c0f3a39742d6f7a8e420a8387f6aec3b6818b727d2dfc98a0c40e992d"}, + {file = "Fiona-1.9.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a017a39650df0cc541c57cf7de450bb4cee6fd9760eb716323b594c1074634a2"}, + {file = "Fiona-1.9.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c3b52d49bc379fdcfd1250b38e7e00ab24ee14eb765376c793bbe251ffd09d6a"}, + {file = "Fiona-1.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbb0ae851cd4417104c469335a01f938251a8639317f93d422c5c808150bd27"}, + {file = "Fiona-1.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:4a6d8fcbbdafa8af8ac1904628b0267382ed9f9921933d061d7bfc5d3f3daf99"}, + {file = "Fiona-1.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:74f056a84dc52a0b21a2cf024601a69105596f06f28d40b45049948be17b4df2"}, + {file = "Fiona-1.9.1-cp37-cp37m-manylinux2014_x86_64.whl", hash = "sha256:5b9f3f6c782bb4ae2c924eefd373cabdeaaa99f86477b9c7c71eb20c052ee7c5"}, + {file = "Fiona-1.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:5d6183189a7e05e2498d38a1df3ab07e1353fa48e977cbc3a31203927bd06bca"}, + {file = "Fiona-1.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:5f2c14beec98a330aee1fd81fa0447a6aa1d5e0a75d000c0052dbe1f23dd6cfd"}, + {file = "Fiona-1.9.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:585a47ec21f2d198abc112158eaf12a6587a272beb7f001162d8c3b262676666"}, + {file = "Fiona-1.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4368b4054f21518a19dea54ce9ac445c40418c6331c0c99d1531c3ddff05da"}, + {file = "Fiona-1.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:839ed0db23198bb754f0d655d4eeaf5f9c141bef734557e77e95e4dc83e42e7f"}, + {file = "Fiona-1.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:decca3956032ae2d2c19d2f7fa8a4553c43a6e66eb5abe9a05f6ddadcb1bfe5c"}, + {file = "Fiona-1.9.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:cb827fa0030d03d8080723137c74b865ec18dbade87c02ed60215491a315c6be"}, + {file = "Fiona-1.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7e0f36484757baa8cd1c0602941e029ff992282776f9afae4c5b90f501ff005"}, + {file = "Fiona-1.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:f98933b552adf0506799097934a6950de412288e7a50466144d04874d6f63fbc"}, + {file = "Fiona-1.9.1.tar.gz", hash = "sha256:3a3725e94840a387fef48726d60db6a6791563f366939d22378a4661f8941be7"}, +] +flake8 = [ + {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, + {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, +] +flask = [ + {file = "Flask-2.2.2-py3-none-any.whl", hash = "sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526"}, + {file = "Flask-2.2.2.tar.gz", hash = "sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b"}, +] +flask-cors = [ + {file = "Flask-Cors-3.0.10.tar.gz", hash = "sha256:b60839393f3b84a0f3746f6cdca56c1ad7426aa738b70d6c61375857823181de"}, + {file = "Flask_Cors-3.0.10-py2.py3-none-any.whl", hash = "sha256:74efc975af1194fc7891ff5cd85b0f7478be4f7f59fe158102e91abb72bb4438"}, +] +flask-socketio = [ + {file = "Flask-SocketIO-5.3.1.tar.gz", hash = "sha256:fd0ed0fc1341671d92d5f5b2f5503916deb7aa7e2940e6636cfa2c087c828bf9"}, + {file = "Flask_SocketIO-5.3.1-py3-none-any.whl", hash = "sha256:ff0c721f20bff1e2cfba77948727a8db48f187e89a72fe50c34478ce6efb3353"}, +] +flatbuffers = [ + {file = "flatbuffers-22.9.24-py2.py3-none-any.whl", hash = "sha256:fc30f024e2eee55922d610f4d68626002fcd3c8f87d8058ec5ae9edd86993bcb"}, +] +fonttools = [ + {file = "fonttools-4.37.4-py3-none-any.whl", hash = "sha256:afae1b39555f9c3f0ad1f0f1daf678e5ad157e38c8842ecb567951bf1a9b9fd7"}, + {file = "fonttools-4.37.4.zip", hash = "sha256:86918c150c6412798e15a0de6c3e0d061ddefddd00f97b4f7b43dfa867ad315e"}, +] +frozenlist = [ + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5f271c93f001748fc26ddea409241312a75e13466b06c94798d1a341cf0e6989"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9c6ef8014b842f01f5d2b55315f1af5cbfde284eb184075c189fd657c2fd8204"}, + {file = "frozenlist-1.3.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:219a9676e2eae91cb5cc695a78b4cb43d8123e4160441d2b6ce8d2c70c60e2f3"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b47d64cdd973aede3dd71a9364742c542587db214e63b7529fbb487ed67cddd9"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2af6f7a4e93f5d08ee3f9152bce41a6015b5cf87546cb63872cc19b45476e98a"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a718b427ff781c4f4e975525edb092ee2cdef6a9e7bc49e15063b088961806f8"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c56c299602c70bc1bb5d1e75f7d8c007ca40c9d7aebaf6e4ba52925d88ef826d"}, + {file = "frozenlist-1.3.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:717470bfafbb9d9be624da7780c4296aa7935294bd43a075139c3d55659038ca"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:31b44f1feb3630146cffe56344704b730c33e042ffc78d21f2125a6a91168131"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c3b31180b82c519b8926e629bf9f19952c743e089c41380ddca5db556817b221"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d82bed73544e91fb081ab93e3725e45dd8515c675c0e9926b4e1f420a93a6ab9"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:49459f193324fbd6413e8e03bd65789e5198a9fa3095e03f3620dee2f2dabff2"}, + {file = "frozenlist-1.3.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:94e680aeedc7fd3b892b6fa8395b7b7cc4b344046c065ed4e7a1e390084e8cb5"}, + {file = "frozenlist-1.3.1-cp310-cp310-win32.whl", hash = "sha256:fabb953ab913dadc1ff9dcc3a7a7d3dc6a92efab3a0373989b8063347f8705be"}, + {file = "frozenlist-1.3.1-cp310-cp310-win_amd64.whl", hash = "sha256:eee0c5ecb58296580fc495ac99b003f64f82a74f9576a244d04978a7e97166db"}, + {file = "frozenlist-1.3.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0bc75692fb3770cf2b5856a6c2c9de967ca744863c5e89595df64e252e4b3944"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086ca1ac0a40e722d6833d4ce74f5bf1aba2c77cbfdc0cd83722ffea6da52a04"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1b51eb355e7f813bcda00276b0114c4172872dc5fb30e3fea059b9367c18fbcb"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:74140933d45271c1a1283f708c35187f94e1256079b3c43f0c2267f9db5845ff"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee4c5120ddf7d4dd1eaf079af3af7102b56d919fa13ad55600a4e0ebe532779b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97d9e00f3ac7c18e685320601f91468ec06c58acc185d18bb8e511f196c8d4b2"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6e19add867cebfb249b4e7beac382d33215d6d54476bb6be46b01f8cafb4878b"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:a027f8f723d07c3f21963caa7d585dcc9b089335565dabe9c814b5f70c52705a"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:61d7857950a3139bce035ad0b0945f839532987dfb4c06cfe160254f4d19df03"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:53b2b45052e7149ee8b96067793db8ecc1ae1111f2f96fe1f88ea5ad5fd92d10"}, + {file = "frozenlist-1.3.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bbb1a71b1784e68870800b1bc9f3313918edc63dbb8f29fbd2e767ce5821696c"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win32.whl", hash = "sha256:ab6fa8c7871877810e1b4e9392c187a60611fbf0226a9e0b11b7b92f5ac72792"}, + {file = "frozenlist-1.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f89139662cc4e65a4813f4babb9ca9544e42bddb823d2ec434e18dad582543bc"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:4c0c99e31491a1d92cde8648f2e7ccad0e9abb181f6ac3ddb9fc48b63301808e"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:61e8cb51fba9f1f33887e22488bad1e28dd8325b72425f04517a4d285a04c519"}, + {file = "frozenlist-1.3.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc2f3e368ee5242a2cbe28323a866656006382872c40869b49b265add546703f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:58fb94a01414cddcdc6839807db77ae8057d02ddafc94a42faee6004e46c9ba8"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:022178b277cb9277d7d3b3f2762d294f15e85cd2534047e68a118c2bb0058f3e"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:572ce381e9fe027ad5e055f143763637dcbac2542cfe27f1d688846baeef5170"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19127f8dcbc157ccb14c30e6f00392f372ddb64a6ffa7106b26ff2196477ee9f"}, + {file = "frozenlist-1.3.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42719a8bd3792744c9b523674b752091a7962d0d2d117f0b417a3eba97d1164b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2743bb63095ef306041c8f8ea22bd6e4d91adabf41887b1ad7886c4c1eb43d5f"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:fa47319a10e0a076709644a0efbcaab9e91902c8bd8ef74c6adb19d320f69b83"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:52137f0aea43e1993264a5180c467a08a3e372ca9d378244c2d86133f948b26b"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:f5abc8b4d0c5b556ed8cd41490b606fe99293175a82b98e652c3f2711b452988"}, + {file = "frozenlist-1.3.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e1cf7bc8cbbe6ce3881863671bac258b7d6bfc3706c600008925fb799a256e2"}, + {file = "frozenlist-1.3.1-cp38-cp38-win32.whl", hash = "sha256:0dde791b9b97f189874d654c55c24bf7b6782343e14909c84beebd28b7217845"}, + {file = "frozenlist-1.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:9494122bf39da6422b0972c4579e248867b6b1b50c9b05df7e04a3f30b9a413d"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:31bf9539284f39ff9398deabf5561c2b0da5bb475590b4e13dd8b268d7a3c5c1"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e0c8c803f2f8db7217898d11657cb6042b9b0553a997c4a0601f48a691480fab"}, + {file = "frozenlist-1.3.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:da5ba7b59d954f1f214d352308d1d86994d713b13edd4b24a556bcc43d2ddbc3"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74e6b2b456f21fc93ce1aff2b9728049f1464428ee2c9752a4b4f61e98c4db96"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:526d5f20e954d103b1d47232e3839f3453c02077b74203e43407b962ab131e7b"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b499c6abe62a7a8d023e2c4b2834fce78a6115856ae95522f2f974139814538c"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab386503f53bbbc64d1ad4b6865bf001414930841a870fc97f1546d4d133f141"}, + {file = "frozenlist-1.3.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f63c308f82a7954bf8263a6e6de0adc67c48a8b484fab18ff87f349af356efd"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:12607804084d2244a7bd4685c9d0dca5df17a6a926d4f1967aa7978b1028f89f"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:da1cdfa96425cbe51f8afa43e392366ed0b36ce398f08b60de6b97e3ed4affef"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:f810e764617b0748b49a731ffaa525d9bb36ff38332411704c2400125af859a6"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:35c3d79b81908579beb1fb4e7fcd802b7b4921f1b66055af2578ff7734711cfa"}, + {file = "frozenlist-1.3.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c92deb5d9acce226a501b77307b3b60b264ca21862bd7d3e0c1f3594022f01bc"}, + {file = "frozenlist-1.3.1-cp39-cp39-win32.whl", hash = "sha256:5e77a8bd41e54b05e4fb2708dc6ce28ee70325f8c6f50f3df86a44ecb1d7a19b"}, + {file = "frozenlist-1.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:625d8472c67f2d96f9a4302a947f92a7adbc1e20bedb6aff8dbc8ff039ca6189"}, + {file = "frozenlist-1.3.1.tar.gz", hash = "sha256:3a735e4211a04ccfa3f4833547acdf5d2f863bfeb01cfd3edaffbc251f15cec8"}, +] +ft4222 = [ + {file = "ft4222-1.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:64c80402e19ada10f142cf9d5f5b343a121689b94dfc31fafc7864db13ac7f79"}, + {file = "ft4222-1.6.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5c713b6527513a77e674a6db60d97f67b18ce9f85727168ecbeef82557f2b2d1"}, + {file = "ft4222-1.6.0-cp310-cp310-win32.whl", hash = "sha256:324d6330d501bf6f7746aa1b81f9540a2b385e9318bd4e6e4f744d7107f90c67"}, + {file = "ft4222-1.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:1279115892441e4a2ab468429e5003c72f47423daa4d7179c0da4af3522c056a"}, + {file = "ft4222-1.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:719ec8f9c05636d8306a64f8f6f86dd9ec27d80afb3fc07e7d0b01c9b3d83d1e"}, + {file = "ft4222-1.6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:886f3b53bad1dc238a3d6e5105b6d7442244ea854afea38daf84529491ad6e3d"}, + {file = "ft4222-1.6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:c28fb526ec98afefebb24f0e333bfef0def3ca46f8b4f84712c5e6e3c7e0fe9c"}, + {file = "ft4222-1.6.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f4b3ced3e5603f6bb22fcfcf9567a95ec47a93dd453a911e438c02d665bf657c"}, + {file = "ft4222-1.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:376ddba0e44a24237d926bc28a519ef7877174726629c3335529f548a829e857"}, + {file = "ft4222-1.6.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:3dff18e3d95027fb7407748d9a92eece9a2e88c3f7521be59fc848fe32a3780f"}, + {file = "ft4222-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:c0673a1420bd10b3dd782d307c60516bcd4ff24f7c27661b319c781e8a0618df"}, + {file = "ft4222-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:de38894f69cd0253a4b683612006912c5d98fae94f07f910650ab5027dc3df8e"}, + {file = "ft4222-1.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:e3aae2cbd0a15f7dd9506971f32ab67dcf192c24b012258ccf2e0daeb099d8e5"}, + {file = "ft4222-1.6.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f4d6d21e8e00b9c5aabaff48489973cf45452cb88a9d6946eaa6aea1d48c0794"}, + {file = "ft4222-1.6.0-cp38-cp38-win32.whl", hash = "sha256:eeb1f8cf04262e2bcdaf159a767148a61ef91aa8b86950ae883bb8ba8f0e82da"}, + {file = "ft4222-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:06419df94566e5e62358f7d68055fd4155b916646ead2c479e75b964548714e3"}, + {file = "ft4222-1.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:7a91689f9e44c6a8afedb6e5a11ba70d0c661e879607fd8635c76ebaa503cd90"}, + {file = "ft4222-1.6.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:31976a15a7cd649814887e3887480f3f625e82e7e2d354f33d582a0faf4c7689"}, + {file = "ft4222-1.6.0-cp39-cp39-win32.whl", hash = "sha256:b4c8309bbc2d0cf2704dd8737bcf2a6ae27284f7cf45746e79d3dbd4810e0cb3"}, + {file = "ft4222-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:395596fd03120015b7a049928ba31983f7e71596bcdb85bd78eff4293e955266"}, + {file = "ft4222-1.6.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:937124275e50adc87213fa3bda52e3ac08348454eee21b23e23ce4afd656035b"}, + {file = "ft4222-1.6.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:eb9752990913ee327ebf1279f4a4847b54cacbf95aa920d24f07cb687a77572b"}, + {file = "ft4222-1.6.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:37696c46340064e7518e4d77c8f8b044d72af26850f2e3370369201dd44b08b9"}, + {file = "ft4222-1.6.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ea7fdf9f5757e981445bbf80669f4f059912963f5718d16736c0f2b341a05610"}, + {file = "ft4222-1.6.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:f242776c497d49f0533c643138375a59fe1745c159e43ec55845fe59c1f17020"}, + {file = "ft4222-1.6.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:5ba637e9c43c7aaa82915b06c4098ac885f2ae45152b1f01584307b8e6e5b005"}, + {file = "ft4222-1.6.0.tar.gz", hash = "sha256:537de8f49c21fe49f4be16e0a359315e72322a6a513b153effc95c8105c6af06"}, +] +future = [ + {file = "future-0.18.2.tar.gz", hash = "sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"}, +] +future-fstrings = [ + {file = "future_fstrings-1.2.0-py2.py3-none-any.whl", hash = "sha256:90e49598b553d8746c4dc7d9442e0359d038c3039d802c91c0a55505da318c63"}, + {file = "future_fstrings-1.2.0.tar.gz", hash = "sha256:6cf41cbe97c398ab5a81168ce0dbb8ad95862d3caf23c21e4430627b90844089"}, +] +geoalchemy2 = [ + {file = "GeoAlchemy2-0.12.5-py2.py3-none-any.whl", hash = "sha256:3a59eb651df95b3dfee8e1d82f4d18c80b75f712860a0a3080defc6b0435070d"}, + {file = "GeoAlchemy2-0.12.5.tar.gz", hash = "sha256:31c2502dce317b57b335e4eb87562d501fa39e46c728be514d9b86091e08dd67"}, +] +geopandas = [ + {file = "geopandas-0.12.2-py3-none-any.whl", hash = "sha256:0a470e4bf6f5367e6fd83ab6b40405e0b805c8174665bbcb7c4077ed90202912"}, + {file = "geopandas-0.12.2.tar.gz", hash = "sha256:0acdacddefa176525e4da6d9aeeece225da26055c4becdc6e97cf40fa97c27f4"}, +] +gevent = [ + {file = "gevent-22.10.1-cp27-cp27m-win32.whl", hash = "sha256:702a51b8f21bad1976b0893f90ade466e8c27039b846b611ad2beb8c6e6ac701"}, + {file = "gevent-22.10.1-cp27-cp27m-win_amd64.whl", hash = "sha256:af7baec79a5f8ad1cc132d3b14edd12661c628d8094e501b089b1fe2d3df7f6e"}, + {file = "gevent-22.10.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cf6dd33052919de8fb56e0bea0e6a7c7d6545281fe280ea78e311621c7adb50e"}, + {file = "gevent-22.10.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f16c6937d47593f051fc3ac7996c819919082a1e7e0dec927cdae8771d26ed45"}, + {file = "gevent-22.10.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:21dbeb6d3b47093f40ca97aab493b2fb64b6f22112f88f56d79cf6f52a8c1c16"}, + {file = "gevent-22.10.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:acb21bee2e66da45b8916073c8ae54c44629beb94d49120c188d27aff4ebf8dd"}, + {file = "gevent-22.10.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2ea4ce36c09355379bc038be2bd50118f97d2eb6381b7096de4d05aa4c3e241"}, + {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e73c9f71aa2a6795ecbec9b57282b002375e863e283558feb87b62840c8c1ac"}, + {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5bc3758f0dc95007c1780d28a9fd2150416a79c50f308f62a674d78a845ea1b9"}, + {file = "gevent-22.10.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03c10ca0beeab0c6be516030471ea630447ddd1f649d3335e5b162097cd4130a"}, + {file = "gevent-22.10.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fe2c0ff095171c49f78f1d4e6dc89fa58253783c7b6dccab9f1d76e2ee391f10"}, + {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d18fcc324f39a3b21795022eb47c7752d6e4f4ed89d8cca41f1cc604553265b3"}, + {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06ea39c70ce166c4a1d4386c7fae96cb8d84ad799527b3378406051104d15443"}, + {file = "gevent-22.10.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a920a812b6c0c36d4613a15c254ca1ce415ee75ade0df3b8941ab61ae7ce3f"}, + {file = "gevent-22.10.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:33c4cd095f99768ecc4b3bb75a12f52ea9df5c40a58671c667f86ea087a075e1"}, + {file = "gevent-22.10.1-cp36-cp36m-win32.whl", hash = "sha256:db592cfe5106730667ac36f43554e7a869d757e411f8a08116c3739cee507145"}, + {file = "gevent-22.10.1-cp36-cp36m-win_amd64.whl", hash = "sha256:a12443b7326e40d00fb445d37bae154fd1f4693055330c6b4e68670ca3b6e6bf"}, + {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5d64b208bec99adc7e0b6e08a3e2c296065c665ca725ca8da434c4ffc5aa302e"}, + {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1ac816f5e8e318c5fa27091ee43fcf4caaa6ae73a487e875a1a296a04c3da5af"}, + {file = "gevent-22.10.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:eb0d9d6f869ba7c49d7f9b7d244dd20daec5cc87cd3e2e90209d6ed8172e0cad"}, + {file = "gevent-22.10.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3c1cfd9f9fb6a2a5a9a04132d315db7fb819db019dea260695fe6e4012416f96"}, + {file = "gevent-22.10.1-cp37-cp37m-win32.whl", hash = "sha256:4b0d29fc18ee338a85396facfc508e5f26e2e0e90f4c2889f8a9e74d341ad467"}, + {file = "gevent-22.10.1-cp37-cp37m-win_amd64.whl", hash = "sha256:36e47ca663081a71fca137b7c852e99e7ee3761082070c13aa2ae3b5b6234af6"}, + {file = "gevent-22.10.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a838437b7b629328ad457cd36df454500afe7f3df4b971a6ff85851dfcf8c844"}, + {file = "gevent-22.10.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c06c0f3f4f1b147f51a934fbf541880cee769492b98c4ebd3e930b5ff862646"}, + {file = "gevent-22.10.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d701208d4d65dbdf9feb02561a75ecc5bd28300e47b59f74033a07b593887806"}, + {file = "gevent-22.10.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e8c4ccc544f6e6c26ab10d0d6a7be86bd522222ce40f00bfafa01289f04bffc"}, + {file = "gevent-22.10.1-cp38-cp38-win32.whl", hash = "sha256:4be5859af086de1ed85702c0a84479387087ddf49e38332c41861b0a10e96d8f"}, + {file = "gevent-22.10.1-cp38-cp38-win_amd64.whl", hash = "sha256:be43278781d39b4081f7f4d3e8ebb1dac188c9fe98f25da817325cb12c01887a"}, + {file = "gevent-22.10.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0cafb8f5399224990a5329bca3606bf399ee3604ae711b2d9238129b44551ad5"}, + {file = "gevent-22.10.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e1c609f9e4171588006bea7ff41bb830ff27c27d071bbd311f91860fb5ef4cc"}, + {file = "gevent-22.10.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2dcfd8ef9dcca78c51dd266d0f4b48d9b7ea2592ae881bf66d8dbe59bb16631a"}, + {file = "gevent-22.10.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:eefcb21fda3055f2e7eaad2e8098885a7bbddd83b174b012e2142db6b2b4c09d"}, + {file = "gevent-22.10.1-cp39-cp39-win32.whl", hash = "sha256:3f7d11136b3ae6312effbc2ac0ed902ae718d86e7acb9a51cf927262cfb2931e"}, + {file = "gevent-22.10.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ec5f77f629d997668983be53bad2a90d1b858a00e43b9e75e1c9a118c3a840b"}, + {file = "gevent-22.10.1-pp27-pypy_73-win_amd64.whl", hash = "sha256:d8df3f628c8a9fb339b87a849dc2076e56d124e2169261fa58b4a01db3a335b6"}, + {file = "gevent-22.10.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:0569e133bb620de1001ac807ad9a8abaadedd25349c6d695f80c9048a3f59d42"}, + {file = "gevent-22.10.1.tar.gz", hash = "sha256:df3042349c9a4460eeaec8d0e56d737cb183eed055e75a6af9dbda94aaddaf4d"}, +] +geventhttpclient = [ + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd76acdc7e7ee5c54c7b279f806b28957a6b092f79c40db34adcfd972749343c"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:320a2c756d8a4f296de370476a1515485c186d9e22c3fc29e04f8f743a7d47bb"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36d3345c6585b09738195a7c45d279a87ccbab0350f1cce3679d3f0dce8577a1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:407d54499556c2741b93691b86da93232590b013f4a0b773327d766fe3e5c0a9"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bcf325131b0e4600b793643108cd85dddd66bbf532fd2eb498be5727ef532a1e"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a5841dd02e6f792a4ef15dbd04fefe620c831ba0b78105808160bb779a31af4"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2ba69422d4e8670dd99803b1313ba574a4d41f52e92b512af51068c9c577bdc1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6e3af579c6b46b9caa515a8baf6a2cadeafcd1d41ad22ca5712851f074a40b47"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6ff7fc19f9a4fdd54a2b1c106a705ea2c679fa049685ed763051d417725bdab1"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-win32.whl", hash = "sha256:eec7c52e8eb817674a193e0124486b507215d9e86d34f2638bf9a9292d16f815"}, + {file = "geventhttpclient-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:0e9f7283c01d970e643d89da81127869a8d94bb7a0081020dcad5b590bc007c4"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5ceb492d43a659b895794999dc40d0e7c23b1d41dd34040bbacd0dc264b57d5b"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:95959c201d3151fa8f57e0f1ce184476d1173996bdde41dc7d600006023dc5be"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:31c7febba298ecf44838561074a3fb7a01523adca286469b5a82dcc90e8d6a07"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:996c5f453d810b3c592160193d6832a065cca0112e92adc74e62df0e4c564df6"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f817e226c02b5a71d86de3772d6accdf250288d1e6825e426c713759830162d"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c55b7ac0ba0e1e1afbf297b7608f0b3a0bbc34fb4b0c19b7869f32a77ddc6209"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6775bc81e25c48fa58b034444aecfa508b0c3d1bc1e4ae546cc17661be1f51aa"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:a0156882c73537bbbbc7c693ae44c9808119963174078692613ffa4feea21fcf"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3ebb582a291c4c5daaac2ea115b413f4be86874baa60def44d333301cee17bd7"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-win32.whl", hash = "sha256:716f1f72f50b841daf9c9511a01fc31a030866510a11863f27741e26e4f556a7"}, + {file = "geventhttpclient-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:777fcdb72077dfbf70516ecb9e9022246dd337b83a4c1e96f17f3ab9e15f4547"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:379d90d8b1fcdda94e74d693806e0b0116c0610504e7f62d5576bac738dc66a5"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00b7b2b836294c091c53789a469c5671202d79420b5191931df4e3a767d607fa"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9d075355862d7726eb3436f0136fce7650c884f2d04eaae7a39fed3aad9798bc"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa7b1a27f950d209fe223a97906fe41312dc12c92372424639b8a9b96f1adf91"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fe4e06313aad353b103950780b050d3958000464cc732d621ff8ea3cacbd2bc4"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:84d7be660b6bc53dd53e3f46b3bc5d275972a8116bd183a77139bb4d9d6d9fb1"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:81f839d6becd664d0972b488422f5dc821f8ad2f2196d53aa5e4d799a3a35a66"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-win32.whl", hash = "sha256:e707f62271a093e6e3af6f1bbd8cc398b414b8c508fe6b15505dd8e76c4409ac"}, + {file = "geventhttpclient-2.0.2-cp36-cp36m-win_amd64.whl", hash = "sha256:28d7655d1d50bc75ece683a0ae8faf978821d4aeae358d77b59371548db07f1e"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58877b4440a580063571a23fbc616aed7c735c6bf9ef525c5129783df8b6966"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57c993c4b2bea551c4a71b75ae1e172e9f3e4352f704ff1b619a0f16aa762f76"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3f67e789e31c7b1ce440cd1465dcdefeca29ba6108735eac0b1a593d3a55b7f"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5f3326e115ec7e7ce95a5d0d47698e8f3584944c4c434a7404937d56b17136b8"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ef328ee3e7dca5055b833fdf3c181647a335abf0249947b27f5df2d95390198c"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:27049ea40e3b559eee380310272aaa9b7c19e73c1d9e51e2ec137362be2caa70"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:b88a10538341e33fed1682c0dd4579c655d49db5863e7456583085a1cd6bd9d4"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:d52aba2c38420b3fc518188449f1c2a46b1a99adf1c0266c68e72ee0422cd0fa"}, + {file = "geventhttpclient-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:3648626ca58ea4b340e695d78e5d533e6b8be78d375edbd42ff188bc3447e095"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fcf96e212b55b93490f3a5fcdfe7a2ef4995a0d13b7d9df398b11e319b7a86b1"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3e9f2ff09706e3a64a99886d5f2595f3bf364821bc609f2865dbc3e499e21a36"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:721c3075897bfc81e918066f16ae3d1a88c7bb14eeeb831a4f89ea636474643e"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91615fed7931acd49cfe5fc30984acd5411dc1f2643b1544c879d1a537233c6d"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7adaa29e5699dea54e0224d1d2d9d8869668d8ad79f5b89433ff9c46f9424a6c"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9be5000ba57336a90b438782117c1e43205f51f49aa9b1499a82e210e8431b11"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:12d271cc53486efb3716e99855dc5cb84f2cd3fc9f3243721747bb39ec0fff8a"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b9c0c6b75b3905000d2490dc64b4c98a8bac155efbc0ff8917ac082ae0bad261"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e956a457d8831dc81d6f046ab09ebeec680f9a1e9c07e25a1906e77b287918ee"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-win32.whl", hash = "sha256:bc46d5479673dfb293ea428c057d2e23e48ebef5c5d44587cdbaada7f87553e4"}, + {file = "geventhttpclient-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:f44153e4b3ef9b901edcd14be54145a0058bf5fa371b3e583153865fac866245"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:ebf98db9435824cf0b80b5247be6c88b20bfafd6249f7ebaabb85297da37e380"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c8b7298eb1ebd015257bf4503e34f5fbbe64bd83324140f76b511046aba5a0d5"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:60b81a6d4e65db7c1a5350c9fb72ebf800b478849a7e8020d1ab93af237a3747"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad6c2fcbc3733785bd3b8c2bb43d1f605f9085b0a8b70ce354d198f37143f884"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94edb022fa50d576cf63f6dd0c437c1acd24a719872a5935991aaf08f8e88cb2"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca459cedb3827d960362e05ea3a4ae600a6d0d93de77eac2ac0f79828e5e18c"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7551b6db860b56411de1f96618e91b54f65e1a7be8d10255bd1adfb738bb6ee5"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bcb7e061c243308d9a44b02de5298001e917f1636a9f270c10da86601fcc8dfa"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:96922d170ef8933f4c20036e8d70d4fbe861f54c543e32e7459ebdbaafa65a2e"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-win32.whl", hash = "sha256:ebb3c993903d40fd4bb1f3e55b84c62c8fc1d14433ae6d4d477dd9a325354c94"}, + {file = "geventhttpclient-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:dbccf1ba155dea3ea99ba0e67a835c05b4303f05298e85f5bb2a46700ccdf092"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:8770b8ab9e8c31d2aaf8a6fbc63fbb7239c58db10bb49cee191ca5c141c61542"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daff1e977fccf98f27266d3891afdc101f1d705a48331754909e960bcae83f8a"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2435e0f2a60e00d977822ec4c12e7851deb7aa49a23d32d648e72c641aae3b05"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09acd03d0a8c1bb7d5a1cb6fcb77aaa19a907c1b4915ab58da5d283675edb0a5"}, + {file = "geventhttpclient-2.0.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:5d0813d97050446dab2fb243312e6c446e4ef5e9591befd597ef8f2887f8e2a8"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:852da9bb0fc792cdca5ffc9327490094783e42415494b3569e5d532615027439"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79304a63a9d0512f2757c5862487b332b18a9c85feebecf6ebc3526c6dd1ba2"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:01c1c783fce45f16db448d7e34864f1e9c22fe60a7780d2c1c14edbb1fb7262e"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:77c407c2b4bea817c6f752502db4ab0e9f9465b4fb85b459d1332b5f93a3096c"}, + {file = "geventhttpclient-2.0.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4f0d70a83ef4ab93102c6601477c13e9cdbc87205e5237fbf5797e30dc9d3ee8"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b03f298ec19b8a4717cce8112fe30322c9e5bfada84dde61a1a44d1eeffc1d3c"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b2dc94b9a23eb6744a8c729aec2b1cdc4e39acf1d8f16ea85a62810aa6b2cae5"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:805554594bb29231fd990cc2cbbe493d223d76a6085fec891dd76bb4e0928933"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb23527d98f626ca7a4e8961ed9bdc6aed3388de306614c69a133b34262460f4"}, + {file = "geventhttpclient-2.0.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a594ab319872a38fb7f16be4cfb107d3c63c43a081f2abe241834e9877f27401"}, + {file = "geventhttpclient-2.0.2.tar.gz", hash = "sha256:8135a85200b170def7293d01dd1557931fcd1bec1ac78c52ad7cedd22368b9ba"}, +] +greenlet = [ + {file = "greenlet-1.1.3.post0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:949c9061b8c6d3e6e439466a9be1e787208dec6246f4ec5fffe9677b4c19fcc3"}, + {file = "greenlet-1.1.3.post0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:d7815e1519a8361c5ea2a7a5864945906f8e386fa1bc26797b4d443ab11a4589"}, + {file = "greenlet-1.1.3.post0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:9649891ab4153f217f319914455ccf0b86986b55fc0573ce803eb998ad7d6854"}, + {file = "greenlet-1.1.3.post0-cp27-cp27m-win32.whl", hash = "sha256:11fc7692d95cc7a6a8447bb160d98671ab291e0a8ea90572d582d57361360f05"}, + {file = "greenlet-1.1.3.post0-cp27-cp27m-win_amd64.whl", hash = "sha256:05ae7383f968bba4211b1fbfc90158f8e3da86804878442b4fb6c16ccbcaa519"}, + {file = "greenlet-1.1.3.post0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ccbe7129a282ec5797df0451ca1802f11578be018a32979131065565da89b392"}, + {file = "greenlet-1.1.3.post0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4a8b58232f5b72973350c2b917ea3df0bebd07c3c82a0a0e34775fc2c1f857e9"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:f6661b58412879a2aa099abb26d3c93e91dedaba55a6394d1fb1512a77e85de9"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c6e942ca9835c0b97814d14f78da453241837419e0d26f7403058e8db3e38f8"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a812df7282a8fc717eafd487fccc5ba40ea83bb5b13eb3c90c446d88dbdfd2be"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83a7a6560df073ec9de2b7cb685b199dfd12519bc0020c62db9d1bb522f989fa"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:17a69967561269b691747e7f436d75a4def47e5efcbc3c573180fc828e176d80"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:60839ab4ea7de6139a3be35b77e22e0398c270020050458b3d25db4c7c394df5"}, + {file = "greenlet-1.1.3.post0-cp310-cp310-win_amd64.whl", hash = "sha256:8926a78192b8b73c936f3e87929931455a6a6c6c385448a07b9f7d1072c19ff3"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:c6f90234e4438062d6d09f7d667f79edcc7c5e354ba3a145ff98176f974b8132"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:814f26b864ed2230d3a7efe0336f5766ad012f94aad6ba43a7c54ca88dd77cba"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8fda1139d87ce5f7bd80e80e54f9f2c6fe2f47983f1a6f128c47bf310197deb6"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0643250dd0756f4960633f5359884f609a234d4066686754e834073d84e9b51"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cb863057bed786f6622982fb8b2c122c68e6e9eddccaa9fa98fd937e45ee6c4f"}, + {file = "greenlet-1.1.3.post0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8c0581077cf2734569f3e500fab09c0ff6a2ab99b1afcacbad09b3c2843ae743"}, + {file = "greenlet-1.1.3.post0-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:695d0d8b5ae42c800f1763c9fce9d7b94ae3b878919379150ee5ba458a460d57"}, + {file = "greenlet-1.1.3.post0-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:5662492df0588a51d5690f6578f3bbbd803e7f8d99a99f3bf6128a401be9c269"}, + {file = "greenlet-1.1.3.post0-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:bffba15cff4802ff493d6edcf20d7f94ab1c2aee7cfc1e1c7627c05f1102eee8"}, + {file = "greenlet-1.1.3.post0-cp35-cp35m-win32.whl", hash = "sha256:7afa706510ab079fd6d039cc6e369d4535a48e202d042c32e2097f030a16450f"}, + {file = "greenlet-1.1.3.post0-cp35-cp35m-win_amd64.whl", hash = "sha256:3a24f3213579dc8459e485e333330a921f579543a5214dbc935bc0763474ece3"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:64e10f303ea354500c927da5b59c3802196a07468332d292aef9ddaca08d03dd"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:eb6ac495dccb1520667cfea50d89e26f9ffb49fa28496dea2b95720d8b45eb54"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:88720794390002b0c8fa29e9602b395093a9a766b229a847e8d88349e418b28a"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:39464518a2abe9c505a727af7c0b4efff2cf242aa168be5f0daa47649f4d7ca8"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0914f02fcaa8f84f13b2df4a81645d9e82de21ed95633765dd5cc4d3af9d7403"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96656c5f7c95fc02c36d4f6ef32f4e94bb0b6b36e6a002c21c39785a4eec5f5d"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:4f74aa0092602da2069df0bc6553919a15169d77bcdab52a21f8c5242898f519"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3aeac044c324c1a4027dca0cde550bd83a0c0fbff7ef2c98df9e718a5086c194"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-win32.whl", hash = "sha256:fe7c51f8a2ab616cb34bc33d810c887e89117771028e1e3d3b77ca25ddeace04"}, + {file = "greenlet-1.1.3.post0-cp36-cp36m-win_amd64.whl", hash = "sha256:70048d7b2c07c5eadf8393e6398595591df5f59a2f26abc2f81abca09610492f"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:66aa4e9a726b70bcbfcc446b7ba89c8cec40f405e51422c39f42dfa206a96a05"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:025b8de2273d2809f027d347aa2541651d2e15d593bbce0d5f502ca438c54136"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:82a38d7d2077128a017094aff334e67e26194f46bd709f9dcdacbf3835d47ef5"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7d20c3267385236b4ce54575cc8e9f43e7673fc761b069c820097092e318e3b"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8ece5d1a99a2adcb38f69af2f07d96fb615415d32820108cd340361f590d128"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2794eef1b04b5ba8948c72cc606aab62ac4b0c538b14806d9c0d88afd0576d6b"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a8d24eb5cb67996fb84633fdc96dbc04f2d8b12bfcb20ab3222d6be271616b67"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0120a879aa2b1ac5118bce959ea2492ba18783f65ea15821680a256dfad04754"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-win32.whl", hash = "sha256:bef49c07fcb411c942da6ee7d7ea37430f830c482bf6e4b72d92fd506dd3a427"}, + {file = "greenlet-1.1.3.post0-cp37-cp37m-win_amd64.whl", hash = "sha256:62723e7eb85fa52e536e516ee2ac91433c7bb60d51099293671815ff49ed1c21"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d25cdedd72aa2271b984af54294e9527306966ec18963fd032cc851a725ddc1b"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:924df1e7e5db27d19b1359dc7d052a917529c95ba5b8b62f4af611176da7c8ad"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:ec615d2912b9ad807afd3be80bf32711c0ff9c2b00aa004a45fd5d5dde7853d9"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0971d37ae0eaf42344e8610d340aa0ad3d06cd2eee381891a10fe771879791f9"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:325f272eb997916b4a3fc1fea7313a8adb760934c2140ce13a2117e1b0a8095d"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d75afcbb214d429dacdf75e03a1d6d6c5bd1fa9c35e360df8ea5b6270fb2211c"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5c2d21c2b768d8c86ad935e404cc78c30d53dea009609c3ef3a9d49970c864b5"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:467b73ce5dcd89e381292fb4314aede9b12906c18fab903f995b86034d96d5c8"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-win32.whl", hash = "sha256:8149a6865b14c33be7ae760bcdb73548bb01e8e47ae15e013bf7ef9290ca309a"}, + {file = "greenlet-1.1.3.post0-cp38-cp38-win_amd64.whl", hash = "sha256:104f29dd822be678ef6b16bf0035dcd43206a8a48668a6cae4d2fe9c7a7abdeb"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:c8c9301e3274276d3d20ab6335aa7c5d9e5da2009cccb01127bddb5c951f8870"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:8415239c68b2ec9de10a5adf1130ee9cb0ebd3e19573c55ba160ff0ca809e012"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:3c22998bfef3fcc1b15694818fc9b1b87c6cc8398198b96b6d355a7bcb8c934e"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0aa1845944e62f358d63fcc911ad3b415f585612946b8edc824825929b40e59e"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:890f633dc8cb307761ec566bc0b4e350a93ddd77dc172839be122be12bae3e10"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cf37343e43404699d58808e51f347f57efd3010cc7cee134cdb9141bd1ad9ea"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5edf75e7fcfa9725064ae0d8407c849456553a181ebefedb7606bac19aa1478b"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a954002064ee919b444b19c1185e8cce307a1f20600f47d6f4b6d336972c809"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-win32.whl", hash = "sha256:2ccdc818cc106cc238ff7eba0d71b9c77be868fdca31d6c3b1347a54c9b187b2"}, + {file = "greenlet-1.1.3.post0-cp39-cp39-win_amd64.whl", hash = "sha256:91a84faf718e6f8b888ca63d0b2d6d185c8e2a198d2a7322d75c303e7097c8b7"}, + {file = "greenlet-1.1.3.post0.tar.gz", hash = "sha256:f5e09dc5c6e1796969fd4b775ea1417d70e49a5df29aaa8e5d10675d9e11872c"}, +] +gunicorn = [ + {file = "gunicorn-20.1.0-py3-none-any.whl", hash = "sha256:9dcc4547dbb1cb284accfb15ab5667a0e5d1881cc443e0677b4882a4067a807e"}, + {file = "gunicorn-20.1.0.tar.gz", hash = "sha256:e0a968b5ba15f8a328fdfd7ab1fcb5af4470c28aaf7e55df02a99bc13138e6e8"}, +] +h3 = [ + {file = "h3-3.7.4-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:e61d3c6b1b66072f5b74d46dbee7df29daac6ce9738b9a6223a67dc577114515"}, + {file = "h3-3.7.4-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:960cd005b8817314d95fbaff3e848a72385df4e3c6c9703ff99b08581c8def69"}, + {file = "h3-3.7.4-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:68227df989274b0da54de9101a50741c70c48197ba3beacfb97c88170445c18e"}, + {file = "h3-3.7.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bf4d75fe42a260ac23bf4cb9f9de6e6f2aa37279b2719387711f3e0727c4653"}, + {file = "h3-3.7.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c644ab3f221c7faaab2d1ccd11bc3b1106f172e9bb1c85a863b0a097f6b71cce"}, + {file = "h3-3.7.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7c5366d24c2c01ef3bae68547c15f1965fac6053b2596c0073766bf7544ecaf0"}, + {file = "h3-3.7.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83c2b0cd8259541f95b0493a620fb781b6a18c7c1e8fac1bda4fb234ae23ab43"}, + {file = "h3-3.7.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1108a9acb755310dce50a6e3c58ae1a2460ef60901d40e1155d633c7392f858"}, + {file = "h3-3.7.4-cp310-cp310-win_amd64.whl", hash = "sha256:ce86c6dce2c923bfb16e26586bc5f0443a8be61d4f43227be8587ccb95588a46"}, + {file = "h3-3.7.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ad21cfa8d97a62984ce30692a7ddf71a32a0c744cc247c43cbdbac1536aec4de"}, + {file = "h3-3.7.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be63482de86bbb91db7f3f3b7dd452b9e08a11dacbda2088386831fb0e7de59c"}, + {file = "h3-3.7.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a96ea1844182bd0511cdcdc89e38e3026d9a3d4139fd0c5e899709edd798ffa"}, + {file = "h3-3.7.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2faf304020493c5ffede34264bd28ed529b8b7238103e0904c0f3e9ca880bcfd"}, + {file = "h3-3.7.4-cp36-cp36m-win_amd64.whl", hash = "sha256:3b1642085939c597a9c723ae3b187f80527ffc79cad0ded0e55be9c9bac69c6c"}, + {file = "h3-3.7.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8d03622433da1a2761574311af378ff1ff841f5956db25927837c6aee9d1c13c"}, + {file = "h3-3.7.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:08a09f7a43ed142573c602ef487a058da54ab4d86c173082b29a5057805fe2d3"}, + {file = "h3-3.7.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4469fdf90034b1a67e155cac4f46b077fdc404b6182ab33abcb7081c9bfbf411"}, + {file = "h3-3.7.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:33b147ecc0e19ab1f27303d0e3ae28e5a457f3347ce18ca9a58b694a8b0cdd0a"}, + {file = "h3-3.7.4-cp37-cp37m-win_amd64.whl", hash = "sha256:c95c0818c163b69989c9e876dd82005e60edfbaabfd45429abebfc26f9a357e8"}, + {file = "h3-3.7.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7b0ddfdd02920996d7c6672c91e83efb5432c67ff83f89a03f774e84bcfe19f8"}, + {file = "h3-3.7.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46a1284521d86cab414981056390be944dca780fe74c6c9e463a16d1c8d24871"}, + {file = "h3-3.7.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:585f375ba2a95ceb16b115a378e9118159c912c26703cf1627f57a004818c3b3"}, + {file = "h3-3.7.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdd68e684f0c6e18604d46ee04dbcfe5c79de62238b2c29f1db0f3a5d8dfa47b"}, + {file = "h3-3.7.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a59d7d10597a2da9e9729637a625ae8dff2ed4e7c6c0b4952f0a5b2db6ef7152"}, + {file = "h3-3.7.4-cp38-cp38-win_amd64.whl", hash = "sha256:38a084d74b234d48aafc01e4329cd9a92966e3f45b8cf21224118643b6eaa1c0"}, + {file = "h3-3.7.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a33fae02a54c63acb3c30fe49388715d658d76d42858a6ad4563e7e6859a9e57"}, + {file = "h3-3.7.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9b0277d82578b3ed3220167ef5c5acd8b4e0ef2fcd6c2fd69dbf29e0c4e03765"}, + {file = "h3-3.7.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8155b2de1938eb56128fe4fd96e4f6d2022d4c34d8137bc95d73cbf329f8f89e"}, + {file = "h3-3.7.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1387166f453816f91d624c6ce70876a3c20356cd28a3a759920dee23c78684cf"}, + {file = "h3-3.7.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:62057c1c3d1c7fe492841e42fa360825d66fafd55ac37dc4e90b2292af21cb47"}, + {file = "h3-3.7.4-cp39-cp39-win_amd64.whl", hash = "sha256:25f0c22f4802ab71c45b86d206bd30fa0a6c7fbc3b630398b60c22907e9742e6"}, + {file = "h3-3.7.4.tar.gz", hash = "sha256:f8edf5a546b31afdcd801b60448ea890ce1ff418fb784335e1329519f13aa85e"}, +] +hatanaka = [ + {file = "hatanaka-2.4.0-py3-none-macosx_10_14_x86_64.whl", hash = "sha256:ef594d63473782fac46df5b0c92a59211a3efea1d47c1a964244a0abffc9f3f6"}, + {file = "hatanaka-2.4.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:8fda4aa56f27313de75a806a2f5aa83ed5bb2dc7561bebab856a774d06cf1ee7"}, + {file = "hatanaka-2.4.0-py3-none-win_amd64.whl", hash = "sha256:5a0624f6812b13abb4c996398a60338566885c1786841c4c04de9b1b91da28d2"}, + {file = "hatanaka-2.4.0.tar.gz", hash = "sha256:c22970b99169bddaf22e5239672e856a6bc9602c435f8793d26ad49619a70a99"}, +] +hexdump = [ + {file = "hexdump-3.3.zip", hash = "sha256:d781a43b0c16ace3f9366aade73e8ad3a7bd5137d58f0b45ab2d3f54876f20db"}, +] +html5lib = [ + {file = "html5lib-1.1-py2.py3-none-any.whl", hash = "sha256:0d78f8fde1c230e99fe37986a60526d7049ed4bf8a9fadbad5f00e22e58e041d"}, + {file = "html5lib-1.1.tar.gz", hash = "sha256:b2e5b40261e20f354d198eae92afc10d750afb487ed5e50f9c4eaf07c184146f"}, +] +humanfriendly = [ + {file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477"}, + {file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc"}, +] +hypothesis = [ + {file = "hypothesis-6.46.7-py3-none-any.whl", hash = "sha256:2696cdb9005946bf1d2b215cc91d3fc01625e3342eb8743ddd04b667b2f1882b"}, + {file = "hypothesis-6.46.7.tar.gz", hash = "sha256:967009fa561b3a3f8363a73d71923357271c37dc7fa27b30c2d21a1b6092b240"}, +] +identify = [ + {file = "identify-2.5.6-py2.py3-none-any.whl", hash = "sha256:b276db7ec52d7e89f5bc4653380e33054ddc803d25875952ad90b0f012cbcdaa"}, + {file = "identify-2.5.6.tar.gz", hash = "sha256:6c32dbd747aa4ceee1df33f25fed0b0f6e0d65721b15bd151307ff7056d50245"}, +] +idna = [ + {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"}, + {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"}, +] +imageio = [ + {file = "imageio-2.22.2-py3-none-any.whl", hash = "sha256:9bdafe9c5a3d336a187f3f554f3e30bcdbf8a1d7d46f0e4d94e4a535adfb64c7"}, + {file = "imageio-2.22.2.tar.gz", hash = "sha256:db7010cd10712518819a4187baf61b05988361ea20c23e829918727b27acb977"}, +] +imagesize = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.13.0-py3-none-any.whl", hash = "sha256:8a8a81bcf996e74fee46f0d16bd3eaa382a7eb20fd82445c3ad11f4090334116"}, + {file = "importlib_metadata-4.13.0.tar.gz", hash = "sha256:dd0173e8f150d6815e098fd354f6414b0f079af4644ddfe90c71e2fc6174346d"}, +] +importlib-resources = [ + {file = "importlib_resources-5.10.0-py3-none-any.whl", hash = "sha256:ee17ec648f85480d523596ce49eae8ead87d5631ae1551f913c0100b5edd3437"}, + {file = "importlib_resources-5.10.0.tar.gz", hash = "sha256:c01b1b94210d9849f286b86bb51bcea7cd56dde0600d8db721d7b81330711668"}, +] +influxdb-client = [ + {file = "influxdb_client-1.33.0-py3-none-any.whl", hash = "sha256:38a8bedce673ffd2211f60012b5848cb2418977c19f83a43454882b665acbc69"}, + {file = "influxdb_client-1.33.0.tar.gz", hash = "sha256:45f6a1763804a19b972890daf4c5f0bd2d3ae2f202b86451c579e09dcadd30e6"}, +] +iniconfig = [ + {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, + {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, +] +inputs = [ + {file = "inputs-0.5-py2.py3-none-any.whl", hash = "sha256:13f894564e52134cf1e3862b1811da034875eb1f2b62e6021e3776e9669a96ec"}, + {file = "inputs-0.5.tar.gz", hash = "sha256:a31d5b96a3525f1232f326be9e7ce8ccaf873c6b1fb84d9f3c9bc3d79b23eae4"}, +] +ioctl-opt = [ + {file = "ioctl-opt-1.2.2.tar.gz", hash = "sha256:9628bbd6728f90d019759f54d20b741ddbf9f8db8d41976da4332492f669d643"}, +] +ipykernel = [ + {file = "ipykernel-6.16.1-py3-none-any.whl", hash = "sha256:32eb7bdc5af57185e9a42b0dcef66413ef91a0490b378eae46cbdf0d4e0b5912"}, + {file = "ipykernel-6.16.1.tar.gz", hash = "sha256:3a27a550c1d682e7825f0f7732b0142b79ef1b21cd2e713cacac0c9847535f13"}, +] +ipython = [ + {file = "ipython-8.5.0-py3-none-any.whl", hash = "sha256:6f090e29ab8ef8643e521763a4f1f39dc3914db643122b1e9d3328ff2e43ada2"}, + {file = "ipython-8.5.0.tar.gz", hash = "sha256:097bdf5cd87576fd066179c9f7f208004f7a6864ee1b20f37d346c0bcb099f84"}, +] +ipython-genutils = [ + {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, + {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, +] +ipywidgets = [ + {file = "ipywidgets-8.0.2-py3-none-any.whl", hash = "sha256:1dc3dd4ee19ded045ea7c86eb273033d238d8e43f9e7872c52d092683f263891"}, + {file = "ipywidgets-8.0.2.tar.gz", hash = "sha256:08cb75c6e0a96836147cbfdc55580ae04d13e05d26ffbc377b4e1c68baa28b1f"}, +] +isodate = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +itsdangerous = [ + {file = "itsdangerous-2.1.2-py3-none-any.whl", hash = "sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44"}, + {file = "itsdangerous-2.1.2.tar.gz", hash = "sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a"}, +] +jaraco-classes = [ + {file = "jaraco.classes-3.2.3-py3-none-any.whl", hash = "sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158"}, + {file = "jaraco.classes-3.2.3.tar.gz", hash = "sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a"}, +] +jedi = [ + {file = "jedi-0.18.1-py2.py3-none-any.whl", hash = "sha256:637c9635fcf47945ceb91cd7f320234a7be540ded6f3e99a50cb6febdfd1ba8d"}, + {file = "jedi-0.18.1.tar.gz", hash = "sha256:74137626a64a99c8eb6ae5832d99b3bdd7d29a3850fe2aa80a4126b2a7d949ab"}, +] +jeepney = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] +jinja2 = [ + {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, + {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, +] +jmespath = [ + {file = "jmespath-1.0.1-py3-none-any.whl", hash = "sha256:02e2e4cc71b5bcab88332eebf907519190dd9e6e82107fa7f83b1003a6252980"}, + {file = "jmespath-1.0.1.tar.gz", hash = "sha256:90261b206d6defd58fdd5e85f478bf633a2901798906be2ad389150c5c60edbe"}, +] +joblib = [ + {file = "joblib-1.2.0-py3-none-any.whl", hash = "sha256:091138ed78f800342968c523bdde947e7a305b8594b910a0fea2ab83c3c6d385"}, + {file = "joblib-1.2.0.tar.gz", hash = "sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018"}, +] +json-logging-py = [ + {file = "json-logging-py-0.2.tar.gz", hash = "sha256:118b1fe1f4eacaea6370e5b9710d0f6d0c0a4599aef9d5b9875a6a579974fc9a"}, +] +json-rpc = [ + {file = "json-rpc-1.13.0.tar.gz", hash = "sha256:def0dbcf5b7084fc31d677f2f5990d988d06497f2f47f13024274cfb2d5d7589"}, + {file = "json_rpc-1.13.0-py2.py3-none-any.whl", hash = "sha256:84b45058e5ba95f49c7b6afcf7e03ab86bee89bf2c01f3ad8dd41fe114fc1f84"}, +] +json5 = [ + {file = "json5-0.9.10-py2.py3-none-any.whl", hash = "sha256:993189671e7412e9cdd8be8dc61cf402e8e579b35f1d1bb20ae6b09baa78bbce"}, + {file = "json5-0.9.10.tar.gz", hash = "sha256:ad9f048c5b5a4c3802524474ce40a622fae789860a86f10cc4f7e5f9cf9b46ab"}, +] +jsonschema = [ + {file = "jsonschema-4.16.0-py3-none-any.whl", hash = "sha256:9e74b8f9738d6a946d70705dc692b74b5429cd0960d58e79ffecfc43b2221eb9"}, + {file = "jsonschema-4.16.0.tar.gz", hash = "sha256:165059f076eff6971bae5b742fc029a7b4ef3f9bcf04c14e4776a7605de14b23"}, +] +jupyter = [ + {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, + {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, + {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, +] +jupyter-client = [ + {file = "jupyter_client-7.4.3-py3-none-any.whl", hash = "sha256:8845e3f5a339734b1ecc21d2100638aa1c7a145e356a31845f155cda5b624b1c"}, + {file = "jupyter_client-7.4.3.tar.gz", hash = "sha256:4fa2514cdb54dd9fbdcf7d7e4c5c3c8a973028168a8b4fc097b6aef625be13b0"}, +] +jupyter-console = [ + {file = "jupyter_console-6.4.4-py3-none-any.whl", hash = "sha256:756df7f4f60c986e7bc0172e4493d3830a7e6e75c08750bbe59c0a5403ad6dee"}, + {file = "jupyter_console-6.4.4.tar.gz", hash = "sha256:172f5335e31d600df61613a97b7f0352f2c8250bbd1092ef2d658f77249f89fb"}, +] +jupyter-core = [ + {file = "jupyter_core-4.11.2-py3-none-any.whl", hash = "sha256:3815e80ec5272c0c19aad087a0d2775df2852cfca8f5a17069e99c9350cecff8"}, + {file = "jupyter_core-4.11.2.tar.gz", hash = "sha256:c2909b9bc7dca75560a6c5ae78c34fd305ede31cd864da3c0d0bb2ed89aa9337"}, +] +jupyter-server = [ + {file = "jupyter_server-1.21.0-py3-none-any.whl", hash = "sha256:992531008544d77e05a16251cdfbd0bdff1b1efa14760c79b9cc776ac9214cf1"}, + {file = "jupyter_server-1.21.0.tar.gz", hash = "sha256:d0adca19913a3763359be7f0b8c2ea8bfde356f4b8edd8e3149d7d0fbfaa248b"}, +] +jupyterlab = [ + {file = "jupyterlab-3.4.8-py3-none-any.whl", hash = "sha256:4626a0434c76a3a22f11c4efaa1d031d2586367f72cfdbdbff6b08b6ef0060f7"}, + {file = "jupyterlab-3.4.8.tar.gz", hash = "sha256:1fafb8b657005d91603f3c3adfd6d9e8eaf33fdc601537fef09283332efe67cb"}, +] +jupyterlab-pygments = [ + {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, + {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, +] +jupyterlab-server = [ + {file = "jupyterlab_server-2.16.1-py3-none-any.whl", hash = "sha256:b572cd3e59b0722120f41d47f2363a0072765227184aea418b7cc276db4d75fd"}, + {file = "jupyterlab_server-2.16.1.tar.gz", hash = "sha256:fe0de558ff3bb447a32e24099aa7e17444fdbc8c08f6dbc0171cb1a0ae382d3f"}, +] +jupyterlab-vim = [ + {file = "jupyterlab_vim-0.15.1-py3-none-any.whl", hash = "sha256:e3ea4a0f140bb2956fa7c3b75442121cce264930fb94f06e5b0f75ccae2bde2e"}, + {file = "jupyterlab_vim-0.15.1.tar.gz", hash = "sha256:c4282beb4a67d21f7126e32cda19f99ed324a1d3fc494560e393fe53f7519a74"}, +] +jupyterlab-widgets = [ + {file = "jupyterlab_widgets-3.0.3-py3-none-any.whl", hash = "sha256:6aa1bc0045470d54d76b9c0b7609a8f8f0087573bae25700a370c11f82cb38c8"}, + {file = "jupyterlab_widgets-3.0.3.tar.gz", hash = "sha256:c767181399b4ca8b647befe2d913b1260f51bf9d8ef9b7a14632d4c1a7b536bd"}, +] +keyring = [ + {file = "keyring-23.9.3-py3-none-any.whl", hash = "sha256:69732a15cb1433bdfbc3b980a8a36a04878a6cfd7cb99f497b573f31618001c0"}, + {file = "keyring-23.9.3.tar.gz", hash = "sha256:69b01dd83c42f590250fe7a1f503fc229b14de83857314b1933a3ddbf595c4a5"}, +] +kiwisolver = [ + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2f5e60fabb7343a836360c4f0919b8cd0d6dbf08ad2ca6b9cf90bf0c76a3c4f6"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:10ee06759482c78bdb864f4109886dff7b8a56529bc1609d4f1112b93fe6423c"}, + {file = "kiwisolver-1.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c79ebe8f3676a4c6630fd3f777f3cfecf9289666c84e775a67d1d358578dc2e3"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abbe9fa13da955feb8202e215c4018f4bb57469b1b78c7a4c5c7b93001699938"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7577c1987baa3adc4b3c62c33bd1118c3ef5c8ddef36f0f2c950ae0b199e100d"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f8ad8285b01b0d4695102546b342b493b3ccc6781fc28c8c6a1bb63e95d22f09"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8ed58b8acf29798b036d347791141767ccf65eee7f26bde03a71c944449e53de"}, + {file = "kiwisolver-1.4.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a68b62a02953b9841730db7797422f983935aeefceb1679f0fc85cbfbd311c32"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win32.whl", hash = "sha256:e92a513161077b53447160b9bd8f522edfbed4bd9759e4c18ab05d7ef7e49408"}, + {file = "kiwisolver-1.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:3fe20f63c9ecee44560d0e7f116b3a747a5d7203376abeea292ab3152334d004"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:e0ea21f66820452a3f5d1655f8704a60d66ba1191359b96541eaf457710a5fc6"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bc9db8a3efb3e403e4ecc6cd9489ea2bac94244f80c78e27c31dcc00d2790ac2"}, + {file = "kiwisolver-1.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d5b61785a9ce44e5a4b880272baa7cf6c8f48a5180c3e81c59553ba0cb0821ca"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c2dbb44c3f7e6c4d3487b31037b1bdbf424d97687c1747ce4ff2895795c9bf69"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6295ecd49304dcf3bfbfa45d9a081c96509e95f4b9d0eb7ee4ec0530c4a96514"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4bd472dbe5e136f96a4b18f295d159d7f26fd399136f5b17b08c4e5f498cd494"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bf7d9fce9bcc4752ca4a1b80aabd38f6d19009ea5cbda0e0856983cf6d0023f5"}, + {file = "kiwisolver-1.4.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78d6601aed50c74e0ef02f4204da1816147a6d3fbdc8b3872d263338a9052c51"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:877272cf6b4b7e94c9614f9b10140e198d2186363728ed0f701c6eee1baec1da"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:db608a6757adabb32f1cfe6066e39b3706d8c3aa69bbc353a5b61edad36a5cb4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5853eb494c71e267912275e5586fe281444eb5e722de4e131cddf9d442615626"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:f0a1dbdb5ecbef0d34eb77e56fcb3e95bbd7e50835d9782a45df81cc46949750"}, + {file = "kiwisolver-1.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:283dffbf061a4ec60391d51e6155e372a1f7a4f5b15d59c8505339454f8989e4"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win32.whl", hash = "sha256:d06adcfa62a4431d404c31216f0f8ac97397d799cd53800e9d3efc2fbb3cf14e"}, + {file = "kiwisolver-1.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:e7da3fec7408813a7cebc9e4ec55afed2d0fd65c4754bc376bf03498d4e92686"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:62ac9cc684da4cf1778d07a89bf5f81b35834cb96ca523d3a7fb32509380cbf6"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:41dae968a94b1ef1897cb322b39360a0812661dba7c682aa45098eb8e193dbdf"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02f79693ec433cb4b5f51694e8477ae83b3205768a6fb48ffba60549080e295b"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d0611a0a2a518464c05ddd5a3a1a0e856ccc10e67079bb17f265ad19ab3c7597"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:db5283d90da4174865d520e7366801a93777201e91e79bacbac6e6927cbceede"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1041feb4cda8708ce73bb4dcb9ce1ccf49d553bf87c3954bdfa46f0c3f77252c"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win32.whl", hash = "sha256:a553dadda40fef6bfa1456dc4be49b113aa92c2a9a9e8711e955618cd69622e3"}, + {file = "kiwisolver-1.4.4-cp37-cp37m-win_amd64.whl", hash = "sha256:03baab2d6b4a54ddbb43bba1a3a2d1627e82d205c5cf8f4c924dc49284b87166"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:841293b17ad704d70c578f1f0013c890e219952169ce8a24ebc063eecf775454"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4f270de01dd3e129a72efad823da90cc4d6aafb64c410c9033aba70db9f1ff0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f9f39e2f049db33a908319cf46624a569b36983c7c78318e9726a4cb8923b26c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c97528e64cb9ebeff9701e7938653a9951922f2a38bd847787d4a8e498cc83ae"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d1573129aa0fd901076e2bfb4275a35f5b7aa60fbfb984499d661ec950320b0"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ad881edc7ccb9d65b0224f4e4d05a1e85cf62d73aab798943df6d48ab0cd79a1"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b428ef021242344340460fa4c9185d0b1f66fbdbfecc6c63eff4b7c29fad429d"}, + {file = "kiwisolver-1.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:2e407cb4bd5a13984a6c2c0fe1845e4e41e96f183e5e5cd4d77a857d9693494c"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win32.whl", hash = "sha256:75facbe9606748f43428fc91a43edb46c7ff68889b91fa31f53b58894503a191"}, + {file = "kiwisolver-1.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:5bce61af018b0cb2055e0e72e7d65290d822d3feee430b7b8203d8a855e78766"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8c808594c88a025d4e322d5bb549282c93c8e1ba71b790f539567932722d7bd8"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f0a71d85ecdd570ded8ac3d1c0f480842f49a40beb423bb8014539a9f32a5897"}, + {file = "kiwisolver-1.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b533558eae785e33e8c148a8d9921692a9fe5aa516efbdff8606e7d87b9d5824"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:efda5fc8cc1c61e4f639b8067d118e742b812c930f708e6667a5ce0d13499e29"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7c43e1e1206cd421cd92e6b3280d4385d41d7166b3ed577ac20444b6995a445f"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc8d3bd6c72b2dd9decf16ce70e20abcb3274ba01b4e1c96031e0c4067d1e7cd"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4ea39b0ccc4f5d803e3337dd46bcce60b702be4d86fd0b3d7531ef10fd99a1ac"}, + {file = "kiwisolver-1.4.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:968f44fdbf6dd757d12920d63b566eeb4d5b395fd2d00d29d7ef00a00582aac9"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win32.whl", hash = "sha256:da7e547706e69e45d95e116e6939488d62174e033b763ab1496b4c29b76fabea"}, + {file = "kiwisolver-1.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:ba59c92039ec0a66103b1d5fe588fa546373587a7d68f5c96f743c3396afc04b"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:91672bacaa030f92fc2f43b620d7b337fd9a5af28b0d6ed3f77afc43c4a64b5a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:787518a6789009c159453da4d6b683f468ef7a65bbde796bcea803ccf191058d"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da152d8cdcab0e56e4f45eb08b9aea6455845ec83172092f09b0e077ece2cf7a"}, + {file = "kiwisolver-1.4.4-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:ecb1fa0db7bf4cff9dac752abb19505a233c7f16684c5826d1f11ebd9472b871"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:28bc5b299f48150b5f822ce68624e445040595a4ac3d59251703779836eceff9"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:81e38381b782cc7e1e46c4e14cd997ee6040768101aefc8fa3c24a4cc58e98f8"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a66fdfb34e05b705620dd567f5a03f239a088d5a3f321e7b6ac3239d22aa286"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:872b8ca05c40d309ed13eb2e582cab0c5a05e81e987ab9c521bf05ad1d5cf5cb"}, + {file = "kiwisolver-1.4.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:70e7c2e7b750585569564e2e5ca9845acfaa5da56ac46df68414f29fea97be9f"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:9f85003f5dfa867e86d53fac6f7e6f30c045673fa27b603c397753bebadc3008"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e307eb9bd99801f82789b44bb45e9f541961831c7311521b13a6c85afc09767"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1792d939ec70abe76f5054d3f36ed5656021dcad1322d1cc996d4e54165cef9"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6cb459eea32a4e2cf18ba5fcece2dbdf496384413bc1bae15583f19e567f3b2"}, + {file = "kiwisolver-1.4.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:36dafec3d6d6088d34e2de6b85f9d8e2324eb734162fba59d2ba9ed7a2043d5b"}, + {file = "kiwisolver-1.4.4.tar.gz", hash = "sha256:d41997519fcba4a1e46eb4a2fe31bc12f0ff957b2b81bac28db24744f333e955"}, +] +knack = [ + {file = "knack-0.10.0-py3-none-any.whl", hash = "sha256:9e989286622d4a028ba738111f9fac475bae93146d375522141908ce43077cda"}, + {file = "knack-0.10.0.tar.gz", hash = "sha256:13190fa95d4c21bce04b4bee22d6a4e3fb19a93b6999b1d104bd02c476706c28"}, +] +lazy-object-proxy = [ + {file = "lazy-object-proxy-1.7.1.tar.gz", hash = "sha256:d609c75b986def706743cdebe5e47553f4a5a1da9c5ff66d76013ef396b5a8a4"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bb8c5fd1684d60a9902c60ebe276da1f2281a318ca16c1d0a96db28f62e9166b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a57d51ed2997e97f3b8e3500c984db50a554bb5db56c50b5dab1b41339b37e36"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd45683c3caddf83abbb1249b653a266e7069a09f486daa8863fb0e7496a9fdb"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8561da8b3dd22d696244d6d0d5330618c993a215070f473b699e00cf1f3f6443"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fccdf7c2c5821a8cbd0a9440a456f5050492f2270bd54e94360cac663398739b"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win32.whl", hash = "sha256:898322f8d078f2654d275124a8dd19b079080ae977033b713f677afcfc88e2b9"}, + {file = "lazy_object_proxy-1.7.1-cp310-cp310-win_amd64.whl", hash = "sha256:85b232e791f2229a4f55840ed54706110c80c0a210d076eee093f2b2e33e1bfd"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:46ff647e76f106bb444b4533bb4153c7370cdf52efc62ccfc1a28bdb3cc95442"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12f3bb77efe1367b2515f8cb4790a11cffae889148ad33adad07b9b55e0ab22c"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c19814163728941bb871240d45c4c30d33b8a2e85972c44d4e63dd7107faba44"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e40f2013d96d30217a51eeb1db28c9ac41e9d0ee915ef9d00da639c5b63f01a1"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:2052837718516a94940867e16b1bb10edb069ab475c3ad84fd1e1a6dd2c0fcfc"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win32.whl", hash = "sha256:6a24357267aa976abab660b1d47a34aaf07259a0c3859a34e536f1ee6e76b5bb"}, + {file = "lazy_object_proxy-1.7.1-cp36-cp36m-win_amd64.whl", hash = "sha256:6aff3fe5de0831867092e017cf67e2750c6a1c7d88d84d2481bd84a2e019ec35"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:6a6e94c7b02641d1311228a102607ecd576f70734dc3d5e22610111aeacba8a0"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4ce15276a1a14549d7e81c243b887293904ad2d94ad767f42df91e75fd7b5b6"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e368b7f7eac182a59ff1f81d5f3802161932a41dc1b1cc45c1f757dc876b5d2c"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ecbb350991d6434e1388bee761ece3260e5228952b1f0c46ffc800eb313ff42"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:553b0f0d8dbf21890dd66edd771f9b1b5f51bd912fa5f26de4449bfc5af5e029"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win32.whl", hash = "sha256:c7a683c37a8a24f6428c28c561c80d5f4fd316ddcf0c7cab999b15ab3f5c5c69"}, + {file = "lazy_object_proxy-1.7.1-cp37-cp37m-win_amd64.whl", hash = "sha256:df2631f9d67259dc9620d831384ed7732a198eb434eadf69aea95ad18c587a28"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:07fa44286cda977bd4803b656ffc1c9b7e3bc7dff7d34263446aec8f8c96f88a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4dca6244e4121c74cc20542c2ca39e5c4a5027c81d112bfb893cf0790f96f57e"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:91ba172fc5b03978764d1df5144b4ba4ab13290d7bab7a50f12d8117f8630c38"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:043651b6cb706eee4f91854da4a089816a6606c1428fd391573ef8cb642ae4f7"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b9e89b87c707dd769c4ea91f7a31538888aad05c116a59820f28d59b3ebfe25a"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win32.whl", hash = "sha256:9d166602b525bf54ac994cf833c385bfcc341b364e3ee71e3bf5a1336e677b55"}, + {file = "lazy_object_proxy-1.7.1-cp38-cp38-win_amd64.whl", hash = "sha256:8f3953eb575b45480db6568306893f0bd9d8dfeeebd46812aa09ca9579595148"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dd7ed7429dbb6c494aa9bc4e09d94b778a3579be699f9d67da7e6804c422d3de"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70ed0c2b380eb6248abdef3cd425fc52f0abd92d2b07ce26359fcbc399f636ad"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7096a5e0c1115ec82641afbdd70451a144558ea5cf564a896294e346eb611be1"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f769457a639403073968d118bc70110e7dce294688009f5c24ab78800ae56dc8"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:39b0e26725c5023757fc1ab2a89ef9d7ab23b84f9251e28f9cc114d5b59c1b09"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win32.whl", hash = "sha256:2130db8ed69a48a3440103d4a520b89d8a9405f1b06e2cc81640509e8bf6548f"}, + {file = "lazy_object_proxy-1.7.1-cp39-cp39-win_amd64.whl", hash = "sha256:677ea950bef409b47e51e733283544ac3d660b709cfce7b187f5ace137960d61"}, + {file = "lazy_object_proxy-1.7.1-pp37.pp38-none-any.whl", hash = "sha256:d66906d5785da8e0be7360912e99c9188b70f52c422f9fc18223347235691a84"}, +] +libusb1 = [ + {file = "libusb1-3.0.0-py3-none-any.whl", hash = "sha256:0e652b04cbe85ec8e74f9ee82b49f861fb14b5320ae51399387ad2601ccc0500"}, + {file = "libusb1-3.0.0-py3-none-win32.whl", hash = "sha256:083f75e5d15cb5e2159e64c308c5317284eae926a820d6dce53a9505d18be3b2"}, + {file = "libusb1-3.0.0-py3-none-win_amd64.whl", hash = "sha256:6f6bb010632ada35c661d17a65e135077beef0fbb2434d5ffdb3a4a911fd9490"}, + {file = "libusb1-3.0.0.tar.gz", hash = "sha256:5792a9defee40f15d330a40d9b1800545c32e47ba7fc66b6f28f133c9fcc8538"}, +] +lit = [ + {file = "lit-15.0.7.tar.gz", hash = "sha256:ed08ac55afe714a193653df293ae8a6ee6c45d6fb11eeca72ce347d99b88ecc8"}, +] +lockfile = [ + {file = "lockfile-0.12.2-py2.py3-none-any.whl", hash = "sha256:6c3cb24f344923d30b2785d5ad75182c8ea7ac1b6171b08657258ec7429d50fa"}, + {file = "lockfile-0.12.2.tar.gz", hash = "sha256:6aed02de03cba24efabcd600b30540140634fc06cfa603822d508d5361e9f799"}, +] +lru-dict = [ + {file = "lru-dict-1.1.8.tar.gz", hash = "sha256:878bc8ef4073e5cfb953dfc1cf4585db41e8b814c0106abde34d00ee0d0b3115"}, + {file = "lru_dict-1.1.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9d5815c0e85922cd0fb8344ca8b1c7cf020bf9fc45e670d34d51932c91fd7ec"}, + {file = "lru_dict-1.1.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f877f53249c3e49bbd7612f9083127290bede6c7d6501513567ab1bf9c581381"}, + {file = "lru_dict-1.1.8-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fef595c4f573141d54a38bda9221b9ee3cbe0acc73d67304a1a6d5972eb2a02"}, + {file = "lru_dict-1.1.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:db20597c4e67b4095b376ce2e83930c560f4ce481e8d05737885307ed02ba7c1"}, + {file = "lru_dict-1.1.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:5b09dbe47bc4b4d45ffe56067aff190bc3c0049575da6e52127e114236e0a6a7"}, + {file = "lru_dict-1.1.8-cp310-cp310-win32.whl", hash = "sha256:3b1692755fef288b67af5cd8a973eb331d1f44cb02cbdc13660040809c2bfec6"}, + {file = "lru_dict-1.1.8-cp310-cp310-win_amd64.whl", hash = "sha256:8f6561f9cd5a452cb84905c6a87aa944fdfdc0f41cc057d03b71f9b29b2cc4bd"}, + {file = "lru_dict-1.1.8-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:ca8f89361e0e7aad0bf93ae03a31502e96280faeb7fb92267f4998fb230d36b2"}, + {file = "lru_dict-1.1.8-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8c50ab9edaa5da5838426816a2b7bcde9d576b4fc50e6a8c062073dbc4969d78"}, + {file = "lru_dict-1.1.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fe16ade5fd0a57e9a335f69b8055aaa6fb278fbfa250458e4f6b8255115578f"}, + {file = "lru_dict-1.1.8-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:de972c7f4bc7b6002acff2a8de984c55fbd7f2289dba659cfd90f7a0f5d8f5d1"}, + {file = "lru_dict-1.1.8-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:3d003a864899c29b0379e412709a6e516cbd6a72ee10b09d0b33226343617412"}, + {file = "lru_dict-1.1.8-cp36-cp36m-win32.whl", hash = "sha256:6e2a7aa9e36626fb48fdc341c7e3685a31a7b50ea4918677ea436271ad0d904d"}, + {file = "lru_dict-1.1.8-cp36-cp36m-win_amd64.whl", hash = "sha256:d2ed4151445c3f30423c2698f72197d64b27b1cd61d8d56702ffe235584e47c2"}, + {file = "lru_dict-1.1.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:075b9dd46d7022b675419bc6e3631748ae184bc8af195d20365a98b4f3bb2914"}, + {file = "lru_dict-1.1.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70364e3cbef536adab8762b4835e18f5ca8e3fddd8bd0ec9258c42bbebd0ee77"}, + {file = "lru_dict-1.1.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:720f5728e537f11a311e8b720793a224e985d20e6b7c3d34a891a391865af1a2"}, + {file = "lru_dict-1.1.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c2fe692332c2f1d81fd27457db4b35143801475bfc2e57173a2403588dd82a42"}, + {file = "lru_dict-1.1.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:86d32a4498b74a75340497890a260d37bf1560ad2683969393032977dd36b088"}, + {file = "lru_dict-1.1.8-cp37-cp37m-win32.whl", hash = "sha256:348167f110494cfafae70c066470a6f4e4d43523933edf16ccdb8947f3b5fae0"}, + {file = "lru_dict-1.1.8-cp37-cp37m-win_amd64.whl", hash = "sha256:9be6c4039ef328676b868acea619cd100e3de1a35b3be211cf0eaf9775563b65"}, + {file = "lru_dict-1.1.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a777d48319d293b1b6a933d606c0e4899690a139b4c81173451913bbcab6f44f"}, + {file = "lru_dict-1.1.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99f6cfb3e28490357a0805b409caf693e46c61f8dbb789c51355adb693c568d3"}, + {file = "lru_dict-1.1.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:163079dbda54c3e6422b23da39fb3ecc561035d65e8496ff1950cbdb376018e1"}, + {file = "lru_dict-1.1.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:0972d669e9e207617e06416166718b073a49bf449abbd23940d9545c0847a4d9"}, + {file = "lru_dict-1.1.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:97c24ffc55de6013075979f440acd174e88819f30387074639fb7d7178ca253e"}, + {file = "lru_dict-1.1.8-cp38-cp38-win32.whl", hash = "sha256:0f83cd70a6d32f9018d471be609f3af73058f700691657db4a3d3dd78d3f96dd"}, + {file = "lru_dict-1.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:add762163f4af7f4173fafa4092eb7c7f023cf139ef6d2015cfea867e1440d82"}, + {file = "lru_dict-1.1.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:484ac524e4615f06dc72ffbfd83f26e073c9ec256de5413634fbd024c010a8bc"}, + {file = "lru_dict-1.1.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7284bdbc5579bbdc3fc8f869ed4c169f403835566ab0f84567cdbfdd05241847"}, + {file = "lru_dict-1.1.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ca497cb25f19f24171f9172805f3ff135b911aeb91960bd4af8e230421ccb51"}, + {file = "lru_dict-1.1.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1df1da204a9f0b5eb8393a46070f1d984fa8559435ee790d7f8f5602038fc00"}, + {file = "lru_dict-1.1.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:f4d0a6d733a23865019b1c97ed6fb1fdb739be923192abf4dbb644f697a26a69"}, + {file = "lru_dict-1.1.8-cp39-cp39-win32.whl", hash = "sha256:7be1b66926277993cecdc174c15a20c8ce785c1f8b39aa560714a513eef06473"}, + {file = "lru_dict-1.1.8-cp39-cp39-win_amd64.whl", hash = "sha256:881104711900af45967c2e5ce3e62291dd57d5b2a224d58b7c9f60bf4ad41b8c"}, + {file = "lru_dict-1.1.8-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:beb089c46bd95243d1ac5b2bd13627317b08bf40dd8dc16d4b7ee7ecb3cf65ca"}, + {file = "lru_dict-1.1.8-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10fe823ff90b655f0b6ba124e2b576ecda8c61b8ead76b456db67831942d22f2"}, + {file = "lru_dict-1.1.8-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c07163c9dcbb2eca377f366b1331f46302fd8b6b72ab4d603087feca00044bb0"}, + {file = "lru_dict-1.1.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93336911544ebc0e466272043adab9fb9f6e9dcba6024b639c32553a3790e089"}, + {file = "lru_dict-1.1.8-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:55aeda6b6789b2d030066b4f5f6fc3596560ba2a69028f35f3682a795701b5b1"}, + {file = "lru_dict-1.1.8-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:262a4e622010ceb960a6a5222ed011090e50954d45070fd369c0fa4d2ed7d9a9"}, + {file = "lru_dict-1.1.8-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b6f64005ede008b7a866be8f3f6274dbf74e656e15e4004e9d99ad65efb01809"}, + {file = "lru_dict-1.1.8-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:9d70257246b8207e8ef3d8b18457089f5ff0dfb087bd36eb33bce6584f2e0b3a"}, + {file = "lru_dict-1.1.8-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:f874e9c2209dada1a080545331aa1277ec060a13f61684a8642788bf44b2325f"}, + {file = "lru_dict-1.1.8-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a592363c93d6fc6472d5affe2819e1c7590746aecb464774a4f67e09fbefdfc"}, + {file = "lru_dict-1.1.8-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2f340b61f3cdfee71f66da7dbfd9a5ea2db6974502ccff2065cdb76619840dca"}, + {file = "lru_dict-1.1.8-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:9447214e4857e16d14158794ef01e4501d8fad07d298d03308d9f90512df02fa"}, +] +lxml = [ + {file = "lxml-4.9.1-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:98cafc618614d72b02185ac583c6f7796202062c41d2eeecdf07820bad3295ed"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c62e8dd9754b7debda0c5ba59d34509c4688f853588d75b53c3791983faa96fc"}, + {file = "lxml-4.9.1-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:21fb3d24ab430fc538a96e9fbb9b150029914805d551deeac7d7822f64631dfc"}, + {file = "lxml-4.9.1-cp27-cp27m-win32.whl", hash = "sha256:86e92728ef3fc842c50a5cb1d5ba2bc66db7da08a7af53fb3da79e202d1b2cd3"}, + {file = "lxml-4.9.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4cfbe42c686f33944e12f45a27d25a492cc0e43e1dc1da5d6a87cbcaf2e95627"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:dad7b164905d3e534883281c050180afcf1e230c3d4a54e8038aa5cfcf312b84"}, + {file = "lxml-4.9.1-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a614e4afed58c14254e67862456d212c4dcceebab2eaa44d627c2ca04bf86837"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:f9ced82717c7ec65a67667bb05865ffe38af0e835cdd78728f1209c8fffe0cad"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:d9fc0bf3ff86c17348dfc5d322f627d78273eba545db865c3cd14b3f19e57fa5"}, + {file = "lxml-4.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:e5f66bdf0976ec667fc4594d2812a00b07ed14d1b44259d19a41ae3fff99f2b8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:fe17d10b97fdf58155f858606bddb4e037b805a60ae023c009f760d8361a4eb8"}, + {file = "lxml-4.9.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8caf4d16b31961e964c62194ea3e26a0e9561cdf72eecb1781458b67ec83423d"}, + {file = "lxml-4.9.1-cp310-cp310-win32.whl", hash = "sha256:4780677767dd52b99f0af1f123bc2c22873d30b474aa0e2fc3fe5e02217687c7"}, + {file = "lxml-4.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:b122a188cd292c4d2fcd78d04f863b789ef43aa129b233d7c9004de08693728b"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:be9eb06489bc975c38706902cbc6888f39e946b81383abc2838d186f0e8b6a9d"}, + {file = "lxml-4.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:f1be258c4d3dc609e654a1dc59d37b17d7fef05df912c01fc2e15eb43a9735f3"}, + {file = "lxml-4.9.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:927a9dd016d6033bc12e0bf5dee1dde140235fc8d0d51099353c76081c03dc29"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9232b09f5efee6a495a99ae6824881940d6447debe272ea400c02e3b68aad85d"}, + {file = "lxml-4.9.1-cp35-cp35m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:04da965dfebb5dac2619cb90fcf93efdb35b3c6994fea58a157a834f2f94b318"}, + {file = "lxml-4.9.1-cp35-cp35m-win32.whl", hash = "sha256:4d5bae0a37af799207140652a700f21a85946f107a199bcb06720b13a4f1f0b7"}, + {file = "lxml-4.9.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4878e667ebabe9b65e785ac8da4d48886fe81193a84bbe49f12acff8f7a383a4"}, + {file = "lxml-4.9.1-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:1355755b62c28950f9ce123c7a41460ed9743c699905cbe664a5bcc5c9c7c7fb"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:bcaa1c495ce623966d9fc8a187da80082334236a2a1c7e141763ffaf7a405067"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6eafc048ea3f1b3c136c71a86db393be36b5b3d9c87b1c25204e7d397cee9536"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:13c90064b224e10c14dcdf8086688d3f0e612db53766e7478d7754703295c7c8"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:206a51077773c6c5d2ce1991327cda719063a47adc02bd703c56a662cdb6c58b"}, + {file = "lxml-4.9.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e8f0c9d65da595cfe91713bc1222af9ecabd37971762cb830dea2fc3b3bb2acf"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:8f0a4d179c9a941eb80c3a63cdb495e539e064f8054230844dcf2fcb812b71d3"}, + {file = "lxml-4.9.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:830c88747dce8a3e7525defa68afd742b4580df6aa2fdd6f0855481e3994d391"}, + {file = "lxml-4.9.1-cp36-cp36m-win32.whl", hash = "sha256:1e1cf47774373777936c5aabad489fef7b1c087dcd1f426b621fda9dcc12994e"}, + {file = "lxml-4.9.1-cp36-cp36m-win_amd64.whl", hash = "sha256:5974895115737a74a00b321e339b9c3f45c20275d226398ae79ac008d908bff7"}, + {file = "lxml-4.9.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:1423631e3d51008871299525b541413c9b6c6423593e89f9c4cfbe8460afc0a2"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:2aaf6a0a6465d39b5ca69688fce82d20088c1838534982996ec46633dc7ad6cc"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:9f36de4cd0c262dd9927886cc2305aa3f2210db437aa4fed3fb4940b8bf4592c"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:ae06c1e4bc60ee076292e582a7512f304abdf6c70db59b56745cca1684f875a4"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:57e4d637258703d14171b54203fd6822fda218c6c2658a7d30816b10995f29f3"}, + {file = "lxml-4.9.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6d279033bf614953c3fc4a0aa9ac33a21e8044ca72d4fa8b9273fe75359d5cca"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:a60f90bba4c37962cbf210f0188ecca87daafdf60271f4c6948606e4dabf8785"}, + {file = "lxml-4.9.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6ca2264f341dd81e41f3fffecec6e446aa2121e0b8d026fb5130e02de1402785"}, + {file = "lxml-4.9.1-cp37-cp37m-win32.whl", hash = "sha256:27e590352c76156f50f538dbcebd1925317a0f70540f7dc8c97d2931c595783a"}, + {file = "lxml-4.9.1-cp37-cp37m-win_amd64.whl", hash = "sha256:eea5d6443b093e1545ad0210e6cf27f920482bfcf5c77cdc8596aec73523bb7e"}, + {file = "lxml-4.9.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f05251bbc2145349b8d0b77c0d4e5f3b228418807b1ee27cefb11f69ed3d233b"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:487c8e61d7acc50b8be82bda8c8d21d20e133c3cbf41bd8ad7eb1aaeb3f07c97"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:8d1a92d8e90b286d491e5626af53afef2ba04da33e82e30744795c71880eaa21"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:b570da8cd0012f4af9fa76a5635cd31f707473e65a5a335b186069d5c7121ff2"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ef87fca280fb15342726bd5f980f6faf8b84a5287fcc2d4962ea8af88b35130"}, + {file = "lxml-4.9.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:93e414e3206779ef41e5ff2448067213febf260ba747fc65389a3ddaa3fb8715"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6653071f4f9bac46fbc30f3c7838b0e9063ee335908c5d61fb7a4a86c8fd2036"}, + {file = "lxml-4.9.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:32a73c53783becdb7eaf75a2a1525ea8e49379fb7248c3eeefb9412123536387"}, + {file = "lxml-4.9.1-cp38-cp38-win32.whl", hash = "sha256:1a7c59c6ffd6ef5db362b798f350e24ab2cfa5700d53ac6681918f314a4d3b94"}, + {file = "lxml-4.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:1436cf0063bba7888e43f1ba8d58824f085410ea2025befe81150aceb123e345"}, + {file = "lxml-4.9.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:4beea0f31491bc086991b97517b9683e5cfb369205dac0148ef685ac12a20a67"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:41fb58868b816c202e8881fd0f179a4644ce6e7cbbb248ef0283a34b73ec73bb"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:bd34f6d1810d9354dc7e35158aa6cc33456be7706df4420819af6ed966e85448"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:edffbe3c510d8f4bf8640e02ca019e48a9b72357318383ca60e3330c23aaffc7"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6d949f53ad4fc7cf02c44d6678e7ff05ec5f5552b235b9e136bd52e9bf730b91"}, + {file = "lxml-4.9.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:079b68f197c796e42aa80b1f739f058dcee796dc725cc9a1be0cdb08fc45b000"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9c3a88d20e4fe4a2a4a84bf439a5ac9c9aba400b85244c63a1ab7088f85d9d25"}, + {file = "lxml-4.9.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4e285b5f2bf321fc0857b491b5028c5f276ec0c873b985d58d7748ece1d770dd"}, + {file = "lxml-4.9.1-cp39-cp39-win32.whl", hash = "sha256:ef72013e20dd5ba86a8ae1aed7f56f31d3374189aa8b433e7b12ad182c0d2dfb"}, + {file = "lxml-4.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:10d2017f9150248563bb579cd0d07c61c58da85c922b780060dcc9a3aa9f432d"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538747a9d7827ce3e16a8fdd201a99e661c7dee3c96c885d8ecba3c35d1032c"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:0645e934e940107e2fdbe7c5b6fb8ec6232444260752598bc4d09511bd056c0b"}, + {file = "lxml-4.9.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:6daa662aba22ef3258934105be2dd9afa5bb45748f4f702a3b39a5bf53a1f4dc"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-macosx_10_15_x86_64.whl", hash = "sha256:603a464c2e67d8a546ddaa206d98e3246e5db05594b97db844c2f0a1af37cf5b"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c4b2e0559b68455c085fb0f6178e9752c4be3bba104d6e881eb5573b399d1eb2"}, + {file = "lxml-4.9.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0f3f0059891d3254c7b5fb935330d6db38d6519ecd238ca4fce93c234b4a0f73"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_24_i686.whl", hash = "sha256:c852b1530083a620cb0de5f3cd6826f19862bafeaf77586f1aef326e49d95f0c"}, + {file = "lxml-4.9.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:287605bede6bd36e930577c5925fcea17cb30453d96a7b4c63c14a257118dbb9"}, + {file = "lxml-4.9.1.tar.gz", hash = "sha256:fe749b052bb7233fe5d072fcb549221a8cb1a16725c47c37e42b0b9cb3ff2c3f"}, +] +mako = [ + {file = "Mako-1.2.3-py3-none-any.whl", hash = "sha256:c413a086e38cd885088d5e165305ee8eed04e8b3f8f62df343480da0a385735f"}, + {file = "Mako-1.2.3.tar.gz", hash = "sha256:7fde96466fcfeedb0eed94f187f20b23d85e4cb41444be0e542e2c8c65c396cd"}, +] +markdown = [ + {file = "Markdown-3.4.1-py3-none-any.whl", hash = "sha256:08fb8465cffd03d10b9dd34a5c3fea908e20391a2a90b88d66362cb05beed186"}, + {file = "Markdown-3.4.1.tar.gz", hash = "sha256:3b809086bb6efad416156e00a0da66fe47618a5d6918dd688f53f40c8e4cfeff"}, +] +markdown-it-py = [ + {file = "markdown-it-py-2.1.0.tar.gz", hash = "sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da"}, + {file = "markdown_it_py-2.1.0-py3-none-any.whl", hash = "sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27"}, +] +markupsafe = [ + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win32.whl", hash = "sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6"}, + {file = "MarkupSafe-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win32.whl", hash = "sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff"}, + {file = "MarkupSafe-2.1.1-cp37-cp37m-win_amd64.whl", hash = "sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win32.whl", hash = "sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1"}, + {file = "MarkupSafe-2.1.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win32.whl", hash = "sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c"}, + {file = "MarkupSafe-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247"}, + {file = "MarkupSafe-2.1.1.tar.gz", hash = "sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b"}, +] +matplotlib = [ + {file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_universal2.whl", hash = "sha256:7730e60e751cfcfe7fcb223cf03c0b979e9a064c239783ad37929d340a364cef"}, + {file = "matplotlib-3.6.1-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:9dd40505ccc526acaf9a5db1b3029e237c64b58f1249983b28a291c2d6a1d0fa"}, + {file = "matplotlib-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:85948b303534b69fd771126764cf883fde2af9b003eb5778cb60f3b46f93d3f6"}, + {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:71eced071825005011cdc64efbae2e2c76b8209c18aa487dedf69796fe4b1e40"}, + {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:220314c2d6b9ca11570d7cd4b841c9f3137546f188336003b9fb8def4dcb804d"}, + {file = "matplotlib-3.6.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2cc5d726d4d42865f909c5208a7841109d76584950dd0587b01a77cc279d4ab7"}, + {file = "matplotlib-3.6.1-cp310-cp310-win32.whl", hash = "sha256:183bf3ac6a6023ee590aa4b677f391ceed65ec0d6b930901a8483c267bd12995"}, + {file = "matplotlib-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:a68b91ac7e6bb26100a540a033f54c95fe06d9c0aa51312c2a52d07d1bde78f4"}, + {file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_universal2.whl", hash = "sha256:4648f0d79a87bf50ee740058305c91091ee5e1fbb71a7d2f5fe6707bfe328d1c"}, + {file = "matplotlib-3.6.1-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:9403764017d20ff570f7ce973a8b9637f08a6109118f4e0ce6c7493d8849a0d3"}, + {file = "matplotlib-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4c8b5a243dd29d50289d694e931bd6cb6ae0b5bd654d12c647543d63862540c"}, + {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c1effccef0cea2d4da9feeed22079adf6786f92c800a7d0d2ef2104318a1c66c"}, + {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8dc25473319afabe49150267e54648ac559c33b0fc2a80c8caecfbbc2948a820"}, + {file = "matplotlib-3.6.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47cb088bbce82ae9fc2edf3c25e56a5c6142ce2553fea2b781679f960a70c207"}, + {file = "matplotlib-3.6.1-cp311-cp311-win32.whl", hash = "sha256:4d3b0e0a4611bd22065bbf47e9b2f689ac9e575bcb850a9f0ae2bbed75cab956"}, + {file = "matplotlib-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:e3c116e779fbbf421a9e4d3060db259a9bb486d98f4e3c5a0877c599bd173582"}, + {file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_universal2.whl", hash = "sha256:565f514dec81a41cbed10eb6011501879695087fc2787fb89423a466508abbbd"}, + {file = "matplotlib-3.6.1-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:05e86446562063d6186ff6d700118c0dbd5dccc403a6187351ee526c48878f10"}, + {file = "matplotlib-3.6.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:8245e85fd793f58edf29b8a9e3be47e8ecf76ea1a1e8240545f2746181ca5787"}, + {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1e2c75d5d1ff6b7ef9870360bfa23bea076b8dc0945a60d19453d7619ed9ea8f"}, + {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c9756a8e69f6e1f76d47eb42132175b6814da1fbeae0545304c6d0fc2aae252a"}, + {file = "matplotlib-3.6.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f5788168da2661b42f7468063b725cc73fdbeeb80f2704cb2d8c415e9a57c50"}, + {file = "matplotlib-3.6.1-cp38-cp38-win32.whl", hash = "sha256:0bab7564aafd5902128d54b68dca04f5755413fb6b502100bb0235a545882c48"}, + {file = "matplotlib-3.6.1-cp38-cp38-win_amd64.whl", hash = "sha256:3c53486278a0629fd892783271dc994b962fba8dfe207445d039e14f1928ea46"}, + {file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_universal2.whl", hash = "sha256:27337bcb38d5db7430c14f350924542d75416ec1546d5d9d9f39b362b71db3fb"}, + {file = "matplotlib-3.6.1-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:fad858519bd6d52dbfeebdbe04d00dd8e932ed436f1c535e61bcc970a96c11e4"}, + {file = "matplotlib-3.6.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4a3d903588b519b38ed085d0ae762a1dcd4b70164617292175cfd91b90d6c415"}, + {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:87bdbd37d0a41e025879863fe9b17bab15c0421313bc33e77e5e1aa54215c9c5"}, + {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e632f66218811d4cf8b7a2a649e25ec15406c3c498f72d19e2bcf8377f38445d"}, + {file = "matplotlib-3.6.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ddd58324dc9a77e2e56d7b7aea7dbd0575b6f7cd1333c3ca9d388ac70978344"}, + {file = "matplotlib-3.6.1-cp39-cp39-win32.whl", hash = "sha256:12ab21d0cad122f5b23688d453a0280676e7c42f634f0dbd093d15d42d142b1f"}, + {file = "matplotlib-3.6.1-cp39-cp39-win_amd64.whl", hash = "sha256:563896ba269324872ace436a57775dcc8322678a9496b28a8c25cdafa5ec2b92"}, + {file = "matplotlib-3.6.1-pp38-pypy38_pp73-macosx_10_12_x86_64.whl", hash = "sha256:52935b7d4ccbf0dbc9cf454dbb10ca99c11cbe8da9467596b96e5e21fd4dfc5c"}, + {file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87027ff7b2edeb14476900261ef04d4beae949e1dfa0a3eb3ad6a6efbf9d0e1d"}, + {file = "matplotlib-3.6.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4de03085afb3b80fab341afaf8e60dfe06ce439b6dfed55d657cf34a7bc3c40"}, + {file = "matplotlib-3.6.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b53387d4e59432ff221540a4ffb5ee9669c69417805e4faf0148a00d701c61f9"}, + {file = "matplotlib-3.6.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:02561141c434154f7bae8e5449909d152367cb40aa57bfb2a27f2748b9c5f95f"}, + {file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0161ebf87518ecfe0980c942d5f0d5df0e080c1746ebaab2027a969967014b7"}, + {file = "matplotlib-3.6.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2469f57e4c5cc0e85eddc7b30995ea9c404a78c0b1856da75d1a5887156ca350"}, + {file = "matplotlib-3.6.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5f97141e05baf160c3ec125f06ceb2a44c9bb62f42fcb8ee1c05313c73e99432"}, + {file = "matplotlib-3.6.1.tar.gz", hash = "sha256:e2d1b7225666f7e1bcc94c0bc9c587a82e3e8691da4757e357e5c2515222ee37"}, +] +matplotlib-inline = [ + {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, + {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, +] +mccabe = [ + {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, + {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, +] +mdit-py-plugins = [ + {file = "mdit-py-plugins-0.3.1.tar.gz", hash = "sha256:3fc13298497d6e04fe96efdd41281bfe7622152f9caa1815ea99b5c893de9441"}, + {file = "mdit_py_plugins-0.3.1-py3-none-any.whl", hash = "sha256:606a7f29cf56dbdfaf914acb21709b8f8ee29d857e8f29dcc33d8cb84c57bfa1"}, +] +mdurl = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] +mistune = [ + {file = "mistune-2.0.4-py2.py3-none-any.whl", hash = "sha256:182cc5ee6f8ed1b807de6b7bb50155df7b66495412836b9a74c8fbdfc75fe36d"}, + {file = "mistune-2.0.4.tar.gz", hash = "sha256:9ee0a66053e2267aba772c71e06891fa8f1af6d4b01d5e84e267b4570d4d9808"}, +] +more-itertools = [ + {file = "more-itertools-9.0.0.tar.gz", hash = "sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab"}, + {file = "more_itertools-9.0.0-py3-none-any.whl", hash = "sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41"}, +] +mpld3 = [ + {file = "mpld3-0.5.8-py3-none-any.whl", hash = "sha256:41938e65de4ba41a1b53d92e7c8609e7172e09b33ef5db42bb6f73701106c8b7"}, + {file = "mpld3-0.5.8.tar.gz", hash = "sha256:1a167dbef836dd7c66d8aa71c06a32d50bffa18725f304d93cb74fdb3545043b"}, +] +mpmath = [ + {file = "mpmath-1.2.1-py3-none-any.whl", hash = "sha256:604bc21bd22d2322a177c73bdb573994ef76e62edd595d17e00aff24b0667e5c"}, + {file = "mpmath-1.2.1.tar.gz", hash = "sha256:79ffb45cf9f4b101a807595bcb3e72e0396202e0b1d25d689134b48c4216a81a"}, +] +msal = [ + {file = "msal-1.20.0b1-py2.py3-none-any.whl", hash = "sha256:07805ade58ce76bdaa4f6a3f7e61477901d5145abd7959a850eb6c49a2fbaea6"}, + {file = "msal-1.20.0b1.tar.gz", hash = "sha256:8c0d7238b7bc5abe86515568c4fd59e53c5ccca159ef02b369f867b951480c4d"}, +] +msal-extensions = [ + {file = "msal-extensions-1.0.0.tar.gz", hash = "sha256:c676aba56b0cce3783de1b5c5ecfe828db998167875126ca4b47dc6436451354"}, + {file = "msal_extensions-1.0.0-py2.py3-none-any.whl", hash = "sha256:91e3db9620b822d0ed2b4d1850056a0f133cba04455e62f11612e40f5502f2ee"}, +] +msgpack = [ + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4ab251d229d10498e9a2f3b1e68ef64cb393394ec477e3370c457f9430ce9250"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:112b0f93202d7c0fef0b7810d465fde23c746a2d482e1e2de2aafd2ce1492c88"}, + {file = "msgpack-1.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:002b5c72b6cd9b4bafd790f364b8480e859b4712e91f43014fe01e4f957b8467"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35bc0faa494b0f1d851fd29129b2575b2e26d41d177caacd4206d81502d4c6a6"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4733359808c56d5d7756628736061c432ded018e7a1dff2d35a02439043321aa"}, + {file = "msgpack-1.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb514ad14edf07a1dbe63761fd30f89ae79b42625731e1ccf5e1f1092950eaa6"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c23080fdeec4716aede32b4e0ef7e213c7b1093eede9ee010949f2a418ced6ba"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:49565b0e3d7896d9ea71d9095df15b7f75a035c49be733051c34762ca95bbf7e"}, + {file = "msgpack-1.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:aca0f1644d6b5a73eb3e74d4d64d5d8c6c3d577e753a04c9e9c87d07692c58db"}, + {file = "msgpack-1.0.4-cp310-cp310-win32.whl", hash = "sha256:0dfe3947db5fb9ce52aaea6ca28112a170db9eae75adf9339a1aec434dc954ef"}, + {file = "msgpack-1.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dea20515f660aa6b7e964433b1808d098dcfcabbebeaaad240d11f909298075"}, + {file = "msgpack-1.0.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:e83f80a7fec1a62cf4e6c9a660e39c7f878f603737a0cdac8c13131d11d97f52"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c11a48cf5e59026ad7cb0dc29e29a01b5a66a3e333dc11c04f7e991fc5510a9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1276e8f34e139aeff1c77a3cefb295598b504ac5314d32c8c3d54d24fadb94c9"}, + {file = "msgpack-1.0.4-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c9566f2c39ccced0a38d37c26cc3570983b97833c365a6044edef3574a00c08"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:fcb8a47f43acc113e24e910399376f7277cf8508b27e5b88499f053de6b115a8"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:76ee788122de3a68a02ed6f3a16bbcd97bc7c2e39bd4d94be2f1821e7c4a64e6"}, + {file = "msgpack-1.0.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:0a68d3ac0104e2d3510de90a1091720157c319ceeb90d74f7b5295a6bee51bae"}, + {file = "msgpack-1.0.4-cp36-cp36m-win32.whl", hash = "sha256:85f279d88d8e833ec015650fd15ae5eddce0791e1e8a59165318f371158efec6"}, + {file = "msgpack-1.0.4-cp36-cp36m-win_amd64.whl", hash = "sha256:c1683841cd4fa45ac427c18854c3ec3cd9b681694caf5bff04edb9387602d661"}, + {file = "msgpack-1.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a75dfb03f8b06f4ab093dafe3ddcc2d633259e6c3f74bb1b01996f5d8aa5868c"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9667bdfdf523c40d2511f0e98a6c9d3603be6b371ae9a238b7ef2dc4e7a427b0"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11184bc7e56fd74c00ead4f9cc9a3091d62ecb96e97653add7a879a14b003227"}, + {file = "msgpack-1.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ac5bd7901487c4a1dd51a8c58f2632b15d838d07ceedaa5e4c080f7190925bff"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1e91d641d2bfe91ba4c52039adc5bccf27c335356055825c7f88742c8bb900dd"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2a2df1b55a78eb5f5b7d2a4bb221cd8363913830145fad05374a80bf0877cb1e"}, + {file = "msgpack-1.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:545e3cf0cf74f3e48b470f68ed19551ae6f9722814ea969305794645da091236"}, + {file = "msgpack-1.0.4-cp37-cp37m-win32.whl", hash = "sha256:2cc5ca2712ac0003bcb625c96368fd08a0f86bbc1a5578802512d87bc592fe44"}, + {file = "msgpack-1.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:eba96145051ccec0ec86611fe9cf693ce55f2a3ce89c06ed307de0e085730ec1"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:7760f85956c415578c17edb39eed99f9181a48375b0d4a94076d84148cf67b2d"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:449e57cc1ff18d3b444eb554e44613cffcccb32805d16726a5494038c3b93dab"}, + {file = "msgpack-1.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d603de2b8d2ea3f3bcb2efe286849aa7a81531abc52d8454da12f46235092bcb"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:48f5d88c99f64c456413d74a975bd605a9b0526293218a3b77220a2c15458ba9"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6916c78f33602ecf0509cc40379271ba0f9ab572b066bd4bdafd7434dee4bc6e"}, + {file = "msgpack-1.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:81fc7ba725464651190b196f3cd848e8553d4d510114a954681fd0b9c479d7e1"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d5b5b962221fa2c5d3a7f8133f9abffc114fe218eb4365e40f17732ade576c8e"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:77ccd2af37f3db0ea59fb280fa2165bf1b096510ba9fe0cc2bf8fa92a22fdb43"}, + {file = "msgpack-1.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b17be2478b622939e39b816e0aa8242611cc8d3583d1cd8ec31b249f04623243"}, + {file = "msgpack-1.0.4-cp38-cp38-win32.whl", hash = "sha256:2bb8cdf50dd623392fa75525cce44a65a12a00c98e1e37bf0fb08ddce2ff60d2"}, + {file = "msgpack-1.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:26b8feaca40a90cbe031b03d82b2898bf560027160d3eae1423f4a67654ec5d6"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:462497af5fd4e0edbb1559c352ad84f6c577ffbbb708566a0abaaa84acd9f3ae"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:2999623886c5c02deefe156e8f869c3b0aaeba14bfc50aa2486a0415178fce55"}, + {file = "msgpack-1.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f0029245c51fd9473dc1aede1160b0a29f4a912e6b1dd353fa6d317085b219da"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed6f7b854a823ea44cf94919ba3f727e230da29feb4a99711433f25800cf747f"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0df96d6eaf45ceca04b3f3b4b111b86b33785683d682c655063ef8057d61fd92"}, + {file = "msgpack-1.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6a4192b1ab40f8dca3f2877b70e63799d95c62c068c84dc028b40a6cb03ccd0f"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e3590f9fb9f7fbc36df366267870e77269c03172d086fa76bb4eba8b2b46624"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:1576bd97527a93c44fa856770197dec00d223b0b9f36ef03f65bac60197cedf8"}, + {file = "msgpack-1.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:63e29d6e8c9ca22b21846234913c3466b7e4ee6e422f205a2988083de3b08cae"}, + {file = "msgpack-1.0.4-cp39-cp39-win32.whl", hash = "sha256:fb62ea4b62bfcb0b380d5680f9a4b3f9a2d166d9394e9bbd9666c0ee09a3645c"}, + {file = "msgpack-1.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:4d5834a2a48965a349da1c5a79760d94a1a0172fbb5ab6b5b33cbf8447e109ce"}, + {file = "msgpack-1.0.4.tar.gz", hash = "sha256:f5d869c18f030202eb412f08b28d2afeea553d6613aee89e200d7aca7ef01f5f"}, +] +msgpack-numpy = [ + {file = "msgpack-numpy-0.4.8.tar.gz", hash = "sha256:c667d3180513422f9c7545be5eec5d296dcbb357e06f72ed39cc683797556e69"}, + {file = "msgpack_numpy-0.4.8-py2.py3-none-any.whl", hash = "sha256:773c19d4dfbae1b3c7b791083e2caf66983bb19b40901646f61d8731554ae3da"}, +] +msgpack-python = [ + {file = "msgpack-python-0.5.6.tar.gz", hash = "sha256:378cc8a6d3545b532dfd149da715abae4fda2a3adb6d74e525d0d5e51f46909b"}, +] +msrest = [ + {file = "msrest-0.7.1-py3-none-any.whl", hash = "sha256:21120a810e1233e5e6cc7fe40b474eeb4ec6f757a15d7cf86702c369f9567c32"}, + {file = "msrest-0.7.1.zip", hash = "sha256:6e7661f46f3afd88b75667b7187a92829924446c7ea1d169be8c4bb7eeb788b9"}, +] +msrestazure = [ + {file = "msrestazure-0.6.4-py2.py3-none-any.whl", hash = "sha256:3de50f56147ef529b31e099a982496690468ecef33f0544cb0fa0cfe1e1de5b9"}, + {file = "msrestazure-0.6.4.tar.gz", hash = "sha256:a06f0dabc9a6f5efe3b6add4bd8fb623aeadacf816b7a35b0f89107e0544d189"}, +] +multidict = [ + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3"}, + {file = "multidict-6.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88"}, + {file = "multidict-6.0.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87"}, + {file = "multidict-6.0.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389"}, + {file = "multidict-6.0.2-cp310-cp310-win32.whl", hash = "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293"}, + {file = "multidict-6.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658"}, + {file = "multidict-6.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b"}, + {file = "multidict-6.0.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b"}, + {file = "multidict-6.0.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15"}, + {file = "multidict-6.0.2-cp37-cp37m-win32.whl", hash = "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc"}, + {file = "multidict-6.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86"}, + {file = "multidict-6.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693"}, + {file = "multidict-6.0.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9"}, + {file = "multidict-6.0.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d"}, + {file = "multidict-6.0.2-cp38-cp38-win32.whl", hash = "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57"}, + {file = "multidict-6.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e"}, + {file = "multidict-6.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9"}, + {file = "multidict-6.0.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360"}, + {file = "multidict-6.0.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937"}, + {file = "multidict-6.0.2-cp39-cp39-win32.whl", hash = "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a"}, + {file = "multidict-6.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae"}, + {file = "multidict-6.0.2.tar.gz", hash = "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013"}, +] +munch = [ + {file = "munch-2.5.0-py2.py3-none-any.whl", hash = "sha256:6f44af89a2ce4ed04ff8de41f70b226b984db10a91dcc7b9ac2efc1c77022fdd"}, + {file = "munch-2.5.0.tar.gz", hash = "sha256:2d735f6f24d4dba3417fa448cae40c6e896ec1fdab6cdb5e6510999758a4dbd2"}, +] +mypy = [ + {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"}, + {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"}, + {file = "mypy-0.961-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:bdd5ca340beffb8c44cb9dc26697628d1b88c6bddf5c2f6eb308c46f269bb6f3"}, + {file = "mypy-0.961-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3e09f1f983a71d0672bbc97ae33ee3709d10c779beb613febc36805a6e28bb4e"}, + {file = "mypy-0.961-cp310-cp310-win_amd64.whl", hash = "sha256:e999229b9f3198c0c880d5e269f9f8129c8862451ce53a011326cad38b9ccd24"}, + {file = "mypy-0.961-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b24be97351084b11582fef18d79004b3e4db572219deee0212078f7cf6352723"}, + {file = "mypy-0.961-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f4a21d01fc0ba4e31d82f0fff195682e29f9401a8bdb7173891070eb260aeb3b"}, + {file = "mypy-0.961-cp36-cp36m-win_amd64.whl", hash = "sha256:439c726a3b3da7ca84a0199a8ab444cd8896d95012c4a6c4a0d808e3147abf5d"}, + {file = "mypy-0.961-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5a0b53747f713f490affdceef835d8f0cb7285187a6a44c33821b6d1f46ed813"}, + {file = "mypy-0.961-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0e9f70df36405c25cc530a86eeda1e0867863d9471fe76d1273c783df3d35c2e"}, + {file = "mypy-0.961-cp37-cp37m-win_amd64.whl", hash = "sha256:b88f784e9e35dcaa075519096dc947a388319cb86811b6af621e3523980f1c8a"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:d5aaf1edaa7692490f72bdb9fbd941fbf2e201713523bdb3f4038be0af8846c6"}, + {file = "mypy-0.961-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:9f5f5a74085d9a81a1f9c78081d60a0040c3efb3f28e5c9912b900adf59a16e6"}, + {file = "mypy-0.961-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f4b794db44168a4fc886e3450201365c9526a522c46ba089b55e1f11c163750d"}, + {file = "mypy-0.961-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:64759a273d590040a592e0f4186539858c948302c653c2eac840c7a3cd29e51b"}, + {file = "mypy-0.961-cp38-cp38-win_amd64.whl", hash = "sha256:63e85a03770ebf403291ec50097954cc5caf2a9205c888ce3a61bd3f82e17569"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5f1332964963d4832a94bebc10f13d3279be3ce8f6c64da563d6ee6e2eeda932"}, + {file = "mypy-0.961-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:006be38474216b833eca29ff6b73e143386f352e10e9c2fbe76aa8549e5554f5"}, + {file = "mypy-0.961-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9940e6916ed9371809b35b2154baf1f684acba935cd09928952310fbddaba648"}, + {file = "mypy-0.961-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a5ea0875a049de1b63b972456542f04643daf320d27dc592d7c3d9cd5d9bf950"}, + {file = "mypy-0.961-cp39-cp39-win_amd64.whl", hash = "sha256:1ece702f29270ec6af25db8cf6185c04c02311c6bb21a69f423d40e527b75c56"}, + {file = "mypy-0.961-py3-none-any.whl", hash = "sha256:03c6cc893e7563e7b2949b969e63f02c000b32502a1b4d1314cabe391aa87d66"}, + {file = "mypy-0.961.tar.gz", hash = "sha256:f730d56cb924d371c26b8eaddeea3cc07d78ff51c521c6d04899ac6904b75492"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +myst-parser = [ + {file = "myst-parser-0.18.1.tar.gz", hash = "sha256:79317f4bb2c13053dd6e64f9da1ba1da6cd9c40c8a430c447a7b146a594c246d"}, + {file = "myst_parser-0.18.1-py3-none-any.whl", hash = "sha256:61b275b85d9f58aa327f370913ae1bec26ebad372cc99f3ab85c8ec3ee8d9fb8"}, +] +natsort = [ + {file = "natsort-8.2.0-py3-none-any.whl", hash = "sha256:04fe18fdd2b9e5957f19f687eb117f102ef8dde6b574764e536e91194bed4f5f"}, + {file = "natsort-8.2.0.tar.gz", hash = "sha256:57f85b72c688b09e053cdac302dd5b5b53df5f73ae20b4874fcbffd8bf783d11"}, +] +nbclassic = [ + {file = "nbclassic-0.4.7-py3-none-any.whl", hash = "sha256:d71d18aa6605eaf59e9b99b34c96360af45847f2a30ee2fefbe2f7bed4bc3df2"}, + {file = "nbclassic-0.4.7.tar.gz", hash = "sha256:1e0470583b55089c427940ed31b8a866ffef7ccab101494e409efe5ac7ba9897"}, +] +nbclient = [ + {file = "nbclient-0.7.0-py3-none-any.whl", hash = "sha256:434c91385cf3e53084185334d675a0d33c615108b391e260915d1aa8e86661b8"}, + {file = "nbclient-0.7.0.tar.gz", hash = "sha256:a1d844efd6da9bc39d2209bf996dbd8e07bf0f36b796edfabaa8f8a9ab77c3aa"}, +] +nbconvert = [ + {file = "nbconvert-7.2.2-py3-none-any.whl", hash = "sha256:fc7a3787c927cbd45a52e088e934fbbaab39afe61767522168a724b7483992be"}, + {file = "nbconvert-7.2.2.tar.gz", hash = "sha256:24acfaa466d2c9b7eb524800e4a45afbed862c5d058cfb30fc7aa24d448c95eb"}, +] +nbformat = [ + {file = "nbformat-5.7.0-py3-none-any.whl", hash = "sha256:1b05ec2c552c2f1adc745f4eddce1eac8ca9ffd59bb9fd859e827eaa031319f9"}, + {file = "nbformat-5.7.0.tar.gz", hash = "sha256:1d4760c15c1a04269ef5caf375be8b98dd2f696e5eb9e603ec2bf091f9b0d3f3"}, +] +ncompress = [ + {file = "ncompress-1.0.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0349d7de11edd70a7efea9ce9dc67f0e47b5774832dd063f7ae68a9e3e36ea31"}, + {file = "ncompress-1.0.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:af0011bae90e44121f4e4edbff3dccdce7e4c5fc5e354db7eb48410d71f496df"}, + {file = "ncompress-1.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e6f5bf381412e9d3847b76e8b6bd1f84dfadcd3d9c25903c8592facb437909a0"}, + {file = "ncompress-1.0.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e0ebd71990ef7909b6627b5341a2fe1977dcce61dd3760a29e19e3f9e4c6a275"}, + {file = "ncompress-1.0.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8b9acc46cf36bb998ed215d6e76a94e2bd1e827b9a4cb5362982b7004b5a7620"}, + {file = "ncompress-1.0.0-cp310-cp310-win32.whl", hash = "sha256:2a104803fbe3ab0a96edb14927fa22c8142be838aabe7e938b4a52a4e82db56e"}, + {file = "ncompress-1.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a2ae8a9170fa1f45df7efa292eb8c437b18c225b63d4adca4f50f9da0e8e0c7"}, + {file = "ncompress-1.0.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:7608fbda43d04d9f476be2dbf4ef3c96e72d83b9557a48b07fbc9ff3ad29cdd2"}, + {file = "ncompress-1.0.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8322482e72ac2802d1dca1007ec06aa281a4d5cf1cf9f8c75bb51e917382b756"}, + {file = "ncompress-1.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3590e66313041721ae81e72ece06b7048c9293321bb30900358638673608e264"}, + {file = "ncompress-1.0.0-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:736dbae078107742cf6ac7ccc11ae9c5eab77ef2c02aab3ef64802877bb01cab"}, + {file = "ncompress-1.0.0-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:5336a8831a7e587829ce54e9e27d1fb2e04ddbc7d2d983693e35a3a03ac3ce79"}, + {file = "ncompress-1.0.0-cp36-cp36m-win32.whl", hash = "sha256:9cd040ad73a3b0e917e01cdfba507e10e0bb56849daaac3ac3d86382d4d7ad82"}, + {file = "ncompress-1.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:8eb4a55cbeaeb238a3b412952077be6b3f37b3416cd0211cc22776391ff2fef7"}, + {file = "ncompress-1.0.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:916671d62167191af58d6b4a17b1c09c647e349dcff1fc0b7d764aa64c3773ee"}, + {file = "ncompress-1.0.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15f10fbfa11345ff0af090e3e6ae13a1fe2b52a2bb39d4f2373c2d6aeac75e5d"}, + {file = "ncompress-1.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe0a671a2f7dc1ee0438d278ef30ab425a969536100c4352b5cb6bc0b6210818"}, + {file = "ncompress-1.0.0-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:9d89acf209858e7940223cf35324e1b2effec119bb009a41f039e2ea4db22177"}, + {file = "ncompress-1.0.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:66d991155a1655ccd98e8433c4a7e811d63eb649adb55f47d8f9528a30cc4b7a"}, + {file = "ncompress-1.0.0-cp37-cp37m-win32.whl", hash = "sha256:34c6496168fd4dbc13f1fc0c0fcbadded1957639956f8cbc6894c39999817e36"}, + {file = "ncompress-1.0.0-cp37-cp37m-win_amd64.whl", hash = "sha256:94b3f4e851f5b37e1d4cf2d8da911fa10783a59cba3d7f1f2ae5bd2842558077"}, + {file = "ncompress-1.0.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:aaa18a509d9fc173b4b47d53c834e43ced1eda63d2aa7d4613dc59b2f802a31a"}, + {file = "ncompress-1.0.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6540556d47670a8fb93878a44d0206bbdc87f32e4c5b57d6fe63691efafbb982"}, + {file = "ncompress-1.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9da7c81313aed4b6c6e8020442ed8d03d04bff72947f9380ea1ce2c63ffb8ad1"}, + {file = "ncompress-1.0.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d45ec59a8a3ce00613df0c81e5567854574dbbbf01ecd1a5a0929cd8fb04844d"}, + {file = "ncompress-1.0.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:393cc3c126b9451fb32fe2bc07773264c90e73afbd37da0df472ac23bfd1a2d5"}, + {file = "ncompress-1.0.0-cp38-cp38-win32.whl", hash = "sha256:78674f246878938387b6f82b10d1aa2192e02544d214541943d12ef1a45e66c6"}, + {file = "ncompress-1.0.0-cp38-cp38-win_amd64.whl", hash = "sha256:da216a53db7cd4c0247376f87367dd71df457443567e55310f6d3d23a9aff2f2"}, + {file = "ncompress-1.0.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:34754041d9bac2d6908ae0d07ba541e4d6d606cca222ddd53f3a57e15f386b0a"}, + {file = "ncompress-1.0.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab9fc62baaa55faf8ed8ac67f2c64a7295fec91d7c1f306ac46aa894ca4edf91"}, + {file = "ncompress-1.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:070044eab19586a45d1855c3e50e000ce86d6075b122a5ec8cffd480713dffac"}, + {file = "ncompress-1.0.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f9ba6ab2aadd6fd90365fdad5219e4dc7bc2459b94f1e900a733dddaf4e9b2e6"}, + {file = "ncompress-1.0.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b031e06b42037b181e3514261e1e85a9eae4af990c12b9348a9f22b8042201ff"}, + {file = "ncompress-1.0.0-cp39-cp39-win32.whl", hash = "sha256:13fa26ec8000d786a8079bb265508b5df4b445a4f460481a13549b4bd3c83824"}, + {file = "ncompress-1.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:d11df815d280985dfa660974df11dbe051a1a18dca2f91f9d30fbd6548237b8f"}, + {file = "ncompress-1.0.0.tar.gz", hash = "sha256:e7bbf10cca1376f4f17ae2c447e33a9d4067525abb0c71d488c9a5ced50552f1"}, +] +nest-asyncio = [ + {file = "nest_asyncio-1.5.6-py3-none-any.whl", hash = "sha256:b9a953fb40dceaa587d109609098db21900182b16440652454a146cffb06e8b8"}, + {file = "nest_asyncio-1.5.6.tar.gz", hash = "sha256:d267cc1ff794403f7df692964d1d2a3fa9418ffea2a3f6859a439ff482fef290"}, +] +networkx = [ + {file = "networkx-2.8.8-py3-none-any.whl", hash = "sha256:e435dfa75b1d7195c7b8378c3859f0445cd88c6b0375c181ed66823a9ceb7524"}, + {file = "networkx-2.8.8.tar.gz", hash = "sha256:230d388117af870fce5647a3c52401fcf753e94720e6ea6b4197a5355648885e"}, +] +nodeenv = [ + {file = "nodeenv-1.7.0-py2.py3-none-any.whl", hash = "sha256:27083a7b96a25f2f5e1d8cb4b6317ee8aeda3bdd121394e5ac54e498028a042e"}, + {file = "nodeenv-1.7.0.tar.gz", hash = "sha256:e0e7f7dfb85fc5394c6fe1e8fa98131a2473e04311a45afb6508f7cf1836fa2b"}, +] +nose = [ + {file = "nose-1.3.7-py2-none-any.whl", hash = "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a"}, + {file = "nose-1.3.7-py3-none-any.whl", hash = "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac"}, + {file = "nose-1.3.7.tar.gz", hash = "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98"}, +] +notebook = [ + {file = "notebook-6.4.12-py3-none-any.whl", hash = "sha256:8c07a3bb7640e371f8a609bdbb2366a1976c6a2589da8ef917f761a61e3ad8b1"}, + {file = "notebook-6.4.12.tar.gz", hash = "sha256:6268c9ec9048cff7a45405c990c29ac9ca40b0bc3ec29263d218c5e01f2b4e86"}, +] +notebook-shim = [ + {file = "notebook_shim-0.2.0-py3-none-any.whl", hash = "sha256:481711abddfb2e5305b83cf0efe18475824eb47d1ba9f87f66a8c574b8b5c9e4"}, + {file = "notebook_shim-0.2.0.tar.gz", hash = "sha256:fdb81febb05932c6d19e44e10382ce05469cac5e1b6e99b49be6159ddb5e4804"}, +] +numpy = [ + {file = "numpy-1.23.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:95d79ada05005f6f4f337d3bb9de8a7774f259341c70bc88047a1f7b96a4bcb2"}, + {file = "numpy-1.23.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:926db372bc4ac1edf81cfb6c59e2a881606b409ddc0d0920b988174b2e2a767f"}, + {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c237129f0e732885c9a6076a537e974160482eab8f10db6292e92154d4c67d71"}, + {file = "numpy-1.23.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8365b942f9c1a7d0f0dc974747d99dd0a0cdfc5949a33119caf05cb314682d3"}, + {file = "numpy-1.23.4-cp310-cp310-win32.whl", hash = "sha256:2341f4ab6dba0834b685cce16dad5f9b6606ea8a00e6da154f5dbded70fdc4dd"}, + {file = "numpy-1.23.4-cp310-cp310-win_amd64.whl", hash = "sha256:d331afac87c92373826af83d2b2b435f57b17a5c74e6268b79355b970626e329"}, + {file = "numpy-1.23.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:488a66cb667359534bc70028d653ba1cf307bae88eab5929cd707c761ff037db"}, + {file = "numpy-1.23.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce03305dd694c4873b9429274fd41fc7eb4e0e4dea07e0af97a933b079a5814f"}, + {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8981d9b5619569899666170c7c9748920f4a5005bf79c72c07d08c8a035757b0"}, + {file = "numpy-1.23.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a70a7d3ce4c0e9284e92285cba91a4a3f5214d87ee0e95928f3614a256a1488"}, + {file = "numpy-1.23.4-cp311-cp311-win32.whl", hash = "sha256:5e13030f8793e9ee42f9c7d5777465a560eb78fa7e11b1c053427f2ccab90c79"}, + {file = "numpy-1.23.4-cp311-cp311-win_amd64.whl", hash = "sha256:7607b598217745cc40f751da38ffd03512d33ec06f3523fb0b5f82e09f6f676d"}, + {file = "numpy-1.23.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7ab46e4e7ec63c8a5e6dbf5c1b9e1c92ba23a7ebecc86c336cb7bf3bd2fb10e5"}, + {file = "numpy-1.23.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a8aae2fb3180940011b4862b2dd3756616841c53db9734b27bb93813cd79fce6"}, + {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8c053d7557a8f022ec823196d242464b6955a7e7e5015b719e76003f63f82d0f"}, + {file = "numpy-1.23.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0882323e0ca4245eb0a3d0a74f88ce581cc33aedcfa396e415e5bba7bf05f68"}, + {file = "numpy-1.23.4-cp38-cp38-win32.whl", hash = "sha256:dada341ebb79619fe00a291185bba370c9803b1e1d7051610e01ed809ef3a4ba"}, + {file = "numpy-1.23.4-cp38-cp38-win_amd64.whl", hash = "sha256:0fe563fc8ed9dc4474cbf70742673fc4391d70f4363f917599a7fa99f042d5a8"}, + {file = "numpy-1.23.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c67b833dbccefe97cdd3f52798d430b9d3430396af7cdb2a0c32954c3ef73894"}, + {file = "numpy-1.23.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f76025acc8e2114bb664294a07ede0727aa75d63a06d2fae96bf29a81747e4a7"}, + {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12ac457b63ec8ded85d85c1e17d85efd3c2b0967ca39560b307a35a6703a4735"}, + {file = "numpy-1.23.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95de7dc7dc47a312f6feddd3da2500826defdccbc41608d0031276a24181a2c0"}, + {file = "numpy-1.23.4-cp39-cp39-win32.whl", hash = "sha256:f2f390aa4da44454db40a1f0201401f9036e8d578a25f01a6e237cea238337ef"}, + {file = "numpy-1.23.4-cp39-cp39-win_amd64.whl", hash = "sha256:f260da502d7441a45695199b4e7fd8ca87db659ba1c78f2bbf31f934fe76ae0e"}, + {file = "numpy-1.23.4-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:61be02e3bf810b60ab74e81d6d0d36246dbfb644a462458bb53b595791251911"}, + {file = "numpy-1.23.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:296d17aed51161dbad3c67ed6d164e51fcd18dbcd5dd4f9d0a9c6055dce30810"}, + {file = "numpy-1.23.4-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:4d52914c88b4930dafb6c48ba5115a96cbab40f45740239d9f4159c4ba779962"}, + {file = "numpy-1.23.4.tar.gz", hash = "sha256:ed2cc92af0efad20198638c69bb0fc2870a58dabfba6eb722c933b48556c686c"}, +] +nvidia-ml-py3 = [ + {file = "nvidia-ml-py3-7.352.0.tar.gz", hash = "sha256:390f02919ee9d73fe63a98c73101061a6b37fa694a793abf56673320f1f51277"}, +] +oauthlib = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] +omegaconf = [ + {file = "omegaconf-2.3.0-py3-none-any.whl", hash = "sha256:7b4df175cdb08ba400f45cae3bdcae7ba8365db4d165fc65fd04b050ab63b46b"}, + {file = "omegaconf-2.3.0.tar.gz", hash = "sha256:d5d4b6d29955cc50ad50c46dc269bcd92c6e00f5f90d23ab5fee7bfca4ba4cc7"}, +] +onnx = [ + {file = "onnx-1.12.0-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:bdbd2578424c70836f4d0f9dda16c21868ddb07cc8192f9e8a176908b43d694b"}, + {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213e73610173f6b2e99f99a4b0636f80b379c417312079d603806e48ada4ca8b"}, + {file = "onnx-1.12.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9fd2f4e23078df197bb76a59b9cd8f5a43a6ad2edc035edb3ecfb9042093e05a"}, + {file = "onnx-1.12.0-cp310-cp310-win32.whl", hash = "sha256:23781594bb8b7ee985de1005b3c601648d5b0568a81e01365c48f91d1f5648e4"}, + {file = "onnx-1.12.0-cp310-cp310-win_amd64.whl", hash = "sha256:81a3555fd67be2518bf86096299b48fb9154652596219890abfe90bd43a9ec13"}, + {file = "onnx-1.12.0-cp37-cp37m-macosx_10_12_x86_64.whl", hash = "sha256:5578b93dc6c918cec4dee7fb7d9dd3b09d338301ee64ca8b4f28bc217ed42dca"}, + {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c11162ffc487167da140f1112f49c4f82d815824f06e58bc3095407699f05863"}, + {file = "onnx-1.12.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:341c7016e23273e9ffa9b6e301eee95b8c37d0f04df7cedbdb169d2c39524c96"}, + {file = "onnx-1.12.0-cp37-cp37m-win32.whl", hash = "sha256:3c6e6bcffc3f5c1e148df3837dc667fa4c51999788c1b76b0b8fbba607e02da8"}, + {file = "onnx-1.12.0-cp37-cp37m-win_amd64.whl", hash = "sha256:8a7aa61aea339bd28f310f4af4f52ce6c4b876386228760b16308efd58f95059"}, + {file = "onnx-1.12.0-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:56ceb7e094c43882b723cfaa107d85ad673cfdf91faeb28d7dcadacca4f43a07"}, + {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3629e8258db15d4e2c9b7f1be91a3186719dd94661c218c6f5fde3cc7de3d4d"}, + {file = "onnx-1.12.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2d9a7db54e75529160337232282a4816cc50667dc7dc34be178fd6f6b79d4705"}, + {file = "onnx-1.12.0-cp38-cp38-win32.whl", hash = "sha256:fea5156a03398fe0e23248042d8651c1eaac5f6637d4dd683b4c1f1320b9f7b4"}, + {file = "onnx-1.12.0-cp38-cp38-win_amd64.whl", hash = "sha256:f66d2996e65f490a57b3ae952e4e9189b53cc9fe3f75e601d50d4db2dc1b1cd9"}, + {file = "onnx-1.12.0-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c39a7a0352c856f1df30dccf527eb6cb4909052e5eaf6fa2772a637324c526aa"}, + {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fab13feb4d94342aae6d357d480f2e47d41b9f4e584367542b21ca6defda9e0a"}, + {file = "onnx-1.12.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7a9b3ea02c30efc1d2662337e280266aca491a8e86be0d8a657f874b7cccd1e"}, + {file = "onnx-1.12.0-cp39-cp39-win32.whl", hash = "sha256:f8800f28c746ab06e51ef8449fd1215621f4ddba91be3ffc264658937d38a2af"}, + {file = "onnx-1.12.0-cp39-cp39-win_amd64.whl", hash = "sha256:af90427ca04c6b7b8107c2021e1273227a3ef1a7a01f3073039cae7855a59833"}, + {file = "onnx-1.12.0.tar.gz", hash = "sha256:13b3e77d27523b9dbf4f30dfc9c959455859d5e34e921c44f712d69b8369eff9"}, +] +onnx2torch = [ + {file = "onnx2torch-1.5.4-py3-none-any.whl", hash = "sha256:fd1a0fe05072bfb9f3d86d9330299b130b41f11bd4ae634db17078974e711725"}, + {file = "onnx2torch-1.5.4.tar.gz", hash = "sha256:df837b557a63540223d85fde4a1d679fde0ca8d8bb89d5379c030b01eddc9c24"}, +] +onnxoptimizer = [ + {file = "onnxoptimizer-0.3.1-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e73a5e2e3ca4db9bff54f7131768749c861677b97ee811a136fcf1a52783cf6e"}, + {file = "onnxoptimizer-0.3.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8bf2bfe0dc43f0776867688e1759122dec049ff4f45f7221931b687fe7e139e"}, + {file = "onnxoptimizer-0.3.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a9a815bba418abfb23f319838370cfd9450305a2da7d970a2261046889a70730"}, + {file = "onnxoptimizer-0.3.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:60f1d3600f03466a451c05e3d12ce97565bae016e46f70396ba22208cfeae6f6"}, + {file = "onnxoptimizer-0.3.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:056a765593d197b2b643bfb35520a66eacebfc682583d9ac0389a56c2a259e6f"}, + {file = "onnxoptimizer-0.3.1-cp38-cp38-win_amd64.whl", hash = "sha256:15da6a036df388c3f08c3fc638b4d313ee6a1b96aaaa1c602fd1b424dd7bbc23"}, + {file = "onnxoptimizer-0.3.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5547203ee3392e3dabcea68a3a4d316ee0269ad3cf8a3504d1f68d467f60a06a"}, + {file = "onnxoptimizer-0.3.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c773b03313e253a60c7d8fe56ea5bba38fb3442ab84a825468a35a739e8fb20b"}, + {file = "onnxoptimizer-0.3.1-cp39-cp39-win_amd64.whl", hash = "sha256:aac4d5c4c5f4c471346dfb28356355b0c54c074a1f2fe1fe122b197f68c08a92"}, + {file = "onnxoptimizer-0.3.1.tar.gz", hash = "sha256:0aa2e873a49f3762822e4400e1e8886236156f9d1dbf20319e2c18f7ebfb6a1d"}, +] +onnxruntime-gpu = [ + {file = "onnxruntime_gpu-1.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42b0393c5122ed90fa2eb76192a486261d86e9526ccb78b2a98923c22791d2d1"}, + {file = "onnxruntime_gpu-1.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:ecfe97335027e569d4f46725ba89316041e562b8c499690e25e11cfee4601cd1"}, + {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2be6f7f5a1ce0bc8471ce42e10eab92cfb19d0748b857edcb5320b5e98311b7"}, + {file = "onnxruntime_gpu-1.12.1-cp37-cp37m-win_amd64.whl", hash = "sha256:d73204323aefebe4eecab9fcf76e22b1a00394e3d838c2962a28a27301186b73"}, + {file = "onnxruntime_gpu-1.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b37872527d03d3df10756408ca44014bd6ac354a044ab1c4286cd42dc138e518"}, + {file = "onnxruntime_gpu-1.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:296bd9733986cb7517d15bef5535c555d3f863963a71e6575e92d2a854aee61d"}, + {file = "onnxruntime_gpu-1.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e46d0724ce54c5908c5760037b78de741fbd48962b370c29ebc20e608b30eda"}, + {file = "onnxruntime_gpu-1.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:fd919373be35b9ba54210688265df38ad5e19a530449385c40dab51da407345d"}, +] +opencv-python-headless = [] +osmium = [ + {file = "osmium-3.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7bf648744dadd3396040ed57332780272356fb4b87dbd17521ba9b8b91c6e665"}, + {file = "osmium-3.4.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a8ee676ed0a0dbd359197ffb42518889af700da29629ac13834c1149a4b83ce6"}, + {file = "osmium-3.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:080345e4be12474da9b1d4f45fbf8214ab316826029f821f8043f1b36e749cc3"}, + {file = "osmium-3.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:58b61b72911b7bb87f617ae12872d7c11d360f08fb50927eb769a91be3434d7c"}, + {file = "osmium-3.4.1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bfee94a25e419923bcb885bf624024a1610f0e1c26ceb1a7c65426420d549c0e"}, + {file = "osmium-3.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:2ac803d2d1b0097768a8bf0c174349d686d5dc2ec832ec84cadb20937a415d10"}, + {file = "osmium-3.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:daa478c951ade8b49348096f5e330bbe39cd2b5057127666a9102094cf7f31fb"}, + {file = "osmium-3.4.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5453ed8c8c69ab3ed2c17ae1fd3567b9b54036916f0ffe77235fe363e7029b01"}, + {file = "osmium-3.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:f9776510dbaac32ded5d3bbe31bacdeeefefc79ce8ea54c743e9eb2ce4ace007"}, + {file = "osmium-3.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d49e56225f4e31867e2bbdef000ae3fb9b18ca6697e44c3315d64b46d53fc77e"}, + {file = "osmium-3.4.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:76e8461ae68c5307f44eb407424b7dcfc7d0d8bb5b52283088d8f549c0d715ba"}, + {file = "osmium-3.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:3f4710b8350d93edc194a466ab4cb42efcc58961be46901cced359f74bed27e5"}, + {file = "osmium-3.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a777eaa408a49f8c2f0f0358a09307c9fa69d4809ae3632f3e40c61bdbf11d17"}, + {file = "osmium-3.4.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:50a9c94c4856d81c11e68ff327b81d759311974d7fb67618cec3b15e32b7f4c1"}, + {file = "osmium-3.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:e45b7c54ac756e9cb40e2ba68691df635804eb6aa2023088af66936a9c8e3782"}, + {file = "osmium-3.4.1.tar.gz", hash = "sha256:575dad72ab169cf585b9aeefb4f5f99ac250bf7da1986992afcbf169dc70c381"}, +] +osmnx = [ + {file = "osmnx-1.2.2-py2.py3-none-any.whl", hash = "sha256:94f2a3929e857d8c0da39ae552c6da3b1a3f4bcfea6de108696bda5ee3a7689d"}, + {file = "osmnx-1.2.2.tar.gz", hash = "sha256:30924452ca02758ece3301f9fcfb1b80edf31e2be7abe7fa7e0fefddb5050408"}, +] +packaging = [ + {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, + {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, +] +pandas = [ + {file = "pandas-1.5.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0a78e05ec09731c5b3bd7a9805927ea631fe6f6cb06f0e7c63191a9a778d52b4"}, + {file = "pandas-1.5.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5b0c970e2215572197b42f1cff58a908d734503ea54b326412c70d4692256391"}, + {file = "pandas-1.5.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:f340331a3f411910adfb4bbe46c2ed5872d9e473a783d7f14ecf49bc0869c594"}, + {file = "pandas-1.5.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c709f4700573deb2036d240d140934df7e852520f4a584b2a8d5443b71f54d"}, + {file = "pandas-1.5.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32e3d9f65606b3f6e76555bfd1d0b68d94aff0929d82010b791b6254bf5a4b96"}, + {file = "pandas-1.5.1-cp310-cp310-win_amd64.whl", hash = "sha256:a52419d9ba5906db516109660b114faf791136c94c1a636ed6b29cbfff9187ee"}, + {file = "pandas-1.5.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:66a1ad667b56e679e06ba73bb88c7309b3f48a4c279bd3afea29f65a766e9036"}, + {file = "pandas-1.5.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:36aa1f8f680d7584e9b572c3203b20d22d697c31b71189322f16811d4ecfecd3"}, + {file = "pandas-1.5.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:bcf1a82b770b8f8c1e495b19a20d8296f875a796c4fe6e91da5ef107f18c5ecb"}, + {file = "pandas-1.5.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c25e5c16ee5c0feb6cf9d982b869eec94a22ddfda9aa2fbed00842cbb697624"}, + {file = "pandas-1.5.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:932d2d7d3cab44cfa275601c982f30c2d874722ef6396bb539e41e4dc4618ed4"}, + {file = "pandas-1.5.1-cp311-cp311-win_amd64.whl", hash = "sha256:eb7e8cf2cf11a2580088009b43de84cabbf6f5dae94ceb489f28dba01a17cb77"}, + {file = "pandas-1.5.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:cb2a9cf1150302d69bb99861c5cddc9c25aceacb0a4ef5299785d0f5389a3209"}, + {file = "pandas-1.5.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:81f0674fa50b38b6793cd84fae5d67f58f74c2d974d2cb4e476d26eee33343d0"}, + {file = "pandas-1.5.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:17da7035d9e6f9ea9cdc3a513161f8739b8f8489d31dc932bc5a29a27243f93d"}, + {file = "pandas-1.5.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:669c8605dba6c798c1863157aefde959c1796671ffb342b80fcb80a4c0bc4c26"}, + {file = "pandas-1.5.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:683779e5728ac9138406c59a11e09cd98c7d2c12f0a5fc2b9c5eecdbb4a00075"}, + {file = "pandas-1.5.1-cp38-cp38-win32.whl", hash = "sha256:ddf46b940ef815af4e542697eaf071f0531449407a7607dd731bf23d156e20a7"}, + {file = "pandas-1.5.1-cp38-cp38-win_amd64.whl", hash = "sha256:db45b94885000981522fb92349e6b76f5aee0924cc5315881239c7859883117d"}, + {file = "pandas-1.5.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:927e59c694e039c75d7023465d311277a1fc29ed7236b5746e9dddf180393113"}, + {file = "pandas-1.5.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:e675f8fe9aa6c418dc8d3aac0087b5294c1a4527f1eacf9fe5ea671685285454"}, + {file = "pandas-1.5.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:04e51b01d5192499390c0015630975f57836cc95c7411415b499b599b05c0c96"}, + {file = "pandas-1.5.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5cee0c74e93ed4f9d39007e439debcaadc519d7ea5c0afc3d590a3a7b2edf060"}, + {file = "pandas-1.5.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b156a971bc451c68c9e1f97567c94fd44155f073e3bceb1b0d195fd98ed12048"}, + {file = "pandas-1.5.1-cp39-cp39-win32.whl", hash = "sha256:05c527c64ee02a47a24031c880ee0ded05af0623163494173204c5b72ddce658"}, + {file = "pandas-1.5.1-cp39-cp39-win_amd64.whl", hash = "sha256:6bb391659a747cf4f181a227c3e64b6d197100d53da98dcd766cc158bdd9ec68"}, + {file = "pandas-1.5.1.tar.gz", hash = "sha256:249cec5f2a5b22096440bd85c33106b6102e0672204abd2d5c014106459804ee"}, +] +pandocfilters = [ + {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, + {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, +] +parameterized = [ + {file = "parameterized-0.8.1-py2.py3-none-any.whl", hash = "sha256:9cbb0b69a03e8695d68b3399a8a5825200976536fe1cb79db60ed6a4c8c9efe9"}, + {file = "parameterized-0.8.1.tar.gz", hash = "sha256:41bbff37d6186430f77f900d777e5bb6a24928a1c46fb1de692f8b52b8833b5c"}, +] +paramiko = [ + {file = "paramiko-2.11.0-py2.py3-none-any.whl", hash = "sha256:655f25dc8baf763277b933dfcea101d636581df8d6b9774d1fb653426b72c270"}, + {file = "paramiko-2.11.0.tar.gz", hash = "sha256:003e6bee7c034c21fbb051bf83dc0a9ee4106204dd3c53054c71452cc4ec3938"}, +] +parso = [ + {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, + {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, +] +pexpect = [ + {file = "pexpect-4.8.0-py2.py3-none-any.whl", hash = "sha256:0b48a55dcb3c05f3329815901ea4fc1537514d6ba867a152b581d69ae3710937"}, + {file = "pexpect-4.8.0.tar.gz", hash = "sha256:fc65a43959d153d0114afe13997d439c22823a27cefceb5ff35c2178c6784c0c"}, +] +pickleshare = [ + {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, + {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, +] +pillow = [ + {file = "Pillow-9.2.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:a9c9bc489f8ab30906d7a85afac4b4944a572a7432e00698a7239f44a44e6efb"}, + {file = "Pillow-9.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:510cef4a3f401c246cfd8227b300828715dd055463cdca6176c2e4036df8bd4f"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7888310f6214f19ab2b6df90f3f06afa3df7ef7355fc025e78a3044737fab1f5"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:831e648102c82f152e14c1a0938689dbb22480c548c8d4b8b248b3e50967b88c"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1cc1d2451e8a3b4bfdb9caf745b58e6c7a77d2e469159b0d527a4554d73694d1"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:136659638f61a251e8ed3b331fc6ccd124590eeff539de57c5f80ef3a9594e58"}, + {file = "Pillow-9.2.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:6e8c66f70fb539301e064f6478d7453e820d8a2c631da948a23384865cd95544"}, + {file = "Pillow-9.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:37ff6b522a26d0538b753f0b4e8e164fdada12db6c6f00f62145d732d8a3152e"}, + {file = "Pillow-9.2.0-cp310-cp310-win32.whl", hash = "sha256:c79698d4cd9318d9481d89a77e2d3fcaeff5486be641e60a4b49f3d2ecca4e28"}, + {file = "Pillow-9.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:254164c57bab4b459f14c64e93df11eff5ded575192c294a0c49270f22c5d93d"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:adabc0bce035467fb537ef3e5e74f2847c8af217ee0be0455d4fec8adc0462fc"}, + {file = "Pillow-9.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:336b9036127eab855beec9662ac3ea13a4544a523ae273cbf108b228ecac8437"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50dff9cc21826d2977ef2d2a205504034e3a4563ca6f5db739b0d1026658e004"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cb6259196a589123d755380b65127ddc60f4c64b21fc3bb46ce3a6ea663659b0"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b0554af24df2bf96618dac71ddada02420f946be943b181108cac55a7a2dcd4"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:15928f824870535c85dbf949c09d6ae7d3d6ac2d6efec80f3227f73eefba741c"}, + {file = "Pillow-9.2.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:bdd0de2d64688ecae88dd8935012c4a72681e5df632af903a1dca8c5e7aa871a"}, + {file = "Pillow-9.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:d5b87da55a08acb586bad5c3aa3b86505f559b84f39035b233d5bf844b0834b1"}, + {file = "Pillow-9.2.0-cp311-cp311-win32.whl", hash = "sha256:b6d5e92df2b77665e07ddb2e4dbd6d644b78e4c0d2e9272a852627cdba0d75cf"}, + {file = "Pillow-9.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6bf088c1ce160f50ea40764f825ec9b72ed9da25346216b91361eef8ad1b8f8c"}, + {file = "Pillow-9.2.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:2c58b24e3a63efd22554c676d81b0e57f80e0a7d3a5874a7e14ce90ec40d3069"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eef7592281f7c174d3d6cbfbb7ee5984a671fcd77e3fc78e973d492e9bf0eb3f"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dcd7b9c7139dc8258d164b55696ecd16c04607f1cc33ba7af86613881ffe4ac8"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a138441e95562b3c078746a22f8fca8ff1c22c014f856278bdbdd89ca36cff1b"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_aarch64.whl", hash = "sha256:93689632949aff41199090eff5474f3990b6823404e45d66a5d44304e9cdc467"}, + {file = "Pillow-9.2.0-cp37-cp37m-manylinux_2_28_x86_64.whl", hash = "sha256:f3fac744f9b540148fa7715a435d2283b71f68bfb6d4aae24482a890aed18b59"}, + {file = "Pillow-9.2.0-cp37-cp37m-win32.whl", hash = "sha256:fa768eff5f9f958270b081bb33581b4b569faabf8774726b283edb06617101dc"}, + {file = "Pillow-9.2.0-cp37-cp37m-win_amd64.whl", hash = "sha256:69bd1a15d7ba3694631e00df8de65a8cb031911ca11f44929c97fe05eb9b6c1d"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:030e3460861488e249731c3e7ab59b07c7853838ff3b8e16aac9561bb345da14"}, + {file = "Pillow-9.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:74a04183e6e64930b667d321524e3c5361094bb4af9083db5c301db64cd341f3"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2d33a11f601213dcd5718109c09a52c2a1c893e7461f0be2d6febc2879ec2402"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fd6f5e3c0e4697fa7eb45b6e93996299f3feee73a3175fa451f49a74d092b9f"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a647c0d4478b995c5e54615a2e5360ccedd2f85e70ab57fbe817ca613d5e63b8"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:4134d3f1ba5f15027ff5c04296f13328fecd46921424084516bdb1b2548e66ff"}, + {file = "Pillow-9.2.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:bc431b065722a5ad1dfb4df354fb9333b7a582a5ee39a90e6ffff688d72f27a1"}, + {file = "Pillow-9.2.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1536ad017a9f789430fb6b8be8bf99d2f214c76502becc196c6f2d9a75b01b76"}, + {file = "Pillow-9.2.0-cp38-cp38-win32.whl", hash = "sha256:2ad0d4df0f5ef2247e27fc790d5c9b5a0af8ade9ba340db4a73bb1a4a3e5fb4f"}, + {file = "Pillow-9.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:ec52c351b35ca269cb1f8069d610fc45c5bd38c3e91f9ab4cbbf0aebc136d9c8"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ed2c4ef2451de908c90436d6e8092e13a43992f1860275b4d8082667fbb2ffc"}, + {file = "Pillow-9.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ad2f835e0ad81d1689f1b7e3fbac7b01bb8777d5a985c8962bedee0cc6d43da"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea98f633d45f7e815db648fd7ff0f19e328302ac36427343e4432c84432e7ff4"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7761afe0126d046974a01e030ae7529ed0ca6a196de3ec6937c11df0df1bc91c"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a54614049a18a2d6fe156e68e188da02a046a4a93cf24f373bffd977e943421"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:5aed7dde98403cd91d86a1115c78d8145c83078e864c1de1064f52e6feb61b20"}, + {file = "Pillow-9.2.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:13b725463f32df1bfeacbf3dd197fb358ae8ebcd8c5548faa75126ea425ccb60"}, + {file = "Pillow-9.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:808add66ea764ed97d44dda1ac4f2cfec4c1867d9efb16a33d158be79f32b8a4"}, + {file = "Pillow-9.2.0-cp39-cp39-win32.whl", hash = "sha256:337a74fd2f291c607d220c793a8135273c4c2ab001b03e601c36766005f36885"}, + {file = "Pillow-9.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:fac2d65901fb0fdf20363fbd345c01958a742f2dc62a8dd4495af66e3ff502a4"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ad2277b185ebce47a63f4dc6302e30f05762b688f8dc3de55dbae4651872cdf3"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c7b502bc34f6e32ba022b4a209638f9e097d7a9098104ae420eb8186217ebbb"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1f14f5f691f55e1b47f824ca4fdcb4b19b4323fe43cc7bb105988cad7496be"}, + {file = "Pillow-9.2.0-pp37-pypy37_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:dfe4c1fedfde4e2fbc009d5ad420647f7730d719786388b7de0999bf32c0d9fd"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:f07f1f00e22b231dd3d9b9208692042e29792d6bd4f6639415d2f23158a80013"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1802f34298f5ba11d55e5bb09c31997dc0c6aed919658dfdf0198a2fe75d5490"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17d4cafe22f050b46d983b71c707162d63d796a1235cdf8b9d7a112e97b15bac"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:96b5e6874431df16aee0c1ba237574cb6dff1dcb173798faa6a9d8b399a05d0e"}, + {file = "Pillow-9.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:0030fdbd926fb85844b8b92e2f9449ba89607231d3dd597a21ae72dc7fe26927"}, + {file = "Pillow-9.2.0.tar.gz", hash = "sha256:75e636fd3e0fb872693f23ccb8a5ff2cd578801251f3a4f6854c6a5d437d3c04"}, +] +pillow-avif-plugin = [ + {file = "pillow-avif-plugin-1.2.2.tar.gz", hash = "sha256:38033ef060b96b2615f5c9e8fa87d4928a4254f0f43081abd89c9017f82c589a"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-macosx_10_10_x86_64.whl", hash = "sha256:fc542b03ea87f0e832904fe1ff61cb586bafcd5f80e352ae7ca5cac008e9d651"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-macosx_11_0_arm64.whl", hash = "sha256:c77ed103dc587894e7d707cd4ffc0f5b1c641d09b1fb84245475f02c1db039c9"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27m-win_amd64.whl", hash = "sha256:5c0b979acd9a58a5d5f662eb167d65c8e103af0198fcaa200661abf9733dbf9b"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6dade8e87909766a655a88df0a3c0078c9020315e2fefab8cf3496d9940fd0f8"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:54b1a27610cbf754e332b0f3e7be188cceedcfcdcb4cff92d272aff04082eb1d"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:887665e08bee4ded0512fe991bbbe7403c90aa9646be8d27295271dcdc10581e"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:feba13f19bd9381e079e72f088a66a6509caefb52304888751d637c23b1faa00"}, + {file = "pillow_avif_plugin-1.2.2-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7238447e395ccee68052c06c0fb824696f5cd1f3050f50466f1889d8356a28f7"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:703f4e15dcfc9335f69ebd09cecb576f3db0c3586af6bd43d0971a591183b0de"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9c4665a8d3aa5fb60ed2976eb52ae886f6a276c7ddf346c118b16dc6f1ceec7c"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:593006af5569df6f3aefba184a90a5112798859ec8c745bcb495ab8b84d194bc"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e2fa7245f7544f4ae03ab4efc035a398883b404488806094e661b3438d921b88"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:017e5e52cb4320414e8ce3e2089eae2cb87c22c73ff6012b17ae326fc5753b20"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2a57136d4866de5dc80cfb24d66655955fbdd87acf1d11d88c8dc2ab41023e46"}, + {file = "pillow_avif_plugin-1.2.2-cp310-cp310-win_amd64.whl", hash = "sha256:f339511d0fccb69e3a5e3af39f8fe6700b0a07279015006ea56f8f49e7fecff4"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:05e821ecd90bb0b8d2dc7610625372cc47de9cb893d09662528bad572f669d1c"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d33886a5f9796fe9a8a3bc25ccfdeba7db119adb50b7004f1928a14b07d0213a"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:75b7ed186c2f740dd26e556f6a966c59a170b70263e429a2c81920fe444da8a7"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a11aef6b79078b8dad25c928e5871c146ab94424472851d5bf539ba62abde9ac"}, + {file = "pillow_avif_plugin-1.2.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10696c536d68a14cefea3b98edb8d5a7ae29e8e07458f1d59c5d1cd780a8bf2a"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1a7291d6a5fb7336e72685a31d193e0b3a6bee9986c9ac4d8bd4b68dbe6d4f7f"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:14b9c5dbf237e7dc12f69819ea181a457b3bd4f59f8cd71d028d3635fd3bcab4"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:799cbfbeee831332d280c80df9ce16b5c3b1224c318264e97e89df8da32e870e"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8f64153b2bb8007d00a3ccb32bb374dda185bf1b4a145c2e971fc67b68d015c"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:1b7937a20042fdd4272cdb3f2942d0290a045e6016335eeb79685ee24e8aa652"}, + {file = "pillow_avif_plugin-1.2.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e5aa90fee46584d5958832a47e19fb7c18de059cacdac33494a55b9a064a56dc"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:1124f4a5e2312663cdb96879dd01a200475bd86626bf4905fca33c6d438e479b"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-macosx_11_0_arm64.whl", hash = "sha256:a1d2a17a6b133793ad733b33712cdc0bba13acc6fe83d259ef3c735e172550a4"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:eb2f34c093b93dbc05b28fc34715b411ee0a5f30788137b27eb89864375134c8"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bef5d0f2b198aca6a13ebd77006ecb25e08d2c09deb25dc89ec72d2460da8e67"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c8ec81be95e14de6e8052cceae593d4ff43ba22938186b44c68964715a85c1f9"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:39cbf7b21392778f1bd3988c97429ff7b86a9a972f033c9c3674544114e612ab"}, + {file = "pillow_avif_plugin-1.2.2-cp37-cp37m-win_amd64.whl", hash = "sha256:c9099647c81ec26b2f92dde414876f000f74588f15f5e60228dbf0494891d817"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:050599ad735e6a3d6c565bc563ced74872acd3e8bd3e7ac9f014dc2e82249611"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c3adf80a6ee5e895b75bcd3743f81b3ed96cd0c418326f499312b1bde87b41f1"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:251f1fc60caedd2c641c9f28c4f5a74610aa6c1d1b54e681ce63d508a8e30c53"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:3216e9f28514947fcdecd41782ccb5bb3a73360d758b1a2ff7fbf3831e0f5bf7"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a229d44f8446d6e2515e965d6d066cfd9308d50c6ada22ecb2f13a9b0c492e86"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:529eb2d9e3827acf874c6ddedc28729bfb709c115cc972c77464cc7bacdf2b38"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9d3ee3b5b66d58c33bc723074e2db7e1256d341ba90edfd2c9ea19c7964836da"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:a34a928ed23508a694b34a14252e178b82db577285d43a1eaf143a6a8a6ddd63"}, + {file = "pillow_avif_plugin-1.2.2-cp38-cp38-win_amd64.whl", hash = "sha256:d40bf89d78da035dea9d5167335f73e71b696509e14513d875ac2605b91ae269"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:ad8958a5ea5e3f6ae2a4ded374fb7b84b3af2176e295ca6c3d1d0b6e9866933e"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab19285161028059dfc6362b7aba6a02ff5371d22492d8a71eade7d7e5ca6f59"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8f70d2787bf9bed4588fedd9e1f149eb7c2a9990df3ee123eb18d86882ca0b8a"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:220fc8e8b9e1e3e98cd7f6dcc116cff8ca453b249dba62264ab0dc2a92fc4e1b"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0514b09d518a699525061160093da58eb3a04649daf98d8e9ee52d739de5be6e"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a6314e0144a2b08e39b6842fa5b981383d7932f6e8363b6bbb291a9efc23deb1"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:3bf72f8df4b08b63b183c6368e3bd0cd098bfacc494803383589655607bc1f76"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0d54543ed9fbb6c18b61c93a2296e2a9bfc85ef559cb6a50b92fe80cc4711db5"}, + {file = "pillow_avif_plugin-1.2.2-cp39-cp39-win_amd64.whl", hash = "sha256:86c62edb830a83a97c2a2f9a262968f12ce32adbf1069fd5fae47a0fae706e16"}, +] +pipenv = [ + {file = "pipenv-2022.10.12-py2.py3-none-any.whl", hash = "sha256:f43972a42411107ade86b6f17dd698dfcd843bd84eb7264163ebb363f6b0ede4"}, + {file = "pipenv-2022.10.12.tar.gz", hash = "sha256:a4d88f6667cbcd9ea432d626a8b373cd3101886b9fb964ea7e7f9650a83fc307"}, +] +pkginfo = [ + {file = "pkginfo-1.8.3-py2.py3-none-any.whl", hash = "sha256:848865108ec99d4901b2f7e84058b6e7660aae8ae10164e015a6dcf5b242a594"}, + {file = "pkginfo-1.8.3.tar.gz", hash = "sha256:a84da4318dd86f870a9447a8c98340aa06216bfc6f2b7bdc4b8766984ae1867c"}, +] +pkgutil-resolve-name = [ + {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, + {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +plotly = [ + {file = "plotly-5.10.0-py2.py3-none-any.whl", hash = "sha256:989b13825cc974390aa0169479485d9257d37848a47bc63957251f8e1a7046ba"}, + {file = "plotly-5.10.0.tar.gz", hash = "sha256:4d36d9859b7a153b273562deeed8c292587a472eb1fd57cd4158ec89d9defadb"}, +] +pluggy = [ + {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, + {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, +] +poetry = [ + {file = "poetry-1.2.2-py3-none-any.whl", hash = "sha256:93ea3c4a622485c2a7b7249f1e34e4ac84f8229ded76153b67506313201b154f"}, + {file = "poetry-1.2.2.tar.gz", hash = "sha256:6d9ed0b1b826a0a79190f2078d7d78483fa24bf2494f3b170e354eaa5e7b5ea1"}, +] +poetry-core = [ + {file = "poetry-core-1.3.2.tar.gz", hash = "sha256:0ab006a40cb38d6a38b97264f6835da2f08a96912f2728ce668e9ac6a34f686f"}, + {file = "poetry_core-1.3.2-py3-none-any.whl", hash = "sha256:ea0f5a90b339cde132b4e43cff78a1b440cd928db864bb67cfc97fdfcefe7218"}, +] +poetry-plugin-export = [ + {file = "poetry-plugin-export-1.1.2.tar.gz", hash = "sha256:5e92525dd63f38ce74a51ed68ea91d753523f21ce5f9ef8d3b793e2a4b2222ef"}, + {file = "poetry_plugin_export-1.1.2-py3-none-any.whl", hash = "sha256:946e3313b3d00c18fb9a50522e9d5e6a7e111beaba8d6ae33297662fc2070ac1"}, +] +polyline = [ + {file = "polyline-1.4.0-py2.py3-none-any.whl", hash = "sha256:6559a0d5d37f4d14255744b3c6a648d5ff480d3d5c5f30186effc72a4142fd6c"}, + {file = "polyline-1.4.0.tar.gz", hash = "sha256:7c7f89d09a09c7b6161bdbfb4fd304b186fc7a2060fa4f31cb3f61c646a5c074"}, +] +portalocker = [ + {file = "portalocker-2.6.0-py2.py3-none-any.whl", hash = "sha256:102ed1f2badd8dec9af3d732ef70e94b215b85ba45a8d7ff3c0003f19b442f4e"}, + {file = "portalocker-2.6.0.tar.gz", hash = "sha256:964f6830fb42a74b5d32bce99ed37d8308c1d7d44ddf18f3dd89f4680de97b39"}, +] +pprofile = [ + {file = "pprofile-2.1.0.tar.gz", hash = "sha256:b2bb56603dadf40c0bc0f61621f22c20e41638425f729945d9b7f8e4ae8cdd4a"}, +] +pre-commit = [ + {file = "pre_commit-2.20.0-py2.py3-none-any.whl", hash = "sha256:51a5ba7c480ae8072ecdb6933df22d2f812dc897d5fe848778116129a681aac7"}, + {file = "pre_commit-2.20.0.tar.gz", hash = "sha256:a978dac7bc9ec0bcee55c18a277d553b0f419d259dadb4b9418ff2d00eb43959"}, +] +pretrainedmodels = [ + {file = "pretrainedmodels-0.7.4.tar.gz", hash = "sha256:7e77ead4619a3e11ab3c41982c8ad5b86edffe37c87fd2a37ec3c2cc6470b98a"}, +] +prometheus-client = [ + {file = "prometheus_client-0.15.0-py3-none-any.whl", hash = "sha256:db7c05cbd13a0f79975592d112320f2605a325969b270a94b71dcabc47b931d2"}, + {file = "prometheus_client-0.15.0.tar.gz", hash = "sha256:be26aa452490cfcf6da953f9436e95a9f2b4d578ca80094b4458930e5f584ab1"}, +] +prompt-toolkit = [ + {file = "prompt_toolkit-3.0.31-py3-none-any.whl", hash = "sha256:9696f386133df0fc8ca5af4895afe5d78f5fcfe5258111c2a79a1c3e41ffa96d"}, + {file = "prompt_toolkit-3.0.31.tar.gz", hash = "sha256:9ada952c9d1787f52ff6d5f3484d0b4df8952787c087edf6a1f7c2cb1ea88148"}, +] +protobuf = [ + {file = "protobuf-3.20.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3cc797c9d15d7689ed507b165cd05913acb992d78b379f6014e013f9ecb20996"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:ff8d8fa42675249bb456f5db06c00de6c2f4c27a065955917b28c4f15978b9c3"}, + {file = "protobuf-3.20.1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cd68be2559e2a3b84f517fb029ee611546f7812b1fdd0aa2ecc9bc6ec0e4fdde"}, + {file = "protobuf-3.20.1-cp310-cp310-win32.whl", hash = "sha256:9016d01c91e8e625141d24ec1b20fed584703e527d28512aa8c8707f105a683c"}, + {file = "protobuf-3.20.1-cp310-cp310-win_amd64.whl", hash = "sha256:32ca378605b41fd180dfe4e14d3226386d8d1b002ab31c969c366549e66a2bb7"}, + {file = "protobuf-3.20.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9be73ad47579abc26c12024239d3540e6b765182a91dbc88e23658ab71767153"}, + {file = "protobuf-3.20.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:097c5d8a9808302fb0da7e20edf0b8d4703274d140fd25c5edabddcde43e081f"}, + {file = "protobuf-3.20.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e250a42f15bf9d5b09fe1b293bdba2801cd520a9f5ea2d7fb7536d4441811d20"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:cdee09140e1cd184ba9324ec1df410e7147242b94b5f8b0c64fc89e38a8ba531"}, + {file = "protobuf-3.20.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:af0ebadc74e281a517141daad9d0f2c5d93ab78e9d455113719a45a49da9db4e"}, + {file = "protobuf-3.20.1-cp37-cp37m-win32.whl", hash = "sha256:755f3aee41354ae395e104d62119cb223339a8f3276a0cd009ffabfcdd46bb0c"}, + {file = "protobuf-3.20.1-cp37-cp37m-win_amd64.whl", hash = "sha256:62f1b5c4cd6c5402b4e2d63804ba49a327e0c386c99b1675c8a0fefda23b2067"}, + {file = "protobuf-3.20.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:06059eb6953ff01e56a25cd02cca1a9649a75a7e65397b5b9b4e929ed71d10cf"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:cb29edb9eab15742d791e1025dd7b6a8f6fcb53802ad2f6e3adcb102051063ab"}, + {file = "protobuf-3.20.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:69ccfdf3657ba59569c64295b7d51325f91af586f8d5793b734260dfe2e94e2c"}, + {file = "protobuf-3.20.1-cp38-cp38-win32.whl", hash = "sha256:dd5789b2948ca702c17027c84c2accb552fc30f4622a98ab5c51fcfe8c50d3e7"}, + {file = "protobuf-3.20.1-cp38-cp38-win_amd64.whl", hash = "sha256:77053d28427a29987ca9caf7b72ccafee011257561259faba8dd308fda9a8739"}, + {file = "protobuf-3.20.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6f50601512a3d23625d8a85b1638d914a0970f17920ff39cec63aaef80a93fb7"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:284f86a6207c897542d7e956eb243a36bb8f9564c1742b253462386e96c6b78f"}, + {file = "protobuf-3.20.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7403941f6d0992d40161aa8bb23e12575637008a5a02283a930addc0508982f9"}, + {file = "protobuf-3.20.1-cp39-cp39-win32.whl", hash = "sha256:db977c4ca738dd9ce508557d4fce0f5aebd105e158c725beec86feb1f6bc20d8"}, + {file = "protobuf-3.20.1-cp39-cp39-win_amd64.whl", hash = "sha256:7e371f10abe57cee5021797126c93479f59fccc9693dafd6bd5633ab67808a91"}, + {file = "protobuf-3.20.1-py2.py3-none-any.whl", hash = "sha256:adfc6cf69c7f8c50fd24c793964eef18f0ac321315439d94945820612849c388"}, + {file = "protobuf-3.20.1.tar.gz", hash = "sha256:adc31566d027f45efe3f44eeb5b1f329da43891634d61c75a5944e9be6dd42c9"}, +] +psutil = [ + {file = "psutil-5.9.3-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:b4a247cd3feaae39bb6085fcebf35b3b8ecd9b022db796d89c8f05067ca28e71"}, + {file = "psutil-5.9.3-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5fa88e3d5d0b480602553d362c4b33a63e0c40bfea7312a7bf78799e01e0810b"}, + {file = "psutil-5.9.3-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:767ef4fa33acda16703725c0473a91e1832d296c37c63896c7153ba81698f1ab"}, + {file = "psutil-5.9.3-cp27-cp27m-win32.whl", hash = "sha256:9a4af6ed1094f867834f5f07acd1250605a0874169a5fcadbcec864aec2496a6"}, + {file = "psutil-5.9.3-cp27-cp27m-win_amd64.whl", hash = "sha256:fa5e32c7d9b60b2528108ade2929b115167fe98d59f89555574715054f50fa31"}, + {file = "psutil-5.9.3-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:fe79b4ad4836e3da6c4650cb85a663b3a51aef22e1a829c384e18fae87e5e727"}, + {file = "psutil-5.9.3-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:db8e62016add2235cc87fb7ea000ede9e4ca0aa1f221b40cef049d02d5d2593d"}, + {file = "psutil-5.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:941a6c2c591da455d760121b44097781bc970be40e0e43081b9139da485ad5b7"}, + {file = "psutil-5.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71b1206e7909792d16933a0d2c1c7f04ae196186c51ba8567abae1d041f06dcb"}, + {file = "psutil-5.9.3-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f57d63a2b5beaf797b87024d018772439f9d3103a395627b77d17a8d72009543"}, + {file = "psutil-5.9.3-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e7507f6c7b0262d3e7b0eeda15045bf5881f4ada70473b87bc7b7c93b992a7d7"}, + {file = "psutil-5.9.3-cp310-cp310-win32.whl", hash = "sha256:1b540599481c73408f6b392cdffef5b01e8ff7a2ac8caae0a91b8222e88e8f1e"}, + {file = "psutil-5.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:547ebb02031fdada635452250ff39942db8310b5c4a8102dfe9384ee5791e650"}, + {file = "psutil-5.9.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:d8c3cc6bb76492133474e130a12351a325336c01c96a24aae731abf5a47fe088"}, + {file = "psutil-5.9.3-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07d880053c6461c9b89cd5d4808f3b8336665fa3acdefd6777662c5ed73a851a"}, + {file = "psutil-5.9.3-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e8b50241dd3c2ed498507f87a6602825073c07f3b7e9560c58411c14fe1e1c9"}, + {file = "psutil-5.9.3-cp36-cp36m-win32.whl", hash = "sha256:828c9dc9478b34ab96be75c81942d8df0c2bb49edbb481f597314d92b6441d89"}, + {file = "psutil-5.9.3-cp36-cp36m-win_amd64.whl", hash = "sha256:ed15edb14f52925869250b1375f0ff58ca5c4fa8adefe4883cfb0737d32f5c02"}, + {file = "psutil-5.9.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:d266cd05bd4a95ca1c2b9b5aac50d249cf7c94a542f47e0b22928ddf8b80d1ef"}, + {file = "psutil-5.9.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e4939ff75149b67aef77980409f156f0082fa36accc475d45c705bb00c6c16a"}, + {file = "psutil-5.9.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:68fa227c32240c52982cb931801c5707a7f96dd8927f9102d6c7771ea1ff5698"}, + {file = "psutil-5.9.3-cp37-cp37m-win32.whl", hash = "sha256:beb57d8a1ca0ae0eb3d08ccaceb77e1a6d93606f0e1754f0d60a6ebd5c288837"}, + {file = "psutil-5.9.3-cp37-cp37m-win_amd64.whl", hash = "sha256:12500d761ac091f2426567f19f95fd3f15a197d96befb44a5c1e3cbe6db5752c"}, + {file = "psutil-5.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ba38cf9984d5462b506e239cf4bc24e84ead4b1d71a3be35e66dad0d13ded7c1"}, + {file = "psutil-5.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:46907fa62acaac364fff0b8a9da7b360265d217e4fdeaca0a2397a6883dffba2"}, + {file = "psutil-5.9.3-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a04a1836894c8279e5e0a0127c0db8e198ca133d28be8a2a72b4db16f6cf99c1"}, + {file = "psutil-5.9.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a4e07611997acf178ad13b842377e3d8e9d0a5bac43ece9bfc22a96735d9a4f"}, + {file = "psutil-5.9.3-cp38-cp38-win32.whl", hash = "sha256:6ced1ad823ecfa7d3ce26fe8aa4996e2e53fb49b7fed8ad81c80958501ec0619"}, + {file = "psutil-5.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:35feafe232d1aaf35d51bd42790cbccb882456f9f18cdc411532902370d660df"}, + {file = "psutil-5.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:538fcf6ae856b5e12d13d7da25ad67f02113c96f5989e6ad44422cb5994ca7fc"}, + {file = "psutil-5.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a3d81165b8474087bb90ec4f333a638ccfd1d69d34a9b4a1a7eaac06648f9fbe"}, + {file = "psutil-5.9.3-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a7826e68b0cf4ce2c1ee385d64eab7d70e3133171376cac53d7c1790357ec8f"}, + {file = "psutil-5.9.3-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ec296f565191f89c48f33d9544d8d82b0d2af7dd7d2d4e6319f27a818f8d1cc"}, + {file = "psutil-5.9.3-cp39-cp39-win32.whl", hash = "sha256:9ec95df684583b5596c82bb380c53a603bb051cf019d5c849c47e117c5064395"}, + {file = "psutil-5.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:4bd4854f0c83aa84a5a40d3b5d0eb1f3c128f4146371e03baed4589fe4f3c931"}, + {file = "psutil-5.9.3.tar.gz", hash = "sha256:7ccfcdfea4fc4b0a02ca2c31de7fcd186beb9cff8207800e14ab66f79c773af6"}, +] +ptyprocess = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] +pure-eval = [ + {file = "pure_eval-0.2.2-py3-none-any.whl", hash = "sha256:01eaab343580944bc56080ebe0a674b39ec44a945e6d09ba7db3cb8cec289350"}, + {file = "pure_eval-0.2.2.tar.gz", hash = "sha256:2b45320af6dfaa1750f543d714b6d1c520a1688dec6fd24d339063ce0aaa9ac3"}, +] +py = [ + {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, + {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, +] +pycapnp = [ + {file = "pycapnp-1.1.0-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:bdd013eae51d190a2426d00cc72d0aaed148a5be778ca86ee1adae3ab7a0613f"}, + {file = "pycapnp-1.1.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:b9a1d6306a0e3e0090574aeb08d432bd67f9eb04ab564e89ef34cd1fe320b20f"}, + {file = "pycapnp-1.1.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:47c8bc28521312660c95cfc8a552654949407f8b17bc7ed6955ad7dae34d21a4"}, + {file = "pycapnp-1.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:60adf2674f89f629551171116b8f400b17e9a41a2ef15736767acec405d4ca50"}, + {file = "pycapnp-1.1.0-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:a788a374ccb93354943c89f5b1caf785faf7bb90191cd6265e042aa004f8b206"}, + {file = "pycapnp-1.1.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:2a1746017079107232faf26af8ef4284ab0b20ce5cbe688d44e7553a67e5e5cb"}, + {file = "pycapnp-1.1.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0c770145a4eccfe97f53ab500283aa9bf969d4a37bffc75737a964db4f2af833"}, + {file = "pycapnp-1.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:1774a4fe9db5f094ba40cf00898fa4b437773e7f9c538b779275b9f422a92ebc"}, + {file = "pycapnp-1.1.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:2b28d5d951602c0b832bbe63f85ebdd7685b33118b1c11c2c65a243ec9f35a66"}, + {file = "pycapnp-1.1.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:61b009faba34855c9d29db107e188898c83099347e22ebcbc1d955774403247b"}, + {file = "pycapnp-1.1.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2a7aa9af0185e5977a59228db5042dffb048b2d4bf4f665d2105b4781cf2fcbc"}, + {file = "pycapnp-1.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:13badfb644e2eb7f1219aab259d18b262d1512021e4112fa1ad5e74d17bc30cf"}, + {file = "pycapnp-1.1.0.tar.gz", hash = "sha256:786a2e39b79e592a41e8a1eaeea6e41e2015ecb9f5b7f7c20dfc5768ba1ae077"}, +] +pycodestyle = [ + {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, + {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, +] +pycparser = [ + {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, + {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, +] +pycryptodome = [ + {file = "pycryptodome-3.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:ff7ae90e36c1715a54446e7872b76102baa5c63aa980917f4aa45e8c78d1a3ec"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:2ffd8b31561455453ca9f62cb4c24e6b8d119d6d531087af5f14b64bee2c23e6"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:2ea63d46157386c5053cfebcdd9bd8e0c8b7b0ac4a0507a027f5174929403884"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:7c9ed8aa31c146bef65d89a1b655f5f4eab5e1120f55fc297713c89c9e56ff0b"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:5099c9ca345b2f252f0c28e96904643153bae9258647585e5e6f649bb7a1844a"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-manylinux2014_aarch64.whl", hash = "sha256:2ec709b0a58b539a4f9d33fb8508264c3678d7edb33a68b8906ba914f71e8c13"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-musllinux_1_1_aarch64.whl", hash = "sha256:2ae53125de5b0d2c95194d957db9bb2681da8c24d0fb0fe3b056de2bcaf5d837"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-win32.whl", hash = "sha256:fd2184aae6ee2a944aaa49113e6f5787cdc5e4db1eb8edb1aea914bd75f33a0c"}, + {file = "pycryptodome-3.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:7e3a8f6ee405b3bd1c4da371b93c31f7027944b2bcce0697022801db93120d83"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:b9c5b1a1977491533dfd31e01550ee36ae0249d78aae7f632590db833a5012b8"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0926f7cc3735033061ef3cf27ed16faad6544b14666410727b31fea85a5b16eb"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:2aa55aae81f935a08d5a3c2042eb81741a43e044bd8a81ea7239448ad751f763"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:c3640deff4197fa064295aaac10ab49a0d55ef3d6a54ae1499c40d646655c89f"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-manylinux2014_aarch64.whl", hash = "sha256:045d75527241d17e6ef13636d845a12e54660aa82e823b3b3341bcf5af03fa79"}, + {file = "pycryptodome-3.15.0-cp27-cp27mu-musllinux_1_1_aarch64.whl", hash = "sha256:eb6fce570869e70cc8ebe68eaa1c26bed56d40ad0f93431ee61d400525433c54"}, + {file = "pycryptodome-3.15.0-cp35-abi3-macosx_10_9_x86_64.whl", hash = "sha256:9ee40e2168f1348ae476676a2e938ca80a2f57b14a249d8fe0d3cdf803e5a676"}, + {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_i686.whl", hash = "sha256:4c3ccad74eeb7b001f3538643c4225eac398c77d617ebb3e57571a897943c667"}, + {file = "pycryptodome-3.15.0-cp35-abi3-manylinux1_x86_64.whl", hash = "sha256:1b22bcd9ec55e9c74927f6b1f69843cb256fb5a465088ce62837f793d9ffea88"}, + {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_i686.whl", hash = "sha256:57f565acd2f0cf6fb3e1ba553d0cb1f33405ec1f9c5ded9b9a0a5320f2c0bd3d"}, + {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2010_x86_64.whl", hash = "sha256:4b52cb18b0ad46087caeb37a15e08040f3b4c2d444d58371b6f5d786d95534c2"}, + {file = "pycryptodome-3.15.0-cp35-abi3-manylinux2014_aarch64.whl", hash = "sha256:092a26e78b73f2530b8bd6b3898e7453ab2f36e42fd85097d705d6aba2ec3e5e"}, + {file = "pycryptodome-3.15.0-cp35-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:50ca7e587b8e541eb6c192acf92449d95377d1f88908c0a32ac5ac2703ebe28b"}, + {file = "pycryptodome-3.15.0-cp35-abi3-win32.whl", hash = "sha256:e244ab85c422260de91cda6379e8e986405b4f13dc97d2876497178707f87fc1"}, + {file = "pycryptodome-3.15.0-cp35-abi3-win_amd64.whl", hash = "sha256:c77126899c4b9c9827ddf50565e93955cb3996813c18900c16b2ea0474e130e9"}, + {file = "pycryptodome-3.15.0-pp27-pypy_73-macosx_10_9_x86_64.whl", hash = "sha256:9eaadc058106344a566dc51d3d3a758ab07f8edde013712bc8d22032a86b264f"}, + {file = "pycryptodome-3.15.0-pp27-pypy_73-manylinux1_x86_64.whl", hash = "sha256:ff287bcba9fbeb4f1cccc1f2e90a08d691480735a611ee83c80a7d74ad72b9d9"}, + {file = "pycryptodome-3.15.0-pp27-pypy_73-manylinux2010_x86_64.whl", hash = "sha256:60b4faae330c3624cc5a546ba9cfd7b8273995a15de94ee4538130d74953ec2e"}, + {file = "pycryptodome-3.15.0-pp27-pypy_73-win32.whl", hash = "sha256:a8f06611e691c2ce45ca09bbf983e2ff2f8f4f87313609d80c125aff9fad6e7f"}, + {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-macosx_10_9_x86_64.whl", hash = "sha256:b9cc96e274b253e47ad33ae1fccc36ea386f5251a823ccb50593a935db47fdd2"}, + {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux1_x86_64.whl", hash = "sha256:ecaaef2d21b365d9c5ca8427ffc10cebed9d9102749fd502218c23cb9a05feb5"}, + {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:d2a39a66057ab191e5c27211a7daf8f0737f23acbf6b3562b25a62df65ffcb7b"}, + {file = "pycryptodome-3.15.0-pp36-pypy36_pp73-win32.whl", hash = "sha256:9c772c485b27967514d0df1458b56875f4b6d025566bf27399d0c239ff1b369f"}, + {file = "pycryptodome-3.15.0.tar.gz", hash = "sha256:9135dddad504592bcc18b0d2d95ce86c3a5ea87ec6447ef25cfedea12d6018b8"}, +] +pycuda = [ + {file = "pycuda-2022.1.tar.gz", hash = "sha256:acd9030d93e76e60b122e33ad16bcf01bb1344f4c304dedff1cd2bffb0f313a3"}, +] +pycurl = [ + {file = "pycurl-7.45.1.tar.gz", hash = "sha256:a863ad18ff478f5545924057887cdae422e1b2746e41674615f687498ea5b88a"}, +] +pyflakes = [ + {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, + {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, +] +pygame = [ + {file = "pygame-2.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f149e182d0eeef15d8a9b4c9dad1b87dc6eba3a99bd3c44a777a3a2b053a3dca"}, + {file = "pygame-2.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:dc4444d61d48c5546df5137cdf81554887ddb6e2ef1be7f51eb77ea3b6bdd56f"}, + {file = "pygame-2.1.2-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a0ccf8e3dce7ca67d523a6020b7e3dbf4b26797a9a8db5cc4c7b5ef20fb64701"}, + {file = "pygame-2.1.2-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7889dce887ec83c9a0bef8d9eb3669d8863fdaf37c45bacec707d8ad90b24a38"}, + {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:db2f40d5a75fd9cdda473c58b0d8b294da6e0179f00bb3b1fc2f7f29cac09bea"}, + {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4b4cd440d50a9f8551b8989e856aab175593af07eb825cad22fd2f8f6f2ffce"}, + {file = "pygame-2.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:754c2906f2ef47173a14493e1de116b2a56a2c8e1764f1202ba844d080248a5b"}, + {file = "pygame-2.1.2-cp310-cp310-win32.whl", hash = "sha256:c99b95e62cdda29c2e60235d7763447c168a6a877403e6f9ca5b2e2bb297c2ce"}, + {file = "pygame-2.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:9649419254d3282dae41f23837de4108b17bc62187c3acd8af2ae3801b765cbd"}, + {file = "pygame-2.1.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:dcc285ee1f1d0e2672cc52f880fd3f564b1505de710e817f692fbf64a72ca657"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e1bb25986db77a48f632469c6bc61baf7508ce945aa6161c02180d4ee5ac5b8d"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:e7a8e18677e0064b7a422f6653a622652d932826a27e50f279d55a8b122a1a83"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd528dbb91eca16f7522c975d0f9e94b95f6b5024c82c3247dc0383d242d33c6"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc9586e17875c0cdf8764597955f9daa979098fd4f80be07ed68276ac225480"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c9ce7f3d8af14d7e04eb7eb41c5e5313c43508c252bb2b9eb53e51fc87ada9fd"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e09044e9e1aa8512d6a9c7ce5f94b881824bcfc401105f3c24f546dfc3bb4aa5"}, + {file = "pygame-2.1.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:40e4d8d65985bb467d9c5a1305fb53fd6820c61d764979600becab973339676f"}, + {file = "pygame-2.1.2-cp36-cp36m-win32.whl", hash = "sha256:50d9a21edd551669862c27c9272747401b20b1939abaacb842c08ea1cdd1c04d"}, + {file = "pygame-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:e18c9466131378421d00fc40b637425229238d506a073d9c537b230b355a25d6"}, + {file = "pygame-2.1.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:07ca9f683075aea9bd977af9f09a720ebf747343d3ea8103e4f1735283b02330"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3c8d6637ff75351e581327efefa9d04eeb0f257b533392b6cc6b15ceca4f7c5e"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5ebbefb8b576572c8fc97a3321d37dc2b4afea6b6e3877a67f7158d8c2c4cefe"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d6452419e01a0f848aed0597f69fd10a4c2a7750c15d1b0607f86090a39dcf3"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e627300a66a90651fb39e41601d447b1fdbbfffca3f08ef0278d6cc0436b2160"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a56a811d8821f7b9a594e3d0e0dd8bd39b25e3eea8963d5963263b90fd2ea5c2"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:24b4f7f30fa2b3d092b60be6fcc725fb91d569fc87a9bcc91614ee8b0c005726"}, + {file = "pygame-2.1.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8e87716114e97322fb177e223d889a4be369a0f73212f4f8507fe0cd43253b23"}, + {file = "pygame-2.1.2-cp37-cp37m-win32.whl", hash = "sha256:20676da24e3e3e6b9fc4eecc7ba09d77ef46c3a83a028763ba1d222476c2e3fb"}, + {file = "pygame-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:93c4cbfc942dd00410eaa9e84252129f9f9993f37f683006d7b49ab245342254"}, + {file = "pygame-2.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2405414d8c572668e04739875661e030a0c588e197fa95463fe301c3d0a0510b"}, + {file = "pygame-2.1.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e8632f6b2ddb90f6f3950744bd65d5ef15af615e3034057fa30ff836f48a7179"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ca5ef1315fa67c241a657ab077be44f230c05740c95f0b46409457dceefdc7e5"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1219a963941bd53aa754e8449364c142004fe706c33a9c22ff2a76521a82d078"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3bb0674aa789848ddc264bfc60c54965bf3bb659c141de4f600e379acc9b944c"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:24254c4244f0d9bdc904f5d3f38e86757ca4c6aa0e44a6d55ef5e016bc7274d6"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97a74ba186deee68318a52637012ef6abf5be6282c659e1d1ba6ad08cf35ec85"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0e97d38308c441942577fea7fcd1326308bc56d6be6c024218e94d075d322e0f"}, + {file = "pygame-2.1.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ea36f4f93524554a35cac2359df63b50af6556ed866830aa1f07f0d8580280ea"}, + {file = "pygame-2.1.2-cp38-cp38-win32.whl", hash = "sha256:4aa3ae32320cc704d63e185864e44f6265c2a6e52c9384afe152cc3d51b3a2ef"}, + {file = "pygame-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:9d7b021b8dde5d528363e474bc18bd6f79a9666eef89fb4859bcb8f0a536c9de"}, + {file = "pygame-2.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:660c80c0b2e80f1f801715583b759fb4c7bc0c11eb3b534e89c9fc4bfbc38acd"}, + {file = "pygame-2.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dad6bf3fdd3752d7519422f3732be779b98fe7c87d32c3efe2fdffdcbeebb6ca"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:119dee20c372c85dc47b717119534d15a60c64ceab8b0eb09278866d10486afe"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fc2e5db54491e8f27785fc5204c96f540d3557dcf5b0a9a857b6594d6b32561b"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2d3c50ee9847b743db6cd7b1bb17a94c2c2abc16679d70f5e745cabdf19e655"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ecda8dd4583982bb65f9c682f244a5e94524dcf628379766227e9ed97201a49"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e06ae8e1c830f1b9c36a2bc6bb11de840232e95b78e2c349c6ed803a303be19"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c5ea87da5fe4b6164c3854f3b0c9146811dbad0dd7fa74297683dfacc485ae1c"}, + {file = "pygame-2.1.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0427c103f741234336e5606d2fad86f5403c1a3d1dc55c309fbff3c984f0c9ae"}, + {file = "pygame-2.1.2-cp39-cp39-win32.whl", hash = "sha256:5e88b0d4338b94960686f59396f23f7f684fed4859fcc3b9f40286d72c1c61af"}, + {file = "pygame-2.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:5d0c14152d0ca8ef5fbcc5ed9981462bdf59a9ae85a291e62d8a8d0b7e5cbe43"}, + {file = "pygame-2.1.2-pp36-pypy36_pp73-win32.whl", hash = "sha256:636f51f56615d67459b11918206bb4da30cd7d7042027bf997c218ccd6c77902"}, + {file = "pygame-2.1.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ff961c3280d6ee5f4163f4772f963d7a4dbe42e36c6dd54b79ad436c1f046e5d"}, + {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fc30e834f65b893d1b4c230070183bf98e6b70c41c1511687e8436a33d5ce49d"}, + {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c7600bf307de1ca1dca0cc7840e34604d5b0b0a5a5dad345c3fa62b054b886d"}, + {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7fdb93b4282962c9a2ebf1af994ee698be823dd913218ed97a5f2fb372b10b66"}, + {file = "pygame-2.1.2-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1fddec8829e96424800c806582d73a5173b7d48946cccf7d035839ca09850db8"}, + {file = "pygame-2.1.2.tar.gz", hash = "sha256:d6d0eca28f886f0477cd0721ac688189155a587f2bb8eae740e52ca56c3ad23c"}, +] +pygments = [ + {file = "Pygments-2.13.0-py3-none-any.whl", hash = "sha256:f643f331ab57ba3c9d89212ee4a2dabc6e94f117cf4eefde99a0574720d14c42"}, + {file = "Pygments-2.13.0.tar.gz", hash = "sha256:56a8508ae95f98e2b9bdf93a6be5ae3f7d8af858b43e02c5a2ff083726be40c1"}, +] +pyjwt = [ + {file = "PyJWT-2.6.0-py3-none-any.whl", hash = "sha256:d83c3d892a77bbb74d3e1a2cfa90afaadb60945205d1095d9221f04466f64c14"}, + {file = "PyJWT-2.6.0.tar.gz", hash = "sha256:69285c7e31fc44f68a1feb309e948e0df53259d579295e6cfe2b1792329f05fd"}, +] +pylev = [ + {file = "pylev-1.4.0-py2.py3-none-any.whl", hash = "sha256:7b2e2aa7b00e05bb3f7650eb506fc89f474f70493271a35c242d9a92188ad3dd"}, + {file = "pylev-1.4.0.tar.gz", hash = "sha256:9e77e941042ad3a4cc305dcdf2b2dec1aec2fbe3dd9015d2698ad02b173006d1"}, +] +pylint = [ + {file = "pylint-2.15.4-py3-none-any.whl", hash = "sha256:629cf1dbdfb6609d7e7a45815a8bb59300e34aa35783b5ac563acaca2c4022e9"}, + {file = "pylint-2.15.4.tar.gz", hash = "sha256:5441e9294335d354b7bad57c1044e5bd7cce25c433475d76b440e53452fa5cb8"}, +] +pymsalruntime = [ + {file = "pymsalruntime-0.11.2-cp310-cp310-win32.whl", hash = "sha256:a45e35c9fa58c196029bb9f5b8bedd313b2a8ac971d576c57c31cb06139de247"}, + {file = "pymsalruntime-0.11.2-cp310-cp310-win_amd64.whl", hash = "sha256:81bd2502779b1c032d878b3e6bdb64882b8c7f94560cffed62e2555470d1fe45"}, + {file = "pymsalruntime-0.11.2-cp36-cp36m-win32.whl", hash = "sha256:e6f6316128de8f62caeadf2a755e3919cc4532964d77a550974b156f27ae5f33"}, + {file = "pymsalruntime-0.11.2-cp36-cp36m-win_amd64.whl", hash = "sha256:ee139e7222e89f5bda3d6e8f9426b6bc0e8b4ef7c6fc2e6b9bd8b6c11579c3fb"}, + {file = "pymsalruntime-0.11.2-cp37-cp37m-win32.whl", hash = "sha256:6eedca38877cc8718ac273cf796aebed9a25d64258d717599f601bc01f9b7922"}, + {file = "pymsalruntime-0.11.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b0f289e4d3b01bbddf422cd82899295cf5ee4c41eda45752dd3a0733acd0ba0f"}, + {file = "pymsalruntime-0.11.2-cp38-cp38-win32.whl", hash = "sha256:5760a1c1b6fa8c10f15a325815e0315a703106d3984cbc939d17f48d84ab21bb"}, + {file = "pymsalruntime-0.11.2-cp38-cp38-win_amd64.whl", hash = "sha256:e0654d04f54d1cee218a868da77f4a5a2b5369e2cc9bb35f539144256f08b00a"}, + {file = "pymsalruntime-0.11.2-cp39-cp39-win32.whl", hash = "sha256:06501f5a1fcf83ba2edbae34c94df08f50c31290301ac20f1828c4cecc2ae1bc"}, + {file = "pymsalruntime-0.11.2-cp39-cp39-win_amd64.whl", hash = "sha256:c10f8761baa042cba3a502be50fb19d0fe46850de82cb730b836ac3aff7a9b0e"}, + {file = "pymsalruntime-0.11.2.tar.gz", hash = "sha256:636d01c7f6f165b48e288c2af0bd21447649846af00050956ba28a5d4079e98b"}, +] +pymysql = [ + {file = "PyMySQL-0.9.3-py2.py3-none-any.whl", hash = "sha256:3943fbbbc1e902f41daf7f9165519f140c4451c179380677e6a848587042561a"}, + {file = "PyMySQL-0.9.3.tar.gz", hash = "sha256:d8c059dcd81dedb85a9f034d5e22dcb4442c0b201908bede99e306d65ea7c8e7"}, +] +pynacl = [ + {file = "PyNaCl-1.5.0-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:401002a4aaa07c9414132aaed7f6836ff98f59277a234704ff66878c2ee4a0d1"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:52cb72a79269189d4e0dc537556f4740f7f0a9ec41c1322598799b0bdad4ef92"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a36d4a9dda1f19ce6e03c9a784a2921a4b726b02e1c736600ca9c22029474394"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:0c84947a22519e013607c9be43706dd42513f9e6ae5d39d3613ca1e142fba44d"}, + {file = "PyNaCl-1.5.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06b8f6fa7f5de8d5d2f7573fe8c863c051225a27b61e6860fd047b1775807858"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a422368fc821589c228f4c49438a368831cb5bbc0eab5ebe1d7fac9dded6567b"}, + {file = "PyNaCl-1.5.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:61f642bf2378713e2c2e1de73444a3778e5f0a38be6fee0fe532fe30060282ff"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win32.whl", hash = "sha256:e46dae94e34b085175f8abb3b0aaa7da40767865ac82c928eeb9e57e1ea8a543"}, + {file = "PyNaCl-1.5.0-cp36-abi3-win_amd64.whl", hash = "sha256:20f42270d27e1b6a29f54032090b972d97f0a1b0948cc52392041ef7831fee93"}, + {file = "PyNaCl-1.5.0.tar.gz", hash = "sha256:8ac7448f09ab85811607bdd21ec2464495ac8b7c66d146bf545b0f08fb9220ba"}, +] +pyopencl = [ + {file = "pyopencl-2022.2.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c270c7090f6bd8a1cc006aca38256936a0b82f70165e8aff873763218f29bf85"}, + {file = "pyopencl-2022.2.4-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5a823c2003d2d3754836f7e65f19e553f8ca022f493d0111ea3bacd886853596"}, + {file = "pyopencl-2022.2.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c1d1605eed80f9305251578b41068e1021f140e6d503b22933a4860c43e7cde"}, + {file = "pyopencl-2022.2.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:85bf733b73158a66484763ff7bca7b2d5d0187933987a2753cad3f0deeff989f"}, + {file = "pyopencl-2022.2.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b1f4288700717e3f0b0ce7c6663729ea6a1362d86964261fcaa84ca2efcf06b0"}, + {file = "pyopencl-2022.2.4-cp310-cp310-win_amd64.whl", hash = "sha256:dc72723fd7d5041521f9dccece70ab1ab06f2ad74b1738712a49a3b56cea3628"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:82b3f13843c034cee54906b2c750a65910c9f28ba58b61abf80b9bc9c1595a1f"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1a0893caf4b522559a233c4d66e7c8a96a01d8f2c038ff7fd5b3d795a1eb2ed"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a3a5d59d87d3e271f7d41c80d558cf45f4226003ec3c341d8374d3ef849614b4"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:cef4b300b64b58d55364d6d9eb37123907ee1f32f2dc7f0b7d1fcabbaf6fe260"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:47f751cfa75d3038ba0aedf587dd42dc447b8e49b88339048b2147cce176e286"}, + {file = "pyopencl-2022.2.4-cp36-cp36m-win_amd64.whl", hash = "sha256:3b82458c982e6eb61691712139fc8cfd69f28ef127db2b0de06e87058e919f96"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:28a0df521643644a1d5b521032b7c6617f7d53b09851a21ce9b10b8ed869dfd5"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3611fe2f7c219dcf3d3a119873bfe7a837a467aac00c6e53682dda71a060d29f"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a5cfc4459e5c84ecba49b928d2e9a439f0421342e16884a6749aa227efea900"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:37001a8b70bde143597f22e79dbac90ae8c997e2136b0ec4391a2824f78eb548"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:349e1ff88b2c0ddb343666e1857b86c65fa34545354012382fe84b5d564ffde1"}, + {file = "pyopencl-2022.2.4-cp37-cp37m-win_amd64.whl", hash = "sha256:da4bc9167f04eb47774e30c5cc6b411b970d8b09ea61873b86058dd2d64445d1"}, + {file = "pyopencl-2022.2.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8fae8ba0a020c5db230bba53c39fd8f950983a992fbb1318a19ba06af83bd416"}, + {file = "pyopencl-2022.2.4-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8685df5b6ec23da07751afd589a168825f6fa727634bf957652091ed7c937151"}, + {file = "pyopencl-2022.2.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84f94a99822d81372b16dcdc56ee9f7e3d2268acaf9cca21924e65b4ddefae20"}, + {file = "pyopencl-2022.2.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:532d7972d8cc27278761634e5367ff84cacee81f32490ffce9285ea122804768"}, + {file = "pyopencl-2022.2.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d31c4cd0af50aa602b2131a7b5c964e9105291d611f2d4418ed4628094c9336f"}, + {file = "pyopencl-2022.2.4-cp38-cp38-win_amd64.whl", hash = "sha256:ca9d67ca0b072989530c71c4f781acc1efd1adb6a459e742a6d38d2b8ab1d8e8"}, + {file = "pyopencl-2022.2.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:c6d2a3d6dfbae423dbf6dce45ca8a1b32cca2ee842aea49d246f8bbf8ab90812"}, + {file = "pyopencl-2022.2.4-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e6e7fbacef495944e1ebb1456ac7a4c38562077d50dfd804dca6aef204f6a79a"}, + {file = "pyopencl-2022.2.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3735b5d2add089ea02b43ace69b50fb8fb55b11b93f7bc4be5b4249fb3370b64"}, + {file = "pyopencl-2022.2.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:d9e88c92569a5bbcbec802dc193be06a19891979fb02f9ca4739239e2cfd5e04"}, + {file = "pyopencl-2022.2.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0b456e153c1ae1f92379680710daa228c9aa0302918e24f12355533bd4b5e1f4"}, + {file = "pyopencl-2022.2.4-cp39-cp39-win_amd64.whl", hash = "sha256:876912cfebfe9aa92a478d0bf737178e7b686d27103d7279cd1dc754b8e6e85d"}, + {file = "pyopencl-2022.2.4-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72f259595ac26d250f39ffbfcb47cfa81181aff6f24d70e718f67114a452eb43"}, + {file = "pyopencl-2022.2.4-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0caf451084194da8262d6197251cccdac4c2945d0cb8807fe4bd1d7b0b6ecd5c"}, + {file = "pyopencl-2022.2.4-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1d64d2354fef846fa7e252078c5a4b74a3c302454de6167d6802a9e1d374e60"}, + {file = "pyopencl-2022.2.4-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:912bc9c48c443f5a88e8827ee68e9cb03f6a1d78ac6f64bb7a4d65f849fa7069"}, + {file = "pyopencl-2022.2.4-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ebf8ebabee550849e8d4a21e13487d34b17c89749b38169bba249020f21363cf"}, + {file = "pyopencl-2022.2.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3384a1089df1256df00fde4fc87d1f7f500fc640f4ff47adf55a592502a16396"}, + {file = "pyopencl-2022.2.4.tar.gz", hash = "sha256:b57c9ef8bd8e6db07e89106f3091ba236b24f95a38fd40dfb17d2ed7ff6be4ad"}, +] +pyopenssl = [ + {file = "pyOpenSSL-22.0.0-py2.py3-none-any.whl", hash = "sha256:ea252b38c87425b64116f808355e8da644ef9b07e429398bfece610f893ee2e0"}, + {file = "pyOpenSSL-22.0.0.tar.gz", hash = "sha256:660b1b1425aac4a1bea1d94168a85d99f0b3144c869dd4390d27629d0087f1bf"}, +] +pyparsing = [ + {file = "pyparsing-3.0.9-py3-none-any.whl", hash = "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc"}, + {file = "pyparsing-3.0.9.tar.gz", hash = "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb"}, +] +pyprof2calltree = [ + {file = "pyprof2calltree-1.4.5.tar.gz", hash = "sha256:a635672ff31677486350b2be9a823ef92f740e6354a6aeda8fa4a8a3768e8f2f"}, +] +pyproj = [ + {file = "pyproj-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f343725566267a296b09ee7e591894f1fdc90f84f8ad5ec476aeb53bd4479c07"}, + {file = "pyproj-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5816807ca0bdc7256558770c6206a6783a3f02bcf844f94ee245f197bb5f7285"}, + {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7e609903572a56cca758bbaee5c1663c3e829ddce5eec4f368e68277e37022b"}, + {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4fd425ee8b6781c249c7adb7daa2e6c41ce573afabe4f380f5eecd913b56a3be"}, + {file = "pyproj-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:954b068136518b3174d0a99448056e97af62b63392a95c420894f7de2229dae6"}, + {file = "pyproj-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:4a23d84c5ffc383c7d9f0bde3a06fc1f6697b1b96725597f8f01e7b4bef0a2b5"}, + {file = "pyproj-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1f9c100fd0fd80edbc7e4daa303600a8cbef6f0de43d005617acb38276b88dc0"}, + {file = "pyproj-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa5171f700f174777a9e9ed8f4655583243967c0f9cf2c90e3f54e54ff740134"}, + {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a496d9057b2128db9d733e66b206f2d5954bbae6b800d412f562d780561478c"}, + {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:52e54796e2d9554a5eb8f11df4748af1fbbc47f76aa234d6faf09216a84554c5"}, + {file = "pyproj-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a454a7c4423faa2a14e939d08ef293ee347fa529c9df79022b0585a6e1d8310c"}, + {file = "pyproj-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:25a36e297f3e0524694d40259e3e895edc1a47492a0e30608268ffc1328e3f5d"}, + {file = "pyproj-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:77d5f519f3cdb94b026ecca626f78db4f041afe201cf082079c8c0092a30b087"}, + {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ccb4b70ad25218027f77e0c8934d10f9b7cdf91d5e64080147743d58fddbc3c0"}, + {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e161114bc92701647a83c4bbce79489984f12d980cabb365516e953d1450885"}, + {file = "pyproj-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f80adda8c54b84271a93829477a01aa57bc178c834362e9f74e1de1b5033c74c"}, + {file = "pyproj-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:221d8939685e0c43ee594c9f04b6a73a10e8e1cc0e85f28be0b4eb2f1bc8777d"}, + {file = "pyproj-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d94afed99f31673d3d19fe750283621e193e2a53ca9e0443bf9d092c3905833b"}, + {file = "pyproj-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fff9c3a991508f16027be27d153f6c5583d03799443639d13c681e60f49e2d7"}, + {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b85acf09e5a9e35cd9ee72989793adb7089b4e611be02a43d3d0bda50ad116b"}, + {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:45554f47d1a12a84b0620e4abc08a2a1b5d9f273a4759eaef75e74788ec7162a"}, + {file = "pyproj-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12f62c20656ac9b6076ebb213e9a635d52f4f01fef95310121d337e62e910cb6"}, + {file = "pyproj-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:65a0bcdbad95b3c00b419e5d75b1f7e450ec17349b5ea16bf7438ac1d50a12a2"}, + {file = "pyproj-3.4.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:14ad113b5753c6057f9b2f3c85a6497cef7fa237c4328f2943c0223e98c1dde6"}, + {file = "pyproj-3.4.0-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4688b4cd62cbd86b5e855f9e27d90fbb53f2b4c2ea1cd394a46919e1a4151b89"}, + {file = "pyproj-3.4.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47ad53452ae1dc8b0bf1df920a210bb5616989085aa646592f8681f1d741a754"}, + {file = "pyproj-3.4.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:48787962232109bad8b72e27949037a9b03591228a6955f25dbe451233e8648a"}, + {file = "pyproj-3.4.0-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2cb8592259ea54e7557523b079d3f2304081680bdb48bfbf0fd879ee6156129c"}, + {file = "pyproj-3.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82200b4569d68b421c079d2973475b58d5959306fe758b43366e79fe96facfe5"}, + {file = "pyproj-3.4.0.tar.gz", hash = "sha256:a708445927ace9857f52c3ba67d2915da7b41a8fdcd9b8f99a4c9ed60a75eb33"}, +] +pyreadline3 = [ + {file = "pyreadline3-3.4.1-py3-none-any.whl", hash = "sha256:b0efb6516fd4fb07b45949053826a62fa4cb353db5be2bbb4a7aa1fdd1e345fb"}, + {file = "pyreadline3-3.4.1.tar.gz", hash = "sha256:6f3d1f7b8a31ba32b73917cefc1f28cc660562f39aea8646d30bd6eff21f7bae"}, +] +pyrsistent = [ + {file = "pyrsistent-0.18.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:df46c854f490f81210870e509818b729db4488e1f30f2a1ce1698b2295a878d1"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d45866ececf4a5fff8742c25722da6d4c9e180daa7b405dc0a2a2790d668c26"}, + {file = "pyrsistent-0.18.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4ed6784ceac462a7d6fcb7e9b663e93b9a6fb373b7f43594f9ff68875788e01e"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win32.whl", hash = "sha256:e4f3149fd5eb9b285d6bfb54d2e5173f6a116fe19172686797c056672689daf6"}, + {file = "pyrsistent-0.18.1-cp310-cp310-win_amd64.whl", hash = "sha256:636ce2dc235046ccd3d8c56a7ad54e99d5c1cd0ef07d9ae847306c91d11b5fec"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e92a52c166426efbe0d1ec1332ee9119b6d32fc1f0bbfd55d5c1088070e7fc1b"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7a096646eab884bf8bed965bad63ea327e0d0c38989fc83c5ea7b8a87037bfc"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cdfd2c361b8a8e5d9499b9082b501c452ade8bbf42aef97ea04854f4a3f43b22"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win32.whl", hash = "sha256:7ec335fc998faa4febe75cc5268a9eac0478b3f681602c1f27befaf2a1abe1d8"}, + {file = "pyrsistent-0.18.1-cp37-cp37m-win_amd64.whl", hash = "sha256:6455fc599df93d1f60e1c5c4fe471499f08d190d57eca040c0ea182301321286"}, + {file = "pyrsistent-0.18.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fd8da6d0124efa2f67d86fa70c851022f87c98e205f0594e1fae044e7119a5a6"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bfe2388663fd18bd8ce7db2c91c7400bf3e1a9e8bd7d63bf7e77d39051b85ec"}, + {file = "pyrsistent-0.18.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0e3e1fcc45199df76053026a51cc59ab2ea3fc7c094c6627e93b7b44cdae2c8c"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win32.whl", hash = "sha256:b568f35ad53a7b07ed9b1b2bae09eb15cdd671a5ba5d2c66caee40dbf91c68ca"}, + {file = "pyrsistent-0.18.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1b96547410f76078eaf66d282ddca2e4baae8964364abb4f4dcdde855cd123a"}, + {file = "pyrsistent-0.18.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f87cc2863ef33c709e237d4b5f4502a62a00fab450c9e020892e8e2ede5847f5"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6bc66318fb7ee012071b2792024564973ecc80e9522842eb4e17743604b5e045"}, + {file = "pyrsistent-0.18.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:914474c9f1d93080338ace89cb2acee74f4f666fb0424896fcfb8d86058bf17c"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win32.whl", hash = "sha256:1b34eedd6812bf4d33814fca1b66005805d3640ce53140ab8bbb1e2651b0d9bc"}, + {file = "pyrsistent-0.18.1-cp39-cp39-win_amd64.whl", hash = "sha256:e24a828f57e0c337c8d8bb9f6b12f09dfdf0273da25fda9e314f0b684b415a07"}, + {file = "pyrsistent-0.18.1.tar.gz", hash = "sha256:d4d61f8b993a7255ba714df3aca52700f8125289f84f704cf80916517c46eb96"}, +] +pyserial = [ + {file = "pyserial-3.5-py2.py3-none-any.whl", hash = "sha256:c4451db6ba391ca6ca299fb3ec7bae67a5c55dde170964c7a14ceefec02f2cf0"}, + {file = "pyserial-3.5.tar.gz", hash = "sha256:3c77e014170dfffbd816e6ffc205e9842efb10be9f58ec16d3e8675b4925cddb"}, +] +pysocks = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] +pytest = [ + {file = "pytest-7.1.3-py3-none-any.whl", hash = "sha256:1377bda3466d70b55e3f5cecfa55bb7cfcf219c7964629b967c37cf0bda818b7"}, + {file = "pytest-7.1.3.tar.gz", hash = "sha256:4f365fec2dff9c1162f834d9f18af1ba13062db0c708bf7b946f8a5c76180c39"}, +] +pytest-forked = [ + {file = "pytest-forked-1.4.0.tar.gz", hash = "sha256:8b67587c8f98cbbadfdd804539ed5455b6ed03802203485dd2f53c1422d7440e"}, + {file = "pytest_forked-1.4.0-py3-none-any.whl", hash = "sha256:bbbb6717efc886b9d64537b41fb1497cfaf3c9601276be8da2cccfea5a3c8ad8"}, +] +pytest-xdist = [ + {file = "pytest-xdist-2.5.0.tar.gz", hash = "sha256:4580deca3ff04ddb2ac53eba39d76cb5dd5edeac050cb6fbc768b0dd712b4edf"}, + {file = "pytest_xdist-2.5.0-py3-none-any.whl", hash = "sha256:6fe5c74fec98906deb8f2d2b616b5c782022744978e7bd4695d39c8f42d0ce65"}, +] +python-dateutil = [ + {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, + {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, +] +python-engineio = [ + {file = "python-engineio-4.3.4.tar.gz", hash = "sha256:d8d8b072799c36cadcdcc2b40d2a560ce09797ab3d2d596b2ad519a5e4df19ae"}, + {file = "python_engineio-4.3.4-py3-none-any.whl", hash = "sha256:7454314a529bba20e745928601ffeaf101c1b5aca9a6c4e48ad397803d10ea0c"}, +] +python-logstash = [ + {file = "python-logstash-0.4.8.tar.gz", hash = "sha256:d04e1ce11ecc107e4a4f3b807fc57d96811e964a554081b3bbb44732f74ef5f9"}, +] +python-rapidjson = [ + {file = "python-rapidjson-1.10.tar.gz", hash = "sha256:acfecbf5edb91ec72a20a125de7f56b8c2f6161eff4c65382c8ee6a2484d3540"}, + {file = "python_rapidjson-1.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1db7b0af882999f5685eb7046a0f3b3aca5d55a3e84b3089747d29a4ec6fdade"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a87c8c8b615513f9dc414af1554140589036d14840f5e1f1845965e1c0a080e1"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a0a2f5c4abe529ca2764343416e35710a263832533b7bdc76c3285efb5b5ecc8"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40467c3a6d8f070cc4d196fe46a79ed59d1a13a4d3fdc6a0325a21816600e5a7"}, + {file = "python_rapidjson-1.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9df4e7237a3e77666ccb9b437013294e6aa3968528f7c61f60f6f38eea0f8f79"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:99a5215f24ff1fa6cc67ee275a6852aa56d934d3b8cd7a40197feb632b54fd76"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3dbea0ee9fa1cd6ecc13a949f6bb94013639d39cdb56f58df4ab61130d35e57c"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:6d1d0c5da3bd5f701b1aed550e1e7bd59b16ae642877cddf18815006cf998f9a"}, + {file = "python_rapidjson-1.10-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:072f76c1f1483bcc4056d7d3a8b0319bf841a73e955f188302094b62b2163bf9"}, + {file = "python_rapidjson-1.10-cp310-cp310-win32.whl", hash = "sha256:c95d466307a2140a7687a575103980c6e81c9f62d19556cafad3d6b2932b7eb1"}, + {file = "python_rapidjson-1.10-cp310-cp310-win_amd64.whl", hash = "sha256:454ffda58cc6fed64d983b1b8ae4b39a563b4fd671dae9132e06450025898539"}, + {file = "python_rapidjson-1.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fff343076fbeee0cd7e4e3fb9472f2d567a127ec7b8b5b7ecba6bf7960a3ce07"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:686482c67727edad4b6d0c753bc159f35134a5a623e9651c4b7c008ef2996252"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ef7d55688b7123d62690b193537cc048fa9f35cfa43d249fedc0d9fd398890a"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3f27c0601792533ab6e98452961d61566480dc155da19d2a358a5fd9a85d9321"}, + {file = "python_rapidjson-1.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:95176e35e3bacb8a1a27f563e815b5b57c717992c871b1c25fd76a835fbba32c"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:47a0ec20886b8be86af307c10d699a447e22979ed7dd1f2b7ed5cb7496b3d920"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:f7968c0cb09d9a76aa2483556ba46ab42634baf216cb2f2c7cd6bf77119a33c1"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:5faab270a4dd49216ceaca7169682680b2f5df8311c1ed259e4612d9d0cf61b7"}, + {file = "python_rapidjson-1.10-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:81b797934dc037810f5f98af138b55a3b6f18dd569cc5e8f81fe79956a4717ca"}, + {file = "python_rapidjson-1.10-cp311-cp311-win32.whl", hash = "sha256:6c1d62cc58a61629fc5e216fb7b3a1b02787c98fded874a7b474b1e6325e377e"}, + {file = "python_rapidjson-1.10-cp311-cp311-win_amd64.whl", hash = "sha256:29d31fc4254f1a4dca420e58bd1331e990fc2959d09ff2daa7934d52732a8491"}, + {file = "python_rapidjson-1.10-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:718f4e217b511cfbf9166f55ccf4bf4e4538495bee403e390cf89791c0debc26"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:541bbb96353cf3fe2bdb29e727087226532be4e4573daad6f042cfdea533a564"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:50f0402a6899c6a177d4a37152deefcd59c61e44bef56b71e8d006a186c86286"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c035e17744d6d6fba073b550b0040a74e55f2ad33fd798df206ff6879b41ad10"}, + {file = "python_rapidjson-1.10-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:da970bde42309a74a5556e696673ea11c4545b8bee5081b84265ded460b2e9ef"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7437a649821008aa456f2fbec737880d7f9bdda7ec94cc1743a43ccf32b5d26"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:362d969bbd277f78bf0b1ffaa810857ea40351146b827f896f8d49e9c25fc99c"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:409256e7748c4ab7f17b3793c7a78ca01914c487644fc42140d116ed4dec8c4f"}, + {file = "python_rapidjson-1.10-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a34a7e2853456fe50ba2ee22e38e7841e55eee10021d4496cce62285f148e8e7"}, + {file = "python_rapidjson-1.10-cp37-cp37m-win32.whl", hash = "sha256:bc4a97940e5afa60a598483d0eb863b26e4810aaf030d92a4301f5fc183e1b6d"}, + {file = "python_rapidjson-1.10-cp37-cp37m-win_amd64.whl", hash = "sha256:89586b67f9c69b66885774acebf3d018e7b8f93cea2b3cffa306ec9d37877594"}, + {file = "python_rapidjson-1.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2e38082b1a8ce3e2bd55821852c0cd643cdabe6497fd9c054f6b47a099afbea0"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0955ef22fabb36b26fcad702ae54c1bce2bc2a74b1883c42d251d72011d0d426"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0a9db03c68ab0158bcdf80299b2c980186d148aa3e05d5650fea5148a425a29a"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fde8ab0f06debaa06d93085f19dc3ec3db53f22883f1625dd32b96a87e7009de"}, + {file = "python_rapidjson-1.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:23539c9f7d85d64a00d3cb44c7d9ab3be2184d4da42a5f3263dcfd1d0203ee43"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:8bfd484285f3477acef0bb45abd2b80b6252e35a5a53395ce48f0327cbe43c23"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8b0ed643ebaa8ddf3f40422752efe83abda29aa30a9e6866ccd9dd591b5057d0"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:5aeca661a0f229f1312fb3ad3e1a5c6736d49942d80d4931810158559eb8f119"}, + {file = "python_rapidjson-1.10-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8a68ed066e8f0878b7112f943cf35ba9e5217395bcdd8cb478cde01871e2701c"}, + {file = "python_rapidjson-1.10-cp38-cp38-win32.whl", hash = "sha256:d286be6f63446776c4958bb37824c683194b4878fc9cd5b7255134fb5a6ba536"}, + {file = "python_rapidjson-1.10-cp38-cp38-win_amd64.whl", hash = "sha256:aece5270c6e6d5c3d54586c9a5fb9677d70d7019744a59560c5c369c7b9bba25"}, + {file = "python_rapidjson-1.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:dc09c5ad0fe71f262cdcc5655409f132f1560a8af80e76e7757945ce401fdbab"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c9f6447bd7a8ff5135ab7e372b48a174d3c560d5b322e32bd465e8458e6e4593"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:22ede69213885391b46cc14596bfd4cd1a5c6f34a2db6600fb08b03982dbc7b7"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4a9425129623718a04b885a12190faa23e7997c4e8632054e18df7ea473f746d"}, + {file = "python_rapidjson-1.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:60e10f32e1a8d155448842934cbe71eb620b4b4a0cb3627ba4c4856e27556534"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8d23caab17b87ed5b82e28cdc19172ba1ca65c982e3fff387961d3f33710031f"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f07d4fcdcfd64bdad0143b9705c5d5089677ebddf60ac6c1f8074a34b1c70cf9"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:87b991c7ae435489c56a46cef228d2b65a3df689ee4fe24fab69c791c841f633"}, + {file = "python_rapidjson-1.10-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:3706a5c4f21073c04be133c36565efc6e3f5646a03c8d19af78c19d7c70eb708"}, + {file = "python_rapidjson-1.10-cp39-cp39-win32.whl", hash = "sha256:47f9078ea6884f700166a8728d863609fec62232e66a33b8fb4a7706ce7c731c"}, + {file = "python_rapidjson-1.10-cp39-cp39-win_amd64.whl", hash = "sha256:9e4921ab7002ae9faad7f439a7c50aa195039f177e9e51a76c34c97966c79a79"}, +] +python-socketio = [ + {file = "python-socketio-5.7.2.tar.gz", hash = "sha256:92395062d9db3c13d30e7cdedaa0e1330bba78505645db695415f9a3c628d097"}, + {file = "python_socketio-5.7.2-py3-none-any.whl", hash = "sha256:d9a9f047e6fdd306c852fbac36516f4b495c2096f8ad9ceb8803b8e5ff5622e3"}, +] +pytools = [ + {file = "pytools-2022.1.12.tar.gz", hash = "sha256:4d62875e9a2ab2a24e393a9a8b799492f1a721bffa840af3807bfd42871dd1f4"}, +] +pytz = [ + {file = "pytz-2022.5-py2.py3-none-any.whl", hash = "sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22"}, + {file = "pytz-2022.5.tar.gz", hash = "sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914"}, +] +pywavelets = [ + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:d854411eb5ee9cb4bc5d0e66e3634aeb8f594210f6a1bed96dbed57ec70f181c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:231b0e0b1cdc1112f4af3c24eea7bf181c418d37922a67670e9bf6cfa2d544d4"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:754fa5085768227c4f4a26c1e0c78bc509a266d9ebd0eb69a278be7e3ece943c"}, + {file = "PyWavelets-1.4.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:da7b9c006171be1f9ddb12cc6e0d3d703b95f7f43cb5e2c6f5f15d3233fcf202"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win32.whl", hash = "sha256:67a0d28a08909f21400cb09ff62ba94c064882ffd9e3a6b27880a111211d59bd"}, + {file = "PyWavelets-1.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:91d3d393cffa634f0e550d88c0e3f217c96cfb9e32781f2960876f1808d9b45b"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:64c6bac6204327321db30b775060fbe8e8642316e6bff17f06b9f34936f88875"}, + {file = "PyWavelets-1.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3f19327f2129fb7977bc59b966b4974dfd72879c093e44a7287500a7032695de"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ad987748f60418d5f4138db89d82ba0cb49b086e0cbb8fd5c3ed4a814cfb705e"}, + {file = "PyWavelets-1.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:875d4d620eee655346e3589a16a73790cf9f8917abba062234439b594e706784"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win32.whl", hash = "sha256:7231461d7a8eb3bdc7aa2d97d9f67ea5a9f8902522818e7e2ead9c2b3408eeb1"}, + {file = "PyWavelets-1.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:daf0aa79842b571308d7c31a9c43bc99a30b6328e6aea3f50388cd8f69ba7dbc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:ab7da0a17822cd2f6545626946d3b82d1a8e106afc4b50e3387719ba01c7b966"}, + {file = "PyWavelets-1.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:578af438a02a86b70f1975b546f68aaaf38f28fb082a61ceb799816049ed18aa"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb5ca8d11d3f98e89e65796a2125be98424d22e5ada360a0dbabff659fca0fc"}, + {file = "PyWavelets-1.4.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:058b46434eac4c04dd89aeef6fa39e4b6496a951d78c500b6641fd5b2cc2f9f4"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win32.whl", hash = "sha256:de7cd61a88a982edfec01ea755b0740e94766e00a1ceceeafef3ed4c85c605cd"}, + {file = "PyWavelets-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:7ab8d9db0fe549ab2ee0bea61f614e658dd2df419d5b75fba47baa761e95f8f2"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:23bafd60350b2b868076d976bdd92f950b3944f119b4754b1d7ff22b7acbf6c6"}, + {file = "PyWavelets-1.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d0e56cd7a53aed3cceca91a04d62feb3a0aca6725b1912d29546c26f6ea90426"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:030670a213ee8fefa56f6387b0c8e7d970c7f7ad6850dc048bd7c89364771b9b"}, + {file = "PyWavelets-1.4.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:71ab30f51ee4470741bb55fc6b197b4a2b612232e30f6ac069106f0156342356"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win32.whl", hash = "sha256:47cac4fa25bed76a45bc781a293c26ac63e8eaae9eb8f9be961758d22b58649c"}, + {file = "PyWavelets-1.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:88aa5449e109d8f5e7f0adef85f7f73b1ab086102865be64421a3a3d02d277f4"}, + {file = "PyWavelets-1.4.1.tar.gz", hash = "sha256:6437af3ddf083118c26d8f97ab43b0724b956c9f958e9ea788659f6a2834ba93"}, +] +pywin32 = [ + {file = "pywin32-304-cp310-cp310-win32.whl", hash = "sha256:3c7bacf5e24298c86314f03fa20e16558a4e4138fc34615d7de4070c23e65af3"}, + {file = "pywin32-304-cp310-cp310-win_amd64.whl", hash = "sha256:4f32145913a2447736dad62495199a8e280a77a0ca662daa2332acf849f0be48"}, + {file = "pywin32-304-cp310-cp310-win_arm64.whl", hash = "sha256:d3ee45adff48e0551d1aa60d2ec066fec006083b791f5c3527c40cd8aefac71f"}, + {file = "pywin32-304-cp311-cp311-win32.whl", hash = "sha256:30c53d6ce44c12a316a06c153ea74152d3b1342610f1b99d40ba2795e5af0269"}, + {file = "pywin32-304-cp311-cp311-win_amd64.whl", hash = "sha256:7ffa0c0fa4ae4077e8b8aa73800540ef8c24530057768c3ac57c609f99a14fd4"}, + {file = "pywin32-304-cp311-cp311-win_arm64.whl", hash = "sha256:cbbe34dad39bdbaa2889a424d28752f1b4971939b14b1bb48cbf0182a3bcfc43"}, + {file = "pywin32-304-cp36-cp36m-win32.whl", hash = "sha256:be253e7b14bc601718f014d2832e4c18a5b023cbe72db826da63df76b77507a1"}, + {file = "pywin32-304-cp36-cp36m-win_amd64.whl", hash = "sha256:de9827c23321dcf43d2f288f09f3b6d772fee11e809015bdae9e69fe13213988"}, + {file = "pywin32-304-cp37-cp37m-win32.whl", hash = "sha256:f64c0377cf01b61bd5e76c25e1480ca8ab3b73f0c4add50538d332afdf8f69c5"}, + {file = "pywin32-304-cp37-cp37m-win_amd64.whl", hash = "sha256:bb2ea2aa81e96eee6a6b79d87e1d1648d3f8b87f9a64499e0b92b30d141e76df"}, + {file = "pywin32-304-cp38-cp38-win32.whl", hash = "sha256:94037b5259701988954931333aafd39cf897e990852115656b014ce72e052e96"}, + {file = "pywin32-304-cp38-cp38-win_amd64.whl", hash = "sha256:ead865a2e179b30fb717831f73cf4373401fc62fbc3455a0889a7ddac848f83e"}, + {file = "pywin32-304-cp39-cp39-win32.whl", hash = "sha256:25746d841201fd9f96b648a248f731c1dec851c9a08b8e33da8b56148e4c65cc"}, + {file = "pywin32-304-cp39-cp39-win_amd64.whl", hash = "sha256:d24a3382f013b21aa24a5cfbfad5a2cd9926610c0affde3e8ab5b3d7dbcf4ac9"}, +] +pywin32-ctypes = [ + {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, + {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, +] +pywinpty = [ + {file = "pywinpty-2.0.8-cp310-none-win_amd64.whl", hash = "sha256:9cbf89834abc8d4d4c5f295f11e15dd93889a8069db876f2bc10cc64aa4060ac"}, + {file = "pywinpty-2.0.8-cp37-none-win_amd64.whl", hash = "sha256:a2f9a95f3b74262ef73f1be5257c295c8caab1f79f081aa3400ca61c724f9bc6"}, + {file = "pywinpty-2.0.8-cp38-none-win_amd64.whl", hash = "sha256:23389d56258d6a1fbc4b41257bd65e5bdabaf6fde7f30a13806e557ea9ee6865"}, + {file = "pywinpty-2.0.8-cp39-none-win_amd64.whl", hash = "sha256:ea7c1da94eed5ef93e75026c67c60d4dca33ea9a1c212fa89221079a7b463c68"}, + {file = "pywinpty-2.0.8.tar.gz", hash = "sha256:a89b9021c63ef78b1e7d8e14f0fac4748c88a0c2e4f529c84f37f6e72b914280"}, +] +pyyaml = [ + {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, + {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, + {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, + {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, + {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, + {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, + {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, + {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, + {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, + {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, + {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, + {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, + {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, + {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, + {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, + {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, + {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, + {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, + {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, + {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, + {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, + {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, + {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, + {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, + {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, + {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, +] +pyzmq = [ + {file = "pyzmq-23.2.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:a3fd44b5046d247e7f0f1660bcafe7b5fb0db55d0934c05dd57dda9e1f823ce7"}, + {file = "pyzmq-23.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2141e6798d5981be04c08996d27962086a1aa3ea536fe9cf7e89817fd4523f86"}, + {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9a39ddb0431a68954bd318b923230fa5b649c9c62b0e8340388820c5f1b15bd2"}, + {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e06747014a5ad1b28cebf5bc1ddcdaccfb44e9b441d35e6feb1286c8a72e54be"}, + {file = "pyzmq-23.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e0113d70b095339e99bb522fe7294f5ae6a7f3b2b8f52f659469a74b5cc7661"}, + {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:71b32a1e827bdcbf73750e60370d3b07685816ff3d8695f450f0f8c3226503f8"}, + {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:55568a020ad2cae9ae36da6058e7ca332a56df968f601cbdb7cf6efb2a77579a"}, + {file = "pyzmq-23.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8c02a0cd39dc01659b3d6cb70bb3a41aebd9885fd78239acdd8d9c91351c4568"}, + {file = "pyzmq-23.2.1-cp310-cp310-win32.whl", hash = "sha256:e1fe30bcd5aea5948c42685fad910cd285eacb2518ea4dc6c170d6b535bee95d"}, + {file = "pyzmq-23.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:650389bbfca73955b262b2230423d89992f38ec48033307ae80e700eaa2fbb63"}, + {file = "pyzmq-23.2.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:e753eee6d3b93c5354e8ba0a1d62956ee49355f0a36e00570823ef64e66183f5"}, + {file = "pyzmq-23.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f07016e3cf088dbfc6e7c5a7b3f540db5c23b0190d539e4fd3e2b5e6beffa4b5"}, + {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4805af9614b0b41b7e57d17673459facf85604dac502a5a9244f6e8c9a4de658"}, + {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:39dd252b683816935702825e5bf775df16090619ced9bb4ba68c2d0b6f0c9b18"}, + {file = "pyzmq-23.2.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:84678153432241bcdca2210cf4ff83560b200556867aea913ffbb960f5d5f340"}, + {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:90d88f9d9a2ae6cfb1dc4ea2d1710cdf6456bc1b9a06dd1bb485c5d298f2517e"}, + {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:794871988c34727c7f79bdfe2546e6854ae1fa2e1feb382784f23a9c6c63ecb3"}, + {file = "pyzmq-23.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c56b1a62a1fb87565343c57b6743fd5da6e138b8c6562361d7d9b5ce4acf399a"}, + {file = "pyzmq-23.2.1-cp311-cp311-win32.whl", hash = "sha256:c3ebf1668664d20c8f7d468955f18379b7d1f7bc8946b13243d050fa3888c7ff"}, + {file = "pyzmq-23.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:ec9803aca9491fd6f0d853d2a6147f19f8deaaa23b1b713d05c5d09e56ea7142"}, + {file = "pyzmq-23.2.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:385609812eafd9970c3752c51f2f6c4f224807e3e441bcfd8c8273877d00c8a8"}, + {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b861db65f6b8906c8d6db51dde2448f266f0c66bf28db2c37aea50f58a849859"}, + {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6b1e79bba24f6df1712e3188d5c32c480d8eda03e8ecff44dc8ecb0805fa62f3"}, + {file = "pyzmq-23.2.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:8dc66f109a245653b19df0f44a5af7a3f14cb8ad6c780ead506158a057bd36ce"}, + {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:b815991c7d024bf461f358ad871f2be1135576274caed5749c4828859e40354e"}, + {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:29b74774a0bfd3c4d98ac853f0bdca55bd9ec89d5b0def5486407cca54472ef8"}, + {file = "pyzmq-23.2.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:4bb798bef181648827019001f6be43e1c48b34b477763b37a8d27d8c06d197b8"}, + {file = "pyzmq-23.2.1-cp36-cp36m-win32.whl", hash = "sha256:565bd5ab81f6964fc4067ccf2e00877ad0fa917308975694bbb54378389215f8"}, + {file = "pyzmq-23.2.1-cp36-cp36m-win_amd64.whl", hash = "sha256:1f368a82b29f80071781b20663c0fc0c8f6b13273f9f5abe1526af939534f90f"}, + {file = "pyzmq-23.2.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c9cfaf530e6a7ff65f0afe275e99f983f68b54dfb23ea401f0bc297a632766b6"}, + {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c558b50402fca1acc94329c5d8f12aa429738904a5cfb32b9ed3c61235221bb"}, + {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:20bafc4095eab00f41a510579363a3f5e1f5c69d7ee10f1d88895c4df0259183"}, + {file = "pyzmq-23.2.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:f619fd38fc2641abfb53cca719c165182500600b82c695cc548a0f05f764be05"}, + {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:044447ae4b2016a6b8697571fd633f799f860b19b76c4a2fd9b1140d52ee6745"}, + {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:49d30ba7074f469e8167917abf9eb854c6503ae10153034a6d4df33618f1db5f"}, + {file = "pyzmq-23.2.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:48400b96788cdaca647021bf19a9cd668384f46e4d9c55cf045bdd17f65299c8"}, + {file = "pyzmq-23.2.1-cp37-cp37m-win32.whl", hash = "sha256:8a68f57b7a3f7b6b52ada79876be1efb97c8c0952423436e84d70cc139f16f0d"}, + {file = "pyzmq-23.2.1-cp37-cp37m-win_amd64.whl", hash = "sha256:9e5bf6e7239fc9687239de7a283aa8b801ab85371116045b33ae20132a1325d6"}, + {file = "pyzmq-23.2.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:0ff6294e001129a9f22dcbfba186165c7e6f573c46de2704d76f873c94c65416"}, + {file = "pyzmq-23.2.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:ffc6b1623d0f9affb351db4ca61f432dca3628a5ee015f9bf2bfbe9c6836881c"}, + {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:4d6f110c56f7d5b4d64dde3a382ae61b6d48174e30742859d8e971b18b6c9e5c"}, + {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:9269fbfe3a4eb2009199120861c4571ef1655fdf6951c3e7f233567c94e8c602"}, + {file = "pyzmq-23.2.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:12e62ff0d5223ec09b597ab6d73858b9f64a51221399f3cb08aa495e1dff7935"}, + {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6fd5d0d50cbcf4bc376861529a907bed026a4cbe8c22a500ff8243231ef02433"}, + {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9d0ab2936085c85a1fc6f9fd8f89d5235ae99b051e90ec5baa5e73ad44346e1f"}, + {file = "pyzmq-23.2.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:022cf5ea7bcaa8a06a03c2706e0ae66904b6138b2155577cd34c64bc7cc637ab"}, + {file = "pyzmq-23.2.1-cp38-cp38-win32.whl", hash = "sha256:28dbdb90b2f6b131f8f10e6081012e4e25234213433420e67e0c1162de537113"}, + {file = "pyzmq-23.2.1-cp38-cp38-win_amd64.whl", hash = "sha256:10d1910ec381b851aeb024a042a13db178cb1edf125e76a4e9d2548ad103aadb"}, + {file = "pyzmq-23.2.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:99a5a77a10863493a1ee8dece02578c6b32025fb3afff91b40476bc489e81648"}, + {file = "pyzmq-23.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:aecd6ceaccc4b594e0092d6513ef3f1c0fa678dd89f86bb8ff1a47014b8fca35"}, + {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:415ff62ac525d9add1e3550430a09b9928d2d24a20cc4ce809e67caac41219ab"}, + {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:67975a9e1237b9ccc78f457bef17691bbdd2055a9d26e81ee914ba376846d0ce"}, + {file = "pyzmq-23.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e106b64bad744fe469dc3dd864f2764d66399178c1bf39d45294cc7980f14f"}, + {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:8c842109d31a9281d678f668629241c405928afbebd913c48a5a8e7aee61f63d"}, + {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fefdf9b685fda4141b95ebec975946076a5e0723ff70b037032b2085c5317684"}, + {file = "pyzmq-23.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:79a87831b47a9f6161ad23fa5e89d5469dc585abc49f90b9b07fea8905ae1234"}, + {file = "pyzmq-23.2.1-cp39-cp39-win32.whl", hash = "sha256:342ca3077f47ec2ee41b9825142b614e03e026347167cbc72a59b618c4f6106c"}, + {file = "pyzmq-23.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:5e05492be125dce279721d6b54fd1b956546ecc4bcdfcf8e7b4c413bc0874c10"}, + {file = "pyzmq-23.2.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:07ed8aaf7ffe150af873269690cc654ffeca7491f62aae0f3821baa181f8d5fe"}, + {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad28ddb40db8e450d7d4bf8a1d765d3f87b63b10e7e9a825a3c130c6371a8c03"}, + {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2f67b63f53c6994d601404fd1a329e6d940ac3dd1d92946a93b2b9c70df67b9f"}, + {file = "pyzmq-23.2.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c890309296f53f9aa32ffcfc51d805705e1982bffd27c9692a8f1e1b8de279f4"}, + {file = "pyzmq-23.2.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:624fd38071a817644acdae075b92a23ea0bdd126a58148288e8284d23ec361ce"}, + {file = "pyzmq-23.2.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a114992a193577cb62233abf8cb2832970f9975805a64740e325d2f895e7f85a"}, + {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c780acddd2934c6831ff832ecbf78a45a7b62d4eb216480f863854a8b7d54fa7"}, + {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d904f6595acfaaf99a1a61881fea068500c40374d263e5e073aa4005e5f9c28a"}, + {file = "pyzmq-23.2.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:929d548b74c0f82f7f95b54e4a43f9e4ce2523cfb8a54d3f7141e45652304b2a"}, + {file = "pyzmq-23.2.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:f392cbea531b7142d1958c0d4a0c9c8d760dc451e5848d8dd3387804d3e3e62c"}, + {file = "pyzmq-23.2.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a0f09d85c45f58aa8e715b42f8b26beba68b3b63a8f7049113478aca26efbc30"}, + {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:23e708fbfdf4ee3107422b69ca65da1b9f056b431fc0888096a8c1d6cd908e8f"}, + {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:35e635343ff367f697d00fa1484262bb68e36bc74c9b80737eac5a1e04c4e1b1"}, + {file = "pyzmq-23.2.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:efb9e38b2a590282704269585de7eb33bf43dc294cad092e1b172e23d4c217e5"}, + {file = "pyzmq-23.2.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:407f909c4e8fde62fbdad9ebd448319792258cc0550c2815567a4d9d8d9e6d18"}, + {file = "pyzmq-23.2.1.tar.gz", hash = "sha256:2b381aa867ece7d0a82f30a0c7f3d4387b7cf2e0697e33efaa5bed6c5784abcd"}, +] +qtconsole = [ + {file = "qtconsole-5.3.2-py3-none-any.whl", hash = "sha256:c29d24464f57cdbaa17d6f6060be6e6d5e29126e7feb57eebc1747433382b3d1"}, + {file = "qtconsole-5.3.2.tar.gz", hash = "sha256:8eadf012e83ab018295803c247c6ab7eacd3d5ab1e1d88a0f37fdcfdab9295a3"}, +] +qtpy = [ + {file = "QtPy-2.2.1-py3-none-any.whl", hash = "sha256:268cf5328f41353be1b127e04a81bc74ec9a9b54c9ac75dd8fe0ff48d8ad6ead"}, + {file = "QtPy-2.2.1.tar.gz", hash = "sha256:7d5231133b772e40b4ee514b6673aca558331e4b88ca038b26c9e16c5c95524f"}, +] +qudida = [ + {file = "qudida-0.0.4-py3-none-any.whl", hash = "sha256:4519714c40cd0f2e6c51e1735edae8f8b19f4efe1f33be13e9d644ca5f736dd6"}, + {file = "qudida-0.0.4.tar.gz", hash = "sha256:db198e2887ab0c9aa0023e565afbff41dfb76b361f85fd5e13f780d75ba18cc8"}, +] +reactivex = [ + {file = "reactivex-4.0.4-py3-none-any.whl", hash = "sha256:0004796c420bd9e68aad8e65627d85a8e13f293de76656165dffbcb3a0e3fb6a"}, + {file = "reactivex-4.0.4.tar.gz", hash = "sha256:e912e6591022ab9176df8348a653fe8c8fa7a301f26f9931c9d8c78a650e04e8"}, +] +redis = [ + {file = "redis-4.3.4-py3-none-any.whl", hash = "sha256:a52d5694c9eb4292770084fa8c863f79367ca19884b329ab574d5cb2036b3e54"}, + {file = "redis-4.3.4.tar.gz", hash = "sha256:ddf27071df4adf3821c4f2ca59d67525c3a82e5f268bed97b813cb4fabf87880"}, +] +requests = [ + {file = "requests-2.28.1-py3-none-any.whl", hash = "sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"}, + {file = "requests-2.28.1.tar.gz", hash = "sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983"}, +] +requests-oauthlib = [ + {file = "requests-oauthlib-1.3.1.tar.gz", hash = "sha256:75beac4a47881eeb94d5ea5d6ad31ef88856affe2332b9aafb52c6452ccf0d7a"}, + {file = "requests_oauthlib-1.3.1-py2.py3-none-any.whl", hash = "sha256:2577c501a2fb8d05a304c09d090d6e47c306fef15809d102b327cf8364bddab5"}, +] +requests-toolbelt = [ + {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, + {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, +] +reverse-geocoder = [ + {file = "reverse_geocoder-1.5.1.tar.gz", hash = "sha256:2a2e781b5f69376d922b78fe8978f1350c84fce0ddb07e02c834ecf98b57c75c"}, +] +rtree = [ + {file = "Rtree-1.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9855b8f11cdad99c56eb361b7b632a4fbd3d8cbe3f2081426b445f0cfb7fdca9"}, + {file = "Rtree-1.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:18ce7e4d04b85c48f2d364835620b3b20e38e199639746e7b12f07a2303e18ff"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:784efa6b7be9e99b33613ae8495931032689441eabb6120c9b3eb91188c33794"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:157207191aebdacbbdbb369e698cfbfebce53bc97114e96c8af5bed3126475f1"}, + {file = "Rtree-1.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c5fb3671a8d440c24b1dd29ec621d4345ced7185e26f02abe98e85a6629fcb50"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:11d16f51cf9205cd6995af36e24efe8f184270f667fb49bb69b09fc46b97e7d4"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6db6a0a93e41594ffc14b053f386dd414ab5a82535bbd9aedafa6ac8dc0650d8"}, + {file = "Rtree-1.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c6e29e5eb3083ad12ac5c1ce6e37465ea3428d894d3466cc9c9e2ee4bf768e53"}, + {file = "Rtree-1.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:656b148589c0b5bab4a7db4d033634329f42a5feaac10ca40aceeca109d83c1f"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:7b2c15f9373ba314c83a8df5cb6d99b4e3af23c376c6b1317add995432dd0970"}, + {file = "Rtree-1.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93c5e0bf31e76b4f92a6eec3d2891e938408774c75a8ed6ac3d2c8db04a2be33"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6792de0e3c2fd3ad7e069445027603bec7a47000432f49c80246886311f4f152"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:004e131b570dc360a49e7f3b60e7bc6517943a54df056587964d1cb903889e7e"}, + {file = "Rtree-1.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:becd711fe97c2e09b1b7969e83080a3c8012bce2d30f6db879aade255fcba5c1"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:015df09e1bc55ddf7c88799bf1515d058cd0ee78eacf4cd443a32876d3b3a863"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c2973b76f61669a85e160b4ad09879c4089fc0e3f20fd99adf161ca298fe8374"}, + {file = "Rtree-1.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e4335e131a58952635560a003458011d97f9ea6f3c010dc24906050b42ee2c03"}, + {file = "Rtree-1.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:e7ca5d743f6a1dc62653dfac8ee7ce2e1ba91be7cf97916a7f60b7cbe48fb48d"}, + {file = "Rtree-1.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2ee7165e9872a026ccb868c021711eba39cedf7d1820763c9de52d5324691a92"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8de99f28af0f1783eefb80918959903b4b18112f6a12b48f296ecb162804e69d"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1a94e2f4bf74bd202ea8b67ea3d7c71e763ad41f79be1d6b72aa2c8d5a8e92c4"}, + {file = "Rtree-1.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5120da3a1b96f3a7a17dd6af0afdd4e6f3cc9baa87e9ee0a272882f01f980bb"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7e3d5f0e7b28250afbb290ab88b49aa0f121c9714d0da2080581783690347507"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:296203e933b6ec0dd07f6a7456c4f1492def95b6993f20cc61c92b0fee0aecc5"}, + {file = "Rtree-1.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:77908cd7acdd519a731979ebf5baff8afd102109c2f52864c1e6ee75d3ea2d87"}, + {file = "Rtree-1.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:1a213e5d385278ca7668bc5b27083f8d6e39996a9bd59b6528f3a30009dae4ed"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cfa8cffec5cb9fed494c4bb335ebdb69b3c26178b0b685f67f79296c6b3d800c"}, + {file = "Rtree-1.0.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b31fd22d214160859d038da7cb2aaa27acb71efc24a7bcc75c84b5e502721549"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d68a81ad419d5c2ea5fecc677e6c178666c057e2c7b24100a6c48392196f1e9"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62f38020af47b765adc6b0bc7c4e810c6c3d1eab44ba339b592ff25a4c0dc0a7"}, + {file = "Rtree-1.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50b658a6707f215a0056d52e9f83a97148c0af62dea07cf29b3789a2c429e78a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:3573cbb0de872f54d0a0c29596a84e8ac3939c47ca3bece4a82e92775730a0d0"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:d5abe5a19d943a88bea14901970e4c53e4579fc2662404cdea6163bf4c04d49a"}, + {file = "Rtree-1.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1e894112cef4de6c518bdea0b43eada65f12888c3645cc437c3a677aa023039f"}, + {file = "Rtree-1.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:582854252b8fd5c8472478af060635434931fb55edd269bac128cbf2eef43620"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b54057e8a8ad92c1d8e9eaa5cf32aad70dde454abbf9b638e9d6024520a52c02"}, + {file = "Rtree-1.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:698de8ce6c62e159d93b35bacf64bcf3619077b5367bc88cd2cff5e0bc36169b"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:273ee61783de3a1664e5f868feebf5eea4629447137751bfa4087b0f82093082"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:16900ee02cf5c198a42b03635268a80f606aa102f3f7618b89f75023d406da1c"}, + {file = "Rtree-1.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ce4a6fdb63254a4c1efebe7a4f7a59b1c333c703bde4ae715d9ad88c833e10b"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5b20f69e040a05503b22297af223f336fe7047909b57e4b207b98292f33a229f"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:57128293dd625cb1f07726f32208097953e8854d70ab1fc55d6858733618b9ed"}, + {file = "Rtree-1.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e898d7409ab645c25e06d4e058f99271182601d70b2887aba3351bf08e09a0c6"}, + {file = "Rtree-1.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:ad9912faeddb1ddcec5e26b33089166d58a107af6862d8b7f1bb2b7c0002ab39"}, + {file = "Rtree-1.0.1.tar.gz", hash = "sha256:222121699c303a64065d849bf7038b1ecabc37b65c7fa340bedb38ef0e805429"}, +] +s2sphere = [ + {file = "s2sphere-0.2.5-py2.py3-none-any.whl", hash = "sha256:d2340c9cf458ddc9a89afd1d8048a4195ce6fa6b0095ab900d4be5271e537401"}, + {file = "s2sphere-0.2.5.tar.gz", hash = "sha256:c2478c1ff7c601a59a7151a57b605435897514578fa6bdb8730721c182adbbaf"}, +] +scikit-image = [ + {file = "scikit-image-0.19.3.tar.gz", hash = "sha256:24b5367de1762da6ee126dd8f30cc4e7efda474e0d7d70685433f0e3aa2ec450"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:3a01372ae4bca223873304b0bff79b9d92446ac6d6177f73d89b45561e2d09d8"}, + {file = "scikit_image-0.19.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:fdf48d9b1f13af69e4e2c78e05067e322e9c8c97463c315cd0ecb47a94e259fc"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b6a8f98f2ac9bb73706461fd1dec875f6a5141759ed526850a5a49e90003d19"}, + {file = "scikit_image-0.19.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cfbb073f23deb48e0e60c47f8741d8089121d89cc78629ea8c5b51096efc5be7"}, + {file = "scikit_image-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:cc24177de3fdceca5d04807ad9c87d665f0bf01032ed94a9055cd1ed2b3f33e9"}, + {file = "scikit_image-0.19.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:fd9dd3994bb6f9f7a35f228323f3c4dc44b3cf2ff15fd72d895216e9333550c6"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:ad5d8000207a264d1a55681a9276e6a739d3f05cf4429004ad00d61d1892235f"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84baa3179f3ae983c3a5d81c1e404bc92dcf7daeb41bfe9369badcda3fb22b92"}, + {file = "scikit_image-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7f9f8a1387afc6c70f2bed007c3854a2d7489f9f7713c242f16f32ee05934bc2"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:9fb0923a3bfa99457c5e17888f27b3b8a83a3600b4fef317992e7b7234764732"}, + {file = "scikit_image-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:ce3d2207f253b8eb2c824e30d145a9f07a34a14212d57f3beca9f7e03c383cbe"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:2a02d1bd0e2b53e36b952bd5fd6118d9ccc3ee51de35705d63d8eb1f2e86adef"}, + {file = "scikit_image-0.19.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:03779a7e1736fdf89d83c0ba67d44110496edd736a3bfce61a2b5177a1c8a099"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:19a21a101a20c587a3b611a2cf6f86c35aae9f8d9563279b987e83ee1c9a9790"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f50b923f8099c1045fcde7418d86b206c87e333e43da980f41d8577b9605245"}, + {file = "scikit_image-0.19.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e207c6ce5ce121d7d9b9d2b61b9adca57d1abed112c902d8ffbfdc20fb42c12b"}, + {file = "scikit_image-0.19.3-cp38-cp38-win32.whl", hash = "sha256:a7c3985c68bfe05f7571167ee021d14f5b8d1a4a250c91f0b13be7fb07e6af34"}, + {file = "scikit_image-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:651de1c2ce1fbee834753b46b8e7d81cb12a5594898babba63ac82b30ddad49d"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_10_13_x86_64.whl", hash = "sha256:8d8917fcf85b987b1f287f823f3a1a7dac38b70aaca759bc0200f3bc292d5ced"}, + {file = "scikit_image-0.19.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:0b0a199157ce8487c77de4fde0edc0b42d6d42818881c11f459262351d678b2d"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:33dfd463ee6cc509defa279b963829f2230c9e0639ccd3931045be055878eea6"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a8714348ddd671f819457a797c97d4c672166f093def66d66c3254cbd1d43f83"}, + {file = "scikit_image-0.19.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff3b1025356508d41f4fe48528e509d95f9e4015e90cf158cd58c56dc63e0ac5"}, + {file = "scikit_image-0.19.3-cp39-cp39-win32.whl", hash = "sha256:9439e5294de3f18d6e82ec8eee2c46590231cf9c690da80545e83a0733b7a69e"}, + {file = "scikit_image-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:32fb88cc36203b99c9672fb972c9ef98635deaa5fc889fe969f3e11c44f22919"}, +] +scikit-learn = [ + {file = "scikit-learn-1.1.2.tar.gz", hash = "sha256:7c22d1305b16f08d57751a4ea36071e2215efb4c09cb79183faa4e8e82a3dbf8"}, + {file = "scikit_learn-1.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6c840f662b5d3377c4ccb8be1fc21bb52cb5d8b8790f8d6bf021739f84e543cf"}, + {file = "scikit_learn-1.1.2-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:2b8db962360c93554cab7bb3c096c4a24695da394dd4b3c3f13409f409b425bc"}, + {file = "scikit_learn-1.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e7d1fc817867a350133f937aaebcafbc06192517cbdf0cf7e5774ad4d1adb9f"}, + {file = "scikit_learn-1.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ec3ea40d467966821843210c02117d82b097b54276fdcfb50f4dfb5c60dbe39"}, + {file = "scikit_learn-1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:bbef6ea1c012ff9f3e6f6e9ca006b8772d8383e177b898091e68fbd9b3f840f9"}, + {file = "scikit_learn-1.1.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a90ca42fe8242fd6ff56cda2fecc5fca586a88a24ab602d275d2d0dcc0b928fb"}, + {file = "scikit_learn-1.1.2-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a682ec0f82b6f30fb07486daed1c8001b6683cc66b51877644dfc532bece6a18"}, + {file = "scikit_learn-1.1.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c33e16e9a165af6012f5be530ccfbb672e2bc5f9b840238a05eb7f6694304e3f"}, + {file = "scikit_learn-1.1.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f94c0146bad51daef919c402a3da8c1c6162619653e1c00c92baa168fda292f2"}, + {file = "scikit_learn-1.1.2-cp38-cp38-win32.whl", hash = "sha256:2f46c6e3ff1054a5ec701646dcfd61d43b8ecac4d416014daed8843cf4c33d4d"}, + {file = "scikit_learn-1.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:b1e706deca9b2ad87ae27dafd5ac4e8eff01b6db492ed5c12cef4735ec5f21ea"}, + {file = "scikit_learn-1.1.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:567417dbbe6a6278399c3e6daf1654414a5a1a4d818d28f251fa7fc28730a1bf"}, + {file = "scikit_learn-1.1.2-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:d6f232779023c3b060b80b5c82e5823723bc424dcac1d1a148aa2492c54d245d"}, + {file = "scikit_learn-1.1.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:589d46f28460469f444b898223b13d99db9463e1038dc581ba698111f612264b"}, + {file = "scikit_learn-1.1.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76800652fb6d6bf527bce36ecc2cc25738b28fe1a17bd294a218fff8e8bd6d50"}, + {file = "scikit_learn-1.1.2-cp39-cp39-win32.whl", hash = "sha256:1c8fecb7c9984d9ec2ea48898229f98aad681a0873e0935f2b7f724fbce4a047"}, + {file = "scikit_learn-1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:407e9a1cb9e6ba458a539986a9bd25546a757088095b3aab91d465b79a760d37"}, +] +scipy = [ + {file = "scipy-1.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1884b66a54887e21addf9c16fb588720a8309a57b2e258ae1c7986d4444d3bc0"}, + {file = "scipy-1.9.3-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:83b89e9586c62e787f5012e8475fbb12185bafb996a03257e9675cd73d3736dd"}, + {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a72d885fa44247f92743fc20732ae55564ff2a519e8302fb7e18717c5355a8b"}, + {file = "scipy-1.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01e1dd7b15bd2449c8bfc6b7cc67d630700ed655654f0dfcf121600bad205c9"}, + {file = "scipy-1.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:68239b6aa6f9c593da8be1509a05cb7f9efe98b80f43a5861cd24c7557e98523"}, + {file = "scipy-1.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b41bc822679ad1c9a5f023bc93f6d0543129ca0f37c1ce294dd9d386f0a21096"}, + {file = "scipy-1.9.3-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:90453d2b93ea82a9f434e4e1cba043e779ff67b92f7a0e85d05d286a3625df3c"}, + {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83c06e62a390a9167da60bedd4575a14c1f58ca9dfde59830fc42e5197283dab"}, + {file = "scipy-1.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:abaf921531b5aeaafced90157db505e10345e45038c39e5d9b6c7922d68085cb"}, + {file = "scipy-1.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:06d2e1b4c491dc7d8eacea139a1b0b295f74e1a1a0f704c375028f8320d16e31"}, + {file = "scipy-1.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:5a04cd7d0d3eff6ea4719371cbc44df31411862b9646db617c99718ff68d4840"}, + {file = "scipy-1.9.3-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:545c83ffb518094d8c9d83cce216c0c32f8c04aaf28b92cc8283eda0685162d5"}, + {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0d54222d7a3ba6022fdf5773931b5d7c56efe41ede7f7128c7b1637700409108"}, + {file = "scipy-1.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cff3a5295234037e39500d35316a4c5794739433528310e117b8a9a0c76d20fc"}, + {file = "scipy-1.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:2318bef588acc7a574f5bfdff9c172d0b1bf2c8143d9582e05f878e580a3781e"}, + {file = "scipy-1.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d644a64e174c16cb4b2e41dfea6af722053e83d066da7343f333a54dae9bc31c"}, + {file = "scipy-1.9.3-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:da8245491d73ed0a994ed9c2e380fd058ce2fa8a18da204681f2fe1f57f98f95"}, + {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4db5b30849606a95dcf519763dd3ab6fe9bd91df49eba517359e450a7d80ce2e"}, + {file = "scipy-1.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c68db6b290cbd4049012990d7fe71a2abd9ffbe82c0056ebe0f01df8be5436b0"}, + {file = "scipy-1.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:5b88e6d91ad9d59478fafe92a7c757d00c59e3bdc3331be8ada76a4f8d683f58"}, + {file = "scipy-1.9.3.tar.gz", hash = "sha256:fbc5c05c85c1a02be77b1ff591087c83bc44579c6d2bd9fb798bb64ea5e1a027"}, +] +scons = [ + {file = "SCons-4.4.0-py3-none-any.whl", hash = "sha256:cbbd73b83cf85f1aaaf986370359de1bbfd3af97a765cb3554734f6dcec734e1"}, + {file = "SCons-4.4.0.tar.gz", hash = "sha256:7703c4e9d2200b4854a31800c1dbd4587e1fa86e75f58795c740bcfa7eca7eaa"}, +] +secretstorage = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] +segmentation-models-pytorch = [ + {file = "segmentation_models_pytorch-0.2.1-py3-none-any.whl", hash = "sha256:98822571470867fb0f416c112c32f7f1d21702dd32195ec8f7736932c2de0486"}, + {file = "segmentation_models_pytorch-0.2.1.tar.gz", hash = "sha256:86744552b04c6bedf7e10f7928791894d8d9b399b9ed58ed1a3236d2bf69ead6"}, +] +send2trash = [ + {file = "Send2Trash-1.8.0-py3-none-any.whl", hash = "sha256:f20eaadfdb517eaca5ce077640cb261c7d2698385a6a0f072a4a5447fd49fa08"}, + {file = "Send2Trash-1.8.0.tar.gz", hash = "sha256:d2c24762fd3759860a0aff155e45871447ea58d2be6bdd39b5c8f966a0c99c2d"}, +] +sentry-sdk = [ + {file = "sentry-sdk-1.10.0.tar.gz", hash = "sha256:1b965bcdbfe52321bb1307c7c93c74035afdbfceb5f585f01a963327c5befc4e"}, + {file = "sentry_sdk-1.10.0-py2.py3-none-any.whl", hash = "sha256:8c648e96e0e2ec5e17ca75a28c442e2f523453fa7cf761ec093f4a656153490e"}, +] +setproctitle = [ + {file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:288943dec88e178bb2fd868adf491197cc0fc8b6810416b1c6775e686bab87fe"}, + {file = "setproctitle-1.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:630f6fe5e24a619ccf970c78e084319ee8be5be253ecc9b5b216b0f474f5ef18"}, + {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c877691b90026670e5a70adfbcc735460a9f4c274d35ec5e8a43ce3f8443005"}, + {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7a55fe05f15c10e8c705038777656fe45e3bd676d49ad9ac8370b75c66dd7cd7"}, + {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ab45146c71ca6592c9cc8b354a2cc9cc4843c33efcbe1d245d7d37ce9696552d"}, + {file = "setproctitle-1.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e00c9d5c541a2713ba0e657e0303bf96ddddc412ef4761676adc35df35d7c246"}, + {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:265ecbe2c6eafe82e104f994ddd7c811520acdd0647b73f65c24f51374cf9494"}, + {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c2c46200656280a064073447ebd363937562debef329482fd7e570c8d498f806"}, + {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:fa2f50678f04fda7a75d0fe5dd02bbdd3b13cbe6ed4cf626e4472a7ccf47ae94"}, + {file = "setproctitle-1.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f2719a398e1a2c01c2a63bf30377a34d0b6ef61946ab9cf4d550733af8f1ef1"}, + {file = "setproctitle-1.3.2-cp310-cp310-win32.whl", hash = "sha256:e425be62524dc0c593985da794ee73eb8a17abb10fe692ee43bb39e201d7a099"}, + {file = "setproctitle-1.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:e85e50b9c67854f89635a86247412f3ad66b132a4d8534ac017547197c88f27d"}, + {file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2a97d51c17d438cf5be284775a322d57b7ca9505bb7e118c28b1824ecaf8aeaa"}, + {file = "setproctitle-1.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:587c7d6780109fbd8a627758063d08ab0421377c0853780e5c356873cdf0f077"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7d17c8bd073cbf8d141993db45145a70b307385b69171d6b54bcf23e5d644de"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e932089c35a396dc31a5a1fc49889dd559548d14cb2237adae260382a090382e"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e4f8f12258a8739c565292a551c3db62cca4ed4f6b6126664e2381acb4931bf"}, + {file = "setproctitle-1.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:570d255fd99c7f14d8f91363c3ea96bd54f8742275796bca67e1414aeca7d8c3"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a8e0881568c5e6beff91ef73c0ec8ac2a9d3ecc9edd6bd83c31ca34f770910c4"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4bba3be4c1fabf170595b71f3af46c6d482fbe7d9e0563999b49999a31876f77"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:37ece938110cab2bb3957e3910af8152ca15f2b6efdf4f2612e3f6b7e5459b80"}, + {file = "setproctitle-1.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:db684d6bbb735a80bcbc3737856385b55d53f8a44ce9b46e9a5682c5133a9bf7"}, + {file = "setproctitle-1.3.2-cp311-cp311-win32.whl", hash = "sha256:ca58cd260ea02759238d994cfae844fc8b1e206c684beb8f38877dcab8451dfc"}, + {file = "setproctitle-1.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:88486e6cce2a18a033013d17b30a594f1c5cb42520c49c19e6ade40b864bb7ff"}, + {file = "setproctitle-1.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:92c626edc66169a1b09e9541b9c0c9f10488447d8a2b1d87c8f0672e771bc927"}, + {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:710e16fa3bade3b026907e4a5e841124983620046166f355bbb84be364bf2a02"}, + {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1f29b75e86260b0ab59adb12661ef9f113d2f93a59951373eb6d68a852b13e83"}, + {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c8d9650154afaa86a44ff195b7b10d683c73509d085339d174e394a22cccbb9"}, + {file = "setproctitle-1.3.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f0452282258dfcc01697026a8841258dd2057c4438b43914b611bccbcd048f10"}, + {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:e49ae693306d7624015f31cb3e82708916759d592c2e5f72a35c8f4cc8aef258"}, + {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1ff863a20d1ff6ba2c24e22436a3daa3cd80be1dfb26891aae73f61b54b04aca"}, + {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:55ce1e9925ce1765865442ede9dca0ba9bde10593fcd570b1f0fa25d3ec6b31c"}, + {file = "setproctitle-1.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7fe9df7aeb8c64db6c34fc3b13271a363475d77bc157d3f00275a53910cb1989"}, + {file = "setproctitle-1.3.2-cp37-cp37m-win32.whl", hash = "sha256:e5c50e164cd2459bc5137c15288a9ef57160fd5cbf293265ea3c45efe7870865"}, + {file = "setproctitle-1.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:a499fff50387c1520c085a07578a000123f519e5f3eee61dd68e1d301659651f"}, + {file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5b932c3041aa924163f4aab970c2f0e6b4d9d773f4d50326e0ea1cd69240e5c5"}, + {file = "setproctitle-1.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f4bfc89bd33ebb8e4c0e9846a09b1f5a4a86f5cb7a317e75cc42fee1131b4f4f"}, + {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd3cf4286a60fdc95451d8d14e0389a6b4f5cebe02c7f2609325eb016535963"}, + {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5fb4f769c02f63fac90989711a3fee83919f47ae9afd4758ced5d86596318c65"}, + {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5194b4969f82ea842a4f6af2f82cd16ebdc3f1771fb2771796e6add9835c1973"}, + {file = "setproctitle-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f0cde41857a644b7353a0060b5f94f7ba7cf593ebde5a1094da1be581ac9a31"}, + {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9124bedd8006b0e04d4e8a71a0945da9b67e7a4ab88fdad7b1440dc5b6122c42"}, + {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:c8a09d570b39517de10ee5b718730e171251ce63bbb890c430c725c8c53d4484"}, + {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8ff3c8cb26afaed25e8bca7b9dd0c1e36de71f35a3a0706b5c0d5172587a3827"}, + {file = "setproctitle-1.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:589be87172b238f839e19f146b9ea47c71e413e951ef0dc6db4218ddacf3c202"}, + {file = "setproctitle-1.3.2-cp38-cp38-win32.whl", hash = "sha256:4749a2b0c9ac52f864d13cee94546606f92b981b50e46226f7f830a56a9dc8e1"}, + {file = "setproctitle-1.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:e43f315c68aa61cbdef522a2272c5a5b9b8fd03c301d3167b5e1343ef50c676c"}, + {file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:de3a540cd1817ede31f530d20e6a4935bbc1b145fd8f8cf393903b1e02f1ae76"}, + {file = "setproctitle-1.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4058564195b975ddc3f0462375c533cce310ccdd41b80ac9aed641c296c3eff4"}, + {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1c5d5dad7c28bdd1ec4187d818e43796f58a845aa892bb4481587010dc4d362b"}, + {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ffc61a388a5834a97953d6444a2888c24a05f2e333f9ed49f977a87bb1ad4761"}, + {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1fa1a0fbee72b47dc339c87c890d3c03a72ea65c061ade3204f285582f2da30f"}, + {file = "setproctitle-1.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8a988c7220c002c45347430993830666e55bc350179d91fcee0feafe64e1d4"}, + {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bae283e85fc084b18ffeb92e061ff7ac5af9e183c9d1345c93e178c3e5069cbe"}, + {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fed18e44711c5af4b681c2b3b18f85e6f0f1b2370a28854c645d636d5305ccd8"}, + {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:b34baef93bfb20a8ecb930e395ccd2ae3268050d8cf4fe187de5e2bd806fd796"}, + {file = "setproctitle-1.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7f0bed90a216ef28b9d227d8d73e28a8c9b88c0f48a082d13ab3fa83c581488f"}, + {file = "setproctitle-1.3.2-cp39-cp39-win32.whl", hash = "sha256:4d8938249a7cea45ab7e1e48b77685d0f2bab1ebfa9dde23e94ab97968996a7c"}, + {file = "setproctitle-1.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:a47d97a75fd2d10c37410b180f67a5835cb1d8fdea2648fd7f359d4277f180b9"}, + {file = "setproctitle-1.3.2-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:dad42e676c5261eb50fdb16bdf3e2771cf8f99a79ef69ba88729aeb3472d8575"}, + {file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c91b9bc8985d00239f7dc08a49927a7ca1ca8a6af2c3890feec3ed9665b6f91e"}, + {file = "setproctitle-1.3.2-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e8579a43eafd246e285eb3a5b939e7158073d5087aacdd2308f23200eac2458b"}, + {file = "setproctitle-1.3.2-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:2fbd8187948284293f43533c150cd69a0e4192c83c377da837dbcd29f6b83084"}, + {file = "setproctitle-1.3.2-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:faec934cfe5fd6ac1151c02e67156c3f526e82f96b24d550b5d51efa4a5527c6"}, + {file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e1aafc91cbdacc9e5fe712c52077369168e6b6c346f3a9d51bf600b53eae56bb"}, + {file = "setproctitle-1.3.2-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b617f12c9be61e8f4b2857be4a4319754756845dbbbd9c3718f468bbb1e17bcb"}, + {file = "setproctitle-1.3.2-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:b2c9cb2705fc84cb8798f1ba74194f4c080aaef19d9dae843591c09b97678e98"}, + {file = "setproctitle-1.3.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:a149a5f7f2c5a065d4e63cb0d7a4b6d3b66e6e80f12e3f8827c4f63974cbf122"}, + {file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2e3ac25bfc4a0f29d2409650c7532d5ddfdbf29f16f8a256fc31c47d0dc05172"}, + {file = "setproctitle-1.3.2-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65d884e22037b23fa25b2baf1a3316602ed5c5971eb3e9d771a38c3a69ce6e13"}, + {file = "setproctitle-1.3.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7aa0aac1711fadffc1d51e9d00a3bea61f68443d6ac0241a224e4d622489d665"}, + {file = "setproctitle-1.3.2.tar.gz", hash = "sha256:b9fb97907c830d260fa0658ed58afd48a86b2b88aac521135c352ff7fd3477fd"}, +] +setuptools = [ + {file = "setuptools-65.5.0-py3-none-any.whl", hash = "sha256:f62ea9da9ed6289bfe868cd6845968a2c854d1427f8548d52cae02a42b4f0356"}, + {file = "setuptools-65.5.0.tar.gz", hash = "sha256:512e5536220e38146176efb833d4a62aa726b7bbff82cfbc8ba9eaa3996e0b17"}, +] +setuptools-scm = [ + {file = "setuptools_scm-7.0.5-py3-none-any.whl", hash = "sha256:7930f720905e03ccd1e1d821db521bff7ec2ac9cf0ceb6552dd73d24a45d3b02"}, + {file = "setuptools_scm-7.0.5.tar.gz", hash = "sha256:031e13af771d6f892b941adb6ea04545bbf91ebc5ce68c78aaf3fff6e1fb4844"}, +] +shapely = [ + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d048f93e42ba578b82758c15d8ae037d08e69d91d9872bca5a1895b118f4e2b0"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99ab0ddc05e44acabdbe657c599fdb9b2d82e86c5493bdae216c0c4018a82dee"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99a2f0da0109e81e0c101a2b4cd8412f73f5f299e7b5b2deaf64cd2a100ac118"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6fe855e7d45685926b6ba00aaeb5eba5862611f7465775dacd527e081a8ced6d"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec14ceca36f67cb48b34d02d7f65a9acae15cd72b48e303531893ba4a960f3ea"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a2b2a65fa7f97115c1cd989fe9d6f39281ca2a8a014f1d4904c1a6e34d7f25"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-win32.whl", hash = "sha256:21776184516a16bf82a0c3d6d6a312b3cd15a4cabafc61ee01cf2714a82e8396"}, + {file = "Shapely-1.8.5.post1-cp310-cp310-win_amd64.whl", hash = "sha256:a354199219c8d836f280b88f2c5102c81bb044ccea45bd361dc38a79f3873714"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:783bad5f48e2708a0e2f695a34ed382e4162c795cb2f0368b39528ac1d6db7ed"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a23ef3882d6aa203dd3623a3d55d698f59bfbd9f8a3bfed52c2da05a7f0f8640"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ab38f7b5196ace05725e407cb8cab9ff66edb8e6f7bb36a398e8f73f52a7aaa2"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8d086591f744be483b34628b391d741e46f2645fe37594319e0a673cc2c26bcf"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4728666fff8cccc65a07448cae72c75a8773fea061c3f4f139c44adc429b18c3"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-win32.whl", hash = "sha256:84010db15eb364a52b74ea8804ef92a6a930dfc1981d17a369444b6ddec66efd"}, + {file = "Shapely-1.8.5.post1-cp311-cp311-win_amd64.whl", hash = "sha256:48dcfffb9e225c0481120f4bdf622131c8c95f342b00b158cdbe220edbbe20b6"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:2fd15397638df291c427a53d641d3e6fd60458128029c8c4f487190473a69a91"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a74631e511153366c6dbe3229fa93f877e3c87ea8369cd00f1d38c76b0ed9ace"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:66bdac74fbd1d3458fa787191a90fa0ae610f09e2a5ec398c36f968cc0ed743f"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-win32.whl", hash = "sha256:6d388c0c1bd878ed1af4583695690aa52234b02ed35f93a1c8486ff52a555838"}, + {file = "Shapely-1.8.5.post1-cp36-cp36m-win_amd64.whl", hash = "sha256:be9423d5a3577ac2e92c7e758bd8a2b205f5e51a012177a590bc46fc51eb4834"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5d7f85c2d35d39ff53c9216bc76b7641c52326f7e09aaad1789a3611a0f812f2"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:adcf8a11b98af9375e32bff91de184f33a68dc48b9cb9becad4f132fa25cfa3c"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:753ed0e21ab108bd4282405b9b659f2e985e8502b1a72b978eaa51d3496dee19"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-win32.whl", hash = "sha256:65b21243d8f6bcd421210daf1fabb9de84de2c04353c5b026173b88d17c1a581"}, + {file = "Shapely-1.8.5.post1-cp37-cp37m-win_amd64.whl", hash = "sha256:370b574c78dc5af3a198a6da5d9b3d7c04654bd2ef7e80e80a3a0992dfb2d9cd"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:532a55ee2a6c52d23d6f7d1567c8f0473635f3b270262c44e1b0c88096827e22"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:3480657460e939f45a7d359ef0e172a081f249312557fe9aa78c4fd3a362d993"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b65f5d530ba91e49ffc7c589255e878d2506a8b96ffce69d3b7c4500a9a9eaf8"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:147066da0be41b147a61f8eb805dea3b13709dbc873a431ccd7306e24d712bc0"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c2822111ddc5bcfb116e6c663e403579d0fe3f147d2a97426011a191c43a7458"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4b47bb6f9369e8bf3e6dbd33e6a25a47ee02b2874792a529fe04a49bf8bc0df6"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-win32.whl", hash = "sha256:2e0a8c2e55f1be1312b51c92b06462ea89e6bb703fab4b114e7a846d941cfc40"}, + {file = "Shapely-1.8.5.post1-cp38-cp38-win_amd64.whl", hash = "sha256:0d885cb0cf670c1c834df3f371de8726efdf711f18e2a75da5cfa82843a7ab65"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0b4ee3132ee90f07d63db3aea316c4c065ed7a26231458dda0874414a09d6ba3"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:02dd5d7dc6e46515d88874134dc8fcdc65826bca93c3eecee59d1910c42c1b17"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c6a9a4a31cd6e86d0fbe8473ceed83d4fe760b19d949fb557ef668defafea0f6"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:38f0fbbcb8ca20c16451c966c1f527cc43968e121c8a048af19ed3e339a921cd"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:78fb9d929b8ee15cfd424b6c10879ce1907f24e05fb83310fc47d2cd27088e40"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:89164e7a9776a19e29f01369a98529321994e2e4d852b92b7e01d4d9804c55bf"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-win32.whl", hash = "sha256:8e59817b0fe63d34baedaabba8c393c0090f061917d18fc0bcc2f621937a8f73"}, + {file = "Shapely-1.8.5.post1-cp39-cp39-win_amd64.whl", hash = "sha256:e9c30b311de2513555ab02464ebb76115d242842b29c412f5a9aa0cac57be9f6"}, + {file = "Shapely-1.8.5.post1.tar.gz", hash = "sha256:ef3be705c3eac282a28058e6c6e5503419b250f482320df2172abcbea642c831"}, +] +shellingham = [ + {file = "shellingham-1.5.0-py2.py3-none-any.whl", hash = "sha256:a8f02ba61b69baaa13facdba62908ca8690a94b8119b69f5ec5873ea85f7391b"}, + {file = "shellingham-1.5.0.tar.gz", hash = "sha256:72fb7f5c63103ca2cb91b23dee0c71fe8ad6fbfd46418ef17dbe40db51592dad"}, +] +simplejson = [ + {file = "simplejson-3.17.6-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a89acae02b2975b1f8e4974cb8cdf9bf9f6c91162fb8dec50c259ce700f2770a"}, + {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:82ff356ff91be0ab2293fc6d8d262451eb6ac4fd999244c4b5f863e049ba219c"}, + {file = "simplejson-3.17.6-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:0de783e9c2b87bdd75b57efa2b6260c24b94605b5c9843517577d40ee0c3cc8a"}, + {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:d24a9e61df7a7787b338a58abfba975414937b609eb6b18973e25f573bc0eeeb"}, + {file = "simplejson-3.17.6-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:e8603e691580487f11306ecb066c76f1f4a8b54fb3bdb23fa40643a059509366"}, + {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9b01e7b00654115965a206e3015f0166674ec1e575198a62a977355597c0bef5"}, + {file = "simplejson-3.17.6-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:37bc0cf0e5599f36072077e56e248f3336917ded1d33d2688624d8ed3cefd7d2"}, + {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:cf6e7d5fe2aeb54898df18db1baf479863eae581cce05410f61f6b4188c8ada1"}, + {file = "simplejson-3.17.6-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:bdfc54b4468ed4cd7415928cbe782f4d782722a81aeb0f81e2ddca9932632211"}, + {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:dd16302d39c4d6f4afde80edd0c97d4db643327d355a312762ccd9bd2ca515ed"}, + {file = "simplejson-3.17.6-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:deac4bdafa19bbb89edfb73b19f7f69a52d0b5bd3bb0c4ad404c1bbfd7b4b7fd"}, + {file = "simplejson-3.17.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a8bbdb166e2fb816e43ab034c865147edafe28e1b19c72433147789ac83e2dda"}, + {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7854326920d41c3b5d468154318fe6ba4390cb2410480976787c640707e0180"}, + {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:04e31fa6ac8e326480703fb6ded1488bfa6f1d3f760d32e29dbf66d0838982ce"}, + {file = "simplejson-3.17.6-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f63600ec06982cdf480899026f4fda622776f5fabed9a869fdb32d72bc17e99a"}, + {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e03c3b8cc7883a54c3f34a6a135c4a17bc9088a33f36796acdb47162791b02f6"}, + {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a2d30d6c1652140181dc6861f564449ad71a45e4f165a6868c27d36745b65d40"}, + {file = "simplejson-3.17.6-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a1aa6e4cae8e3b8d5321be4f51c5ce77188faf7baa9fe1e78611f93a8eed2882"}, + {file = "simplejson-3.17.6-cp310-cp310-win32.whl", hash = "sha256:97202f939c3ff341fc3fa84d15db86156b1edc669424ba20b0a1fcd4a796a045"}, + {file = "simplejson-3.17.6-cp310-cp310-win_amd64.whl", hash = "sha256:80d3bc9944be1d73e5b1726c3bbfd2628d3d7fe2880711b1eb90b617b9b8ac70"}, + {file = "simplejson-3.17.6-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9fa621b3c0c05d965882c920347b6593751b7ab20d8fa81e426f1735ca1a9fc7"}, + {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd2fb11922f58df8528adfca123f6a84748ad17d066007e7ac977720063556bd"}, + {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:724c1fe135aa437d5126138d977004d165a3b5e2ee98fc4eb3e7c0ef645e7e27"}, + {file = "simplejson-3.17.6-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4ff4ac6ff3aa8f814ac0f50bf218a2e1a434a17aafad4f0400a57a8cc62ef17f"}, + {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:67093a526e42981fdd954868062e56c9b67fdd7e712616cc3265ad0c210ecb51"}, + {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:5d6b4af7ad7e4ac515bc6e602e7b79e2204e25dbd10ab3aa2beef3c5a9cad2c7"}, + {file = "simplejson-3.17.6-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:1c9b1ed7ed282b36571638297525f8ef80f34b3e2d600a56f962c6044f24200d"}, + {file = "simplejson-3.17.6-cp36-cp36m-win32.whl", hash = "sha256:632ecbbd2228575e6860c9e49ea3cc5423764d5aa70b92acc4e74096fb434044"}, + {file = "simplejson-3.17.6-cp36-cp36m-win_amd64.whl", hash = "sha256:4c09868ddb86bf79b1feb4e3e7e4a35cd6e61ddb3452b54e20cf296313622566"}, + {file = "simplejson-3.17.6-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:4b6bd8144f15a491c662f06814bd8eaa54b17f26095bb775411f39bacaf66837"}, + {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5decdc78849617917c206b01e9fc1d694fd58caa961be816cb37d3150d613d9a"}, + {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:521877c7bd060470806eb6335926e27453d740ac1958eaf0d8c00911bc5e1802"}, + {file = "simplejson-3.17.6-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:65b998193bd7b0c7ecdfffbc825d808eac66279313cb67d8892bb259c9d91494"}, + {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:ac786f6cb7aa10d44e9641c7a7d16d7f6e095b138795cd43503769d4154e0dc2"}, + {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:3ff5b3464e1ce86a8de8c88e61d4836927d5595c2162cab22e96ff551b916e81"}, + {file = "simplejson-3.17.6-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:69bd56b1d257a91e763256d63606937ae4eb890b18a789b66951c00062afec33"}, + {file = "simplejson-3.17.6-cp37-cp37m-win32.whl", hash = "sha256:b81076552d34c27e5149a40187a8f7e2abb2d3185576a317aaf14aeeedad862a"}, + {file = "simplejson-3.17.6-cp37-cp37m-win_amd64.whl", hash = "sha256:07ecaafc1b1501f275bf5acdee34a4ad33c7c24ede287183ea77a02dc071e0c0"}, + {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:068670af975247acbb9fc3d5393293368cda17026db467bf7a51548ee8f17ee1"}, + {file = "simplejson-3.17.6-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4d1c135af0c72cb28dd259cf7ba218338f4dc027061262e46fe058b4e6a4c6a3"}, + {file = "simplejson-3.17.6-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:23fe704da910ff45e72543cbba152821685a889cf00fc58d5c8ee96a9bad5f94"}, + {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f444762fed1bc1fd75187ef14a20ed900c1fbb245d45be9e834b822a0223bc81"}, + {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:681eb4d37c9a9a6eb9b3245a5e89d7f7b2b9895590bb08a20aa598c1eb0a1d9d"}, + {file = "simplejson-3.17.6-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:8e8607d8f6b4f9d46fee11447e334d6ab50e993dd4dbfb22f674616ce20907ab"}, + {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b10556817f09d46d420edd982dd0653940b90151d0576f09143a8e773459f6fe"}, + {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e1ec8a9ee0987d4524ffd6299e778c16cc35fef6d1a2764e609f90962f0b293a"}, + {file = "simplejson-3.17.6-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0b4126cac7d69ac06ff22efd3e0b3328a4a70624fcd6bca4fc1b4e6d9e2e12bf"}, + {file = "simplejson-3.17.6-cp38-cp38-win32.whl", hash = "sha256:35a49ebef25f1ebdef54262e54ae80904d8692367a9f208cdfbc38dbf649e00a"}, + {file = "simplejson-3.17.6-cp38-cp38-win_amd64.whl", hash = "sha256:743cd768affaa508a21499f4858c5b824ffa2e1394ed94eb85caf47ac0732198"}, + {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:fb62d517a516128bacf08cb6a86ecd39fb06d08e7c4980251f5d5601d29989ba"}, + {file = "simplejson-3.17.6-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:12133863178a8080a3dccbf5cb2edfab0001bc41e5d6d2446af2a1131105adfe"}, + {file = "simplejson-3.17.6-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:5540fba2d437edaf4aa4fbb80f43f42a8334206ad1ad3b27aef577fd989f20d9"}, + {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d74ee72b5071818a1a5dab47338e87f08a738cb938a3b0653b9e4d959ddd1fd9"}, + {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:28221620f4dcabdeac310846629b976e599a13f59abb21616356a85231ebd6ad"}, + {file = "simplejson-3.17.6-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:b09bc62e5193e31d7f9876220fb429ec13a6a181a24d897b9edfbbdbcd678851"}, + {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7255a37ff50593c9b2f1afa8fafd6ef5763213c1ed5a9e2c6f5b9cc925ab979f"}, + {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:401d40969cee3df7bda211e57b903a534561b77a7ade0dd622a8d1a31eaa8ba7"}, + {file = "simplejson-3.17.6-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a649d0f66029c7eb67042b15374bd93a26aae202591d9afd71e111dd0006b198"}, + {file = "simplejson-3.17.6-cp39-cp39-win32.whl", hash = "sha256:522fad7be85de57430d6d287c4b635813932946ebf41b913fe7e880d154ade2e"}, + {file = "simplejson-3.17.6-cp39-cp39-win_amd64.whl", hash = "sha256:3fe87570168b2ae018391e2b43fbf66e8593a86feccb4b0500d134c998983ccc"}, + {file = "simplejson-3.17.6.tar.gz", hash = "sha256:cf98038d2abf63a1ada5730e91e84c642ba6c225b0198c3684151b1f80c5f8a6"}, +] +six = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] +smbus2 = [ + {file = "smbus2-0.4.2-py2.py3-none-any.whl", hash = "sha256:50f3c78e436b42a9583948be06961a8104cf020ebad5edfaaf2657528bef0818"}, + {file = "smbus2-0.4.2.tar.gz", hash = "sha256:634541ed794068a822fe7499f1577468b9d4641b68dd9bfb6d0eb7270f4d2a32"}, +] +sniffio = [ + {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, + {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, +] +snowballstemmer = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] +sortedcontainers = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] +sounddevice = [ + {file = "sounddevice-0.4.5-py3-none-any.whl", hash = "sha256:5cea4afd9412e731f50ae09a54d68b10628a604cfd56b42a976c54d424c6c39d"}, + {file = "sounddevice-0.4.5-py3-none-macosx_10_6_x86_64.macosx_10_6_universal2.whl", hash = "sha256:0875173595a8bd5a66b5a03a3d958e7b89c3b956b8befbe4491a24a3ce7784c0"}, + {file = "sounddevice-0.4.5-py3-none-win32.whl", hash = "sha256:442adf53850916374a58f902200aaf9412227378181264e60c966da64be47d41"}, + {file = "sounddevice-0.4.5-py3-none-win_amd64.whl", hash = "sha256:d3216c5d3d678c3301058e9aac7000879e255140c524c9ef98730091b67ea676"}, + {file = "sounddevice-0.4.5.tar.gz", hash = "sha256:2fe0d41299e4f3037dad2acede4eff0666b34a1fa3da5335e47120373964bef5"}, +] +soupsieve = [ + {file = "soupsieve-2.3.2.post1-py3-none-any.whl", hash = "sha256:3b2503d3c7084a42b1ebd08116e5f81aadfaea95863628c80a3b774a11b7c759"}, + {file = "soupsieve-2.3.2.post1.tar.gz", hash = "sha256:fc53893b3da2c33de295667a0e19f078c14bf86544af307354de5fcf12a3f30d"}, +] +sphinx = [ + {file = "Sphinx-5.3.0.tar.gz", hash = "sha256:51026de0a9ff9fc13c05d74913ad66047e104f56a129ff73e174eb5c3ee794b5"}, + {file = "sphinx-5.3.0-py3-none-any.whl", hash = "sha256:060ca5c9f7ba57a08a1219e547b269fadf125ae25b06b9fa7f66768efb652d6d"}, +] +sphinx-rtd-theme = [ + {file = "sphinx_rtd_theme-1.0.0-py2.py3-none-any.whl", hash = "sha256:4d35a56f4508cfee4c4fb604373ede6feae2a306731d533f409ef5c3496fdbd8"}, + {file = "sphinx_rtd_theme-1.0.0.tar.gz", hash = "sha256:eec6d497e4c2195fa0e8b2016b337532b8a699a68bcb22a512870e16925c6a5c"}, +] +sphinx-sitemap = [ + {file = "sphinx-sitemap-2.2.0.tar.gz", hash = "sha256:65adda39233cb17c0da10ba1cebaa2df73e271cdb6f8efd5cec8eef3b3cf7737"}, +] +sphinxcontrib-applehelp = [ + {file = "sphinxcontrib-applehelp-1.0.2.tar.gz", hash = "sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58"}, + {file = "sphinxcontrib_applehelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a"}, +] +sphinxcontrib-devhelp = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] +sphinxcontrib-htmlhelp = [ + {file = "sphinxcontrib-htmlhelp-2.0.0.tar.gz", hash = "sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2"}, + {file = "sphinxcontrib_htmlhelp-2.0.0-py2.py3-none-any.whl", hash = "sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07"}, +] +sphinxcontrib-jsmath = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] +sphinxcontrib-qthelp = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] +sphinxcontrib-serializinghtml = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] +spidev = [ + {file = "spidev-3.6-cp39-cp39-linux_armv7l.whl", hash = "sha256:280abc00a1ef7780ef62c3f294f52a2527b6c47d8c269fea98664970bcaf6da5"}, + {file = "spidev-3.6.tar.gz", hash = "sha256:14dbc37594a4aaef85403ab617985d3c3ef464d62bc9b769ef552db53701115b"}, +] +spidev2 = [ + {file = "spidev2-0.9.0.tar.gz", hash = "sha256:152da2911a8660283ceac3a75dd869953379bcbcf079e5436af5aae736876086"}, +] +sqlalchemy = [ + {file = "SQLAlchemy-1.4.42-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:28e881266a172a4d3c5929182fde6bb6fba22ac93f137d5380cc78a11a9dd124"}, + {file = "SQLAlchemy-1.4.42-cp27-cp27m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:ca9389a00f639383c93ed00333ed763812f80b5ae9e772ea32f627043f8c9c88"}, + {file = "SQLAlchemy-1.4.42-cp27-cp27m-win32.whl", hash = "sha256:1d0c23ecf7b3bc81e29459c34a3f4c68ca538de01254e24718a7926810dc39a6"}, + {file = "SQLAlchemy-1.4.42-cp27-cp27m-win_amd64.whl", hash = "sha256:6c9d004eb78c71dd4d3ce625b80c96a827d2e67af9c0d32b1c1e75992a7916cc"}, + {file = "SQLAlchemy-1.4.42-cp27-cp27mu-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:9e3a65ce9ed250b2f096f7b559fe3ee92e6605fab3099b661f0397a9ac7c8d95"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:2e56dfed0cc3e57b2f5c35719d64f4682ef26836b81067ee6cfad062290fd9e2"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b42c59ffd2d625b28cdb2ae4cde8488543d428cba17ff672a543062f7caee525"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:22459fc1718785d8a86171bbe7f01b5c9d7297301ac150f508d06e62a2b4e8d2"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:df76e9c60879fdc785a34a82bf1e8691716ffac32e7790d31a98d7dec6e81545"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-win32.whl", hash = "sha256:e7e740453f0149437c101ea4fdc7eea2689938c5760d7dcc436c863a12f1f565"}, + {file = "SQLAlchemy-1.4.42-cp310-cp310-win_amd64.whl", hash = "sha256:effc89e606165ca55f04f3f24b86d3e1c605e534bf1a96e4e077ce1b027d0b71"}, + {file = "SQLAlchemy-1.4.42-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:97ff50cd85bb907c2a14afb50157d0d5486a4b4639976b4a3346f34b6d1b5272"}, + {file = "SQLAlchemy-1.4.42-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e12c6949bae10f1012ab5c0ea52ab8db99adcb8c7b717938252137cdf694c775"}, + {file = "SQLAlchemy-1.4.42-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11b2ec26c5d2eefbc3e6dca4ec3d3d95028be62320b96d687b6e740424f83b7d"}, + {file = "SQLAlchemy-1.4.42-cp311-cp311-win32.whl", hash = "sha256:6045b3089195bc008aee5c273ec3ba9a93f6a55bc1b288841bd4cfac729b6516"}, + {file = "SQLAlchemy-1.4.42-cp311-cp311-win_amd64.whl", hash = "sha256:0501f74dd2745ec38f44c3a3900fb38b9db1ce21586b691482a19134062bf049"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:6e39e97102f8e26c6c8550cb368c724028c575ec8bc71afbbf8faaffe2b2092a"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:15d878929c30e41fb3d757a5853b680a561974a0168cd33a750be4ab93181628"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fa5b7eb2051e857bf83bade0641628efe5a88de189390725d3e6033a1fff4257"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e1c5f8182b4f89628d782a183d44db51b5af84abd6ce17ebb9804355c88a7b5"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-win32.whl", hash = "sha256:a7dd5b7b34a8ba8d181402d824b87c5cee8963cb2e23aa03dbfe8b1f1e417cde"}, + {file = "SQLAlchemy-1.4.42-cp36-cp36m-win_amd64.whl", hash = "sha256:5ede1495174e69e273fad68ad45b6d25c135c1ce67723e40f6cf536cb515e20b"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:9256563506e040daddccaa948d055e006e971771768df3bb01feeb4386c242b0"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4948b6c5f4e56693bbeff52f574279e4ff972ea3353f45967a14c30fb7ae2beb"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:1811a0b19a08af7750c0b69e38dec3d46e47c4ec1d74b6184d69f12e1c99a5e0"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b01d9cd2f9096f688c71a3d0f33f3cd0af8549014e66a7a7dee6fc214a7277d"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-win32.whl", hash = "sha256:bd448b262544b47a2766c34c0364de830f7fb0772d9959c1c42ad61d91ab6565"}, + {file = "SQLAlchemy-1.4.42-cp37-cp37m-win_amd64.whl", hash = "sha256:04f2598c70ea4a29b12d429a80fad3a5202d56dce19dd4916cc46a965a5ca2e9"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:3ab7c158f98de6cb4f1faab2d12973b330c2878d0c6b689a8ca424c02d66e1b3"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ee377eb5c878f7cefd633ab23c09e99d97c449dd999df639600f49b74725b80"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:934472bb7d8666727746a75670a1f8d91a9cae8c464bba79da30a0f6faccd9e1"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fdb94a3d1ba77ff2ef11912192c066f01e68416f554c194d769391638c8ad09a"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-win32.whl", hash = "sha256:f0f574465b78f29f533976c06b913e54ab4980b9931b69aa9d306afff13a9471"}, + {file = "SQLAlchemy-1.4.42-cp38-cp38-win_amd64.whl", hash = "sha256:a85723c00a636eed863adb11f1e8aaa36ad1c10089537823b4540948a8429798"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:5ce6929417d5dce5ad1d3f147db81735a4a0573b8fb36e3f95500a06eaddd93e"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:723e3b9374c1ce1b53564c863d1a6b2f1dc4e97b1c178d9b643b191d8b1be738"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:876eb185911c8b95342b50a8c4435e1c625944b698a5b4a978ad2ffe74502908"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2fd49af453e590884d9cdad3586415922a8e9bb669d874ee1dc55d2bc425aacd"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-win32.whl", hash = "sha256:e4ef8cb3c5b326f839bfeb6af5f406ba02ad69a78c7aac0fbeeba994ad9bb48a"}, + {file = "SQLAlchemy-1.4.42-cp39-cp39-win_amd64.whl", hash = "sha256:5f966b64c852592469a7eb759615bbd351571340b8b344f1d3fa2478b5a4c934"}, + {file = "SQLAlchemy-1.4.42.tar.gz", hash = "sha256:177e41914c476ed1e1b77fd05966ea88c094053e17a85303c4ce007f88eff363"}, +] +stack-data = [ + {file = "stack_data-0.5.1-py3-none-any.whl", hash = "sha256:5120731a18ba4c82cefcf84a945f6f3e62319ef413bfc210e32aca3a69310ba2"}, + {file = "stack_data-0.5.1.tar.gz", hash = "sha256:95eb784942e861a3d80efd549ff9af6cf847d88343a12eb681d7157cfcb6e32b"}, +] +subprocess32 = [ + {file = "subprocess32-3.5.4-cp27-cp27m-macosx_10_6_intel.whl", hash = "sha256:88e37c1aac5388df41cc8a8456bb49ebffd321a3ad4d70358e3518176de3a56b"}, + {file = "subprocess32-3.5.4-cp27-cp27mu-manylinux2014_x86_64.whl", hash = "sha256:e45d985aef903c5b7444d34350b05da91a9e0ea015415ab45a21212786c649d0"}, + {file = "subprocess32-3.5.4.tar.gz", hash = "sha256:eb2937c80497978d181efa1b839ec2d9622cf9600a039a79d0e108d1f9aec79d"}, +] +sympy = [ + {file = "sympy-1.11.1-py3-none-any.whl", hash = "sha256:938f984ee2b1e8eae8a07b884c8b7a1146010040fccddc6539c54f401c8f6fcf"}, + {file = "sympy-1.11.1.tar.gz", hash = "sha256:e32380dce63cb7c0108ed525570092fd45168bdae2faa17e528221ef72e88658"}, +] +tabulate = [ + {file = "tabulate-0.8.10-py3-none-any.whl", hash = "sha256:0ba055423dbaa164b9e456abe7920c5e8ed33fcc16f6d1b2f2d152c8e1e8b4fc"}, + {file = "tabulate-0.8.10.tar.gz", hash = "sha256:6c57f3f3dd7ac2782770155f3adb2db0b1a269637e42f27599925e64b114f519"}, +] +tenacity = [ + {file = "tenacity-8.1.0-py3-none-any.whl", hash = "sha256:35525cd47f82830069f0d6b73f7eb83bc5b73ee2fff0437952cedf98b27653ac"}, + {file = "tenacity-8.1.0.tar.gz", hash = "sha256:e48c437fdf9340f5666b92cd7990e96bc5fc955e1298baf4a907e3972067a445"}, +] +terminado = [ + {file = "terminado-0.16.0-py3-none-any.whl", hash = "sha256:3e995072a7178a104c41134548ce9b03e4e7f0a538e9c29df4f1fbc81c7cfc75"}, + {file = "terminado-0.16.0.tar.gz", hash = "sha256:fac14374eb5498bdc157ed32e510b1f60d5c3c7981a9f5ba018bb9a64cec0c25"}, +] +threadpoolctl = [ + {file = "threadpoolctl-3.1.0-py3-none-any.whl", hash = "sha256:8b99adda265feb6773280df41eece7b2e6561b772d21ffd52e372f999024907b"}, + {file = "threadpoolctl-3.1.0.tar.gz", hash = "sha256:a335baacfaa4400ae1f0d8e3a58d6674d2f8828e3716bb2802c44955ad391380"}, +] +tifffile = [ + {file = "tifffile-2022.10.10-py3-none-any.whl", hash = "sha256:87f3aee8a0d06b74655269a105de75c1958a24653e1930d523eb516100043503"}, + {file = "tifffile-2022.10.10.tar.gz", hash = "sha256:50b61ba943b866d191295bc38a00191c9fdab23ece063544c7f1a264e3f6aa8e"}, +] +timezonefinder = [ + {file = "timezonefinder-6.1.3-cp38-cp38-manylinux_2_31_x86_64.whl", hash = "sha256:96c96db94e75e072187843152e6c5dc0718500a9a91986032365abe09162d0e7"}, + {file = "timezonefinder-6.1.3.tar.gz", hash = "sha256:f2ee561b1e7692b933fcd914df38800e93db7caf278e7328de7328829b04f275"}, +] +timm = [ + {file = "timm-0.4.12-py3-none-any.whl", hash = "sha256:dba6b1702b7d24bf9f0f1c2fc394b4ee28f93cde5404f1dc732d63ccd00533b6"}, + {file = "timm-0.4.12.tar.gz", hash = "sha256:b14be70dbd4528b5ca8657cf5bc2672c7918c3d9ebfbffe80f4785b54e884b1e"}, +] +tinycss2 = [ + {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, + {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, +] +toml = [ + {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, + {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +tomlkit = [ + {file = "tomlkit-0.11.5-py3-none-any.whl", hash = "sha256:f2ef9da9cef846ee027947dc99a45d6b68a63b0ebc21944649505bf2e8bc5fe7"}, + {file = "tomlkit-0.11.5.tar.gz", hash = "sha256:571854ebbb5eac89abcb4a2e47d7ea27b89bf29e09c35395da6f03dd4ae23d1c"}, +] +torch = [] +torchsummary = [ + {file = "torchsummary-1.5.1-py3-none-any.whl", hash = "sha256:10f41d1743fb918f83293f13183f532ab1bb8f6639a1b89e5f8592ec1919a976"}, + {file = "torchsummary-1.5.1.tar.gz", hash = "sha256:981bf689e22e0cf7f95c746002f20a24ad26aa6b9d861134a14bc6ce92230590"}, +] +torchvision = [] +tornado = [ + {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, + {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, + {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, + {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, + {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, + {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, + {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, +] +tqdm = [ + {file = "tqdm-4.64.1-py2.py3-none-any.whl", hash = "sha256:6fee160d6ffcd1b1c68c65f14c829c22832bc401726335ce92c52d395944a6a1"}, + {file = "tqdm-4.64.1.tar.gz", hash = "sha256:5f4f682a004951c1b450bc753c710e9280c5746ce6ffedee253ddbcbf54cf1e4"}, +] +traitlets = [ + {file = "traitlets-5.5.0-py3-none-any.whl", hash = "sha256:1201b2c9f76097195989cdf7f65db9897593b0dfd69e4ac96016661bb6f0d30f"}, + {file = "traitlets-5.5.0.tar.gz", hash = "sha256:b122f9ff2f2f6c1709dab289a05555be011c87828e911c0cf4074b85cb780a79"}, +] +triton = [ + {file = "triton-2.0.0-1-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38806ee9663f4b0f7cd64790e96c579374089e58f49aac4a6608121aa55e2505"}, + {file = "triton-2.0.0-1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:226941c7b8595219ddef59a1fdb821e8c744289a132415ddd584facedeb475b1"}, + {file = "triton-2.0.0-1-cp36-cp36m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4c9fc8c89874bc48eb7e7b2107a9b8d2c0bf139778637be5bfccb09191685cfd"}, + {file = "triton-2.0.0-1-cp37-cp37m-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d2684b6a60b9f174f447f36f933e9a45f31db96cb723723ecd2dcfd1c57b778b"}, + {file = "triton-2.0.0-1-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9d4978298b74fcf59a75fe71e535c092b023088933b2f1df933ec32615e4beef"}, + {file = "triton-2.0.0-1-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:74f118c12b437fb2ca25e1a04759173b517582fcf4c7be11913316c764213656"}, + {file = "triton-2.0.0-1-pp37-pypy37_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:9618815a8da1d9157514f08f855d9e9ff92e329cd81c0305003eb9ec25cc5add"}, + {file = "triton-2.0.0-1-pp38-pypy38_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1aca3303629cd3136375b82cb9921727f804e47ebee27b2677fef23005c3851a"}, + {file = "triton-2.0.0-1-pp39-pypy39_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e3e13aa8b527c9b642e3a9defcc0fbd8ffbe1c80d8ac8c15a01692478dc64d8a"}, + {file = "triton-2.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f05a7e64e4ca0565535e3d5d3405d7e49f9d308505bb7773d21fb26a4c008c2"}, + {file = "triton-2.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bb4b99ca3c6844066e516658541d876c28a5f6e3a852286bbc97ad57134827fd"}, + {file = "triton-2.0.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47b4d70dc92fb40af553b4460492c31dc7d3a114a979ffb7a5cdedb7eb546c08"}, + {file = "triton-2.0.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fedce6a381901b1547e0e7e1f2546e4f65dca6d91e2d8a7305a2d1f5551895be"}, + {file = "triton-2.0.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75834f27926eab6c7f00ce73aaf1ab5bfb9bec6eb57ab7c0bfc0a23fac803b4c"}, + {file = "triton-2.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0117722f8c2b579cd429e0bee80f7731ae05f63fe8e9414acd9a679885fcbf42"}, + {file = "triton-2.0.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcd9be5d0c2e45d2b7e6ddc6da20112b6862d69741576f9c3dbaf941d745ecae"}, + {file = "triton-2.0.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42a0d2c3fc2eab4ba71384f2e785fbfd47aa41ae05fa58bf12cb31dcbd0aeceb"}, + {file = "triton-2.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:52c47b72c72693198163ece9d90a721299e4fb3b8e24fd13141e384ad952724f"}, +] +tritonclient = [ + {file = "tritonclient-2.28.0-py3-none-any.whl", hash = "sha256:1f58bbe09a88c35f7979de8ab6579a5337372951f723c0aba31e8bee3e8d79da"}, + {file = "tritonclient-2.28.0-py3-none-manylinux1_x86_64.whl", hash = "sha256:47d93197a0876a743012db4c03f1100f7b225b9aaf8d5f8025bf4a5d9e61bfd2"}, +] +types-atomicwrites = [ + {file = "types-atomicwrites-1.4.5.1.tar.gz", hash = "sha256:9e9f0923ebf93524b28bcece5a23ac8c3820f39b060df29f671936d2e4bc04bc"}, + {file = "types_atomicwrites-1.4.5.1-py3-none-any.whl", hash = "sha256:2f1febbdc78b55453b189fa5b136dce34bab7d1d82319163d470e404aab55c83"}, +] +types-certifi = [ + {file = "types-certifi-2021.10.8.3.tar.gz", hash = "sha256:72cf7798d165bc0b76e1c10dd1ea3097c7063c42c21d664523b928e88b554a4f"}, + {file = "types_certifi-2021.10.8.3-py3-none-any.whl", hash = "sha256:b2d1e325e69f71f7c78e5943d410e650b4707bb0ef32e4ddf3da37f54176e88a"}, +] +types-pycurl = [ + {file = "types-pycurl-7.45.1.tar.gz", hash = "sha256:82e00aa2981595bfa55e5a3bac42221eb3435b0026dffbe1177f6ac9f2d51200"}, + {file = "types_pycurl-7.45.1-py3-none-any.whl", hash = "sha256:9eab3414da4a1b1e9a628bd288fc5172b8c182e1d9fb6d8d082441b0fd64baed"}, +] +types-pyyaml = [ + {file = "types-PyYAML-6.0.12.tar.gz", hash = "sha256:f6f350418125872f3f0409d96a62a5a5ceb45231af5cc07ee0034ec48a3c82fa"}, + {file = "types_PyYAML-6.0.12-py3-none-any.whl", hash = "sha256:29228db9f82df4f1b7febee06bbfb601677882e98a3da98132e31c6874163e15"}, +] +types-requests = [ + {file = "types-requests-2.28.11.2.tar.gz", hash = "sha256:fdcd7bd148139fb8eef72cf4a41ac7273872cad9e6ada14b11ff5dfdeee60ed3"}, + {file = "types_requests-2.28.11.2-py3-none-any.whl", hash = "sha256:14941f8023a80b16441b3b46caffcbfce5265fd14555844d6029697824b5a2ef"}, +] +types-tabulate = [ + {file = "types-tabulate-0.8.11.tar.gz", hash = "sha256:17a5fa3b5ca453815778fc9865e8ecd0118b07b2b9faff3e2b06fe448174dd5e"}, + {file = "types_tabulate-0.8.11-py3-none-any.whl", hash = "sha256:af811268241e8fb87b63c052c87d1e329898a93191309d5d42111372232b2e0e"}, +] +types-urllib3 = [ + {file = "types-urllib3-1.26.25.1.tar.gz", hash = "sha256:a948584944b2412c9a74b9cf64f6c48caf8652cb88b38361316f6d15d8a184cd"}, + {file = "types_urllib3-1.26.25.1-py3-none-any.whl", hash = "sha256:f6422596cc9ee5fdf68f9d547f541096a20c2dcfd587e37c804c9ea720bf5cb2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.4.0-py3-none-any.whl", hash = "sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e"}, + {file = "typing_extensions-4.4.0.tar.gz", hash = "sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa"}, +] +urllib3 = [ + {file = "urllib3-1.26.12-py2.py3-none-any.whl", hash = "sha256:b930dd878d5a8afb066a637fbb35144fe7901e3b209d1cd4f524bd0e9deee997"}, + {file = "urllib3-1.26.12.tar.gz", hash = "sha256:3fa96cf423e6987997fc326ae8df396db2a8b7c667747d47ddd8ecba91f4a74e"}, +] +utm = [ + {file = "utm-0.7.0.tar.gz", hash = "sha256:3c9a3650e98bb6eecec535418d0dfd4db8f88c8ceaca112a0ff0787e116566e2"}, +] +virtualenv = [ + {file = "virtualenv-20.16.5-py3-none-any.whl", hash = "sha256:d07dfc5df5e4e0dbc92862350ad87a36ed505b978f6c39609dc489eadd5b0d27"}, + {file = "virtualenv-20.16.5.tar.gz", hash = "sha256:227ea1b9994fdc5ea31977ba3383ef296d7472ea85be9d6732e42a91c04e80da"}, +] +virtualenv-clone = [ + {file = "virtualenv-clone-0.5.7.tar.gz", hash = "sha256:418ee935c36152f8f153c79824bb93eaf6f0f7984bae31d3f48f350b9183501a"}, + {file = "virtualenv_clone-0.5.7-py3-none-any.whl", hash = "sha256:44d5263bceed0bac3e1424d64f798095233b64def1c5689afa43dc3223caf5b0"}, +] +wcwidth = [ + {file = "wcwidth-0.2.5-py2.py3-none-any.whl", hash = "sha256:beb4802a9cebb9144e99086eff703a642a13d6a0052920003a230f3294bbe784"}, + {file = "wcwidth-0.2.5.tar.gz", hash = "sha256:c4d647b99872929fdb7bdcaa4fbe7f01413ed3d98077df798530e5b04f116c83"}, +] +webencodings = [ + {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, + {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, +] +websocket-client = [ + {file = "websocket-client-1.4.1.tar.gz", hash = "sha256:f9611eb65c8241a67fb373bef040b3cf8ad377a9f6546a12b620b6511e8ea9ef"}, + {file = "websocket_client-1.4.1-py3-none-any.whl", hash = "sha256:398909eb7e261f44b8f4bd474785b6ec5f5b499d4953342fe9755e01ef624090"}, +] +werkzeug = [ + {file = "Werkzeug-2.2.2-py3-none-any.whl", hash = "sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5"}, + {file = "Werkzeug-2.2.2.tar.gz", hash = "sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f"}, +] +widgetsnbextension = [ + {file = "widgetsnbextension-4.0.3-py3-none-any.whl", hash = "sha256:7f3b0de8fda692d31ef03743b598620e31c2668b835edbd3962d080ccecf31eb"}, + {file = "widgetsnbextension-4.0.3.tar.gz", hash = "sha256:34824864c062b0b3030ad78210db5ae6a3960dfb61d5b27562d6631774de0286"}, +] +wrapt = [ + {file = "wrapt-1.14.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59"}, + {file = "wrapt-1.14.1-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462"}, + {file = "wrapt-1.14.1-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320"}, + {file = "wrapt-1.14.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069"}, + {file = "wrapt-1.14.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656"}, + {file = "wrapt-1.14.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c"}, + {file = "wrapt-1.14.1-cp310-cp310-win32.whl", hash = "sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8"}, + {file = "wrapt-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_i686.whl", hash = "sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3"}, + {file = "wrapt-1.14.1-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d"}, + {file = "wrapt-1.14.1-cp35-cp35m-win32.whl", hash = "sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7"}, + {file = "wrapt-1.14.1-cp35-cp35m-win_amd64.whl", hash = "sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00"}, + {file = "wrapt-1.14.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1"}, + {file = "wrapt-1.14.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1"}, + {file = "wrapt-1.14.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569"}, + {file = "wrapt-1.14.1-cp36-cp36m-win32.whl", hash = "sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed"}, + {file = "wrapt-1.14.1-cp36-cp36m-win_amd64.whl", hash = "sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471"}, + {file = "wrapt-1.14.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d"}, + {file = "wrapt-1.14.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015"}, + {file = "wrapt-1.14.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a"}, + {file = "wrapt-1.14.1-cp37-cp37m-win32.whl", hash = "sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853"}, + {file = "wrapt-1.14.1-cp37-cp37m-win_amd64.whl", hash = "sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456"}, + {file = "wrapt-1.14.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1"}, + {file = "wrapt-1.14.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0"}, + {file = "wrapt-1.14.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57"}, + {file = "wrapt-1.14.1-cp38-cp38-win32.whl", hash = "sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5"}, + {file = "wrapt-1.14.1-cp38-cp38-win_amd64.whl", hash = "sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383"}, + {file = "wrapt-1.14.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735"}, + {file = "wrapt-1.14.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3"}, + {file = "wrapt-1.14.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe"}, + {file = "wrapt-1.14.1-cp39-cp39-win32.whl", hash = "sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5"}, + {file = "wrapt-1.14.1-cp39-cp39-win_amd64.whl", hash = "sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb"}, + {file = "wrapt-1.14.1.tar.gz", hash = "sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d"}, +] +xattr = [ + {file = "xattr-0.9.9-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:58a9fb4fd19b467e88f4b75b5243706caa57e312d3aee757b53b57c7fd0f4ba9"}, + {file = "xattr-0.9.9-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:e71efca59705c7abde5b7f76323ebe00ed2977f10cba4204b9421dada036b5ca"}, + {file = "xattr-0.9.9-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:1aad96b6603961c3d1ca1aaa8369b1a8d684a7b37357b2428087c286bf0e561c"}, + {file = "xattr-0.9.9-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:46cb74f98d31d9d70f975ec3e6554360a9bdcbb4b9fb50a69fabe54f9f928c97"}, + {file = "xattr-0.9.9-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:80c2db56058a687d7439be041f916cbeb2943fbe2623e53d5da721a4552d8991"}, + {file = "xattr-0.9.9-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:c360d1cc42e885b64d84f64de3c501dd7bce576248327ef583b4625ee63aa023"}, + {file = "xattr-0.9.9-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:debd87afe6bdf88c3689bde52eecf2b166388b13ef7388259d23223374db417d"}, + {file = "xattr-0.9.9-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:4280c9f33a8678828f1bbc3d3dc8b823b5e4a113ee5ecb0fb98bff60cc2b9ad1"}, + {file = "xattr-0.9.9-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:e0916ec1656d2071cd3139d1f52426825985d8ed076f981ef7f0bc13dfa8e96c"}, + {file = "xattr-0.9.9-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a517916fbf2f58a3222bb2048fe1eeff4e23e07a4ce6228a27de004c80bf53ab"}, + {file = "xattr-0.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e886c882b3b28c7a684c3e3daf46347da5428a46b88bc6d62c4867d574b90c54"}, + {file = "xattr-0.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:373e3d1fd9258438fc38d1438142d3659f36743f374a20457346ef26741ed441"}, + {file = "xattr-0.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7beeb54ca140273b2f6320bb98b701ec30628af2ebe4eb30f7051419eb4ef3"}, + {file = "xattr-0.9.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ef3ca29cdaae9c47c625d84bb6c9046f7275cccde0ea805caa23ca58d3671f3f"}, + {file = "xattr-0.9.9-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c381d890931cd18b137ce3fb5c5f08b672c3c61e2e47b1a7442ee46e827abfe"}, + {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:59c5783ccf57cf2700ce57d51a92134900ed26f6ab20d209f383fb898903fea6"}, + {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:966b885b69d95362e2a12d39f84889cf857090e57263b5ac33409498aa00c160"}, + {file = "xattr-0.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:efaaf0cb1ea8e9febb7baad301ae8cc9ad7a96fdfc5c6399d165e7a19e3e61ce"}, + {file = "xattr-0.9.9-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f19fa75ed1e9db86354efab29869cb2be6976d456bd7c89e67b118d5384a1d98"}, + {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ca28ad06828244b315214ee35388f57e81e90aac2ceac3f32e42ae394e31b9c"}, + {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:532c7f1656dd2fe937116b9e210229f716d7fc7ac142f9cdace7da92266d32e8"}, + {file = "xattr-0.9.9-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11c28033c17e98c67e0def9d6ebd415ad3c006a7bc3fee6bad79c5e52d0dff49"}, + {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:473cabb30e544ea08c8c01c1ef18053147cdc8552d443ac97815e46fbb13c7d4"}, + {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:c4a308522b444d090fbd66a385c9519b6b977818226921b0d2fc403667c93564"}, + {file = "xattr-0.9.9-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:82493434488aca72d88b5129dac8f212e7b8bdca7ceffe7bb977c850f2452e4e"}, + {file = "xattr-0.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:e41d289706c7e8940f4d08e865da6a8ae988123e40a44f9a97ddc09e67795d7d"}, + {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ef08698e360cf43688dca3db3421b156b29948a714d5d089348073f463c11646"}, + {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4eb10ac16ca8d534c0395425d52121e0c1981f808e1b3f577f6a5ec33d3853e4"}, + {file = "xattr-0.9.9-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5605fec07b0e964bd980cc70ec335b9eb1b7ac7c6f314c7c2d8f54b09104fe4c"}, + {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:974e7d577ddb15e4552fb0ec10a4cfe09bdf6267365aa2b8394bb04637785aad"}, + {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:ad6777de922c638bfa87a0d7faebc5722ddef04a1210b2a8909289b58b769af0"}, + {file = "xattr-0.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3887e70873ebf0efbde32f9929ec1c7e45ec0013561743e2cc0406a91e51113b"}, + {file = "xattr-0.9.9-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:83caa8e93a45a0f25f91b92d9b45f490c87bff74f02555df6312efeba0dacc31"}, + {file = "xattr-0.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e33ec0a1d913d946d1ab7509f37ee37306c45af735347f13b963df34ffe6e029"}, + {file = "xattr-0.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:263c58dca83372260c5c195e0b59959e38e1f107f0b7350de82e3db38479036c"}, + {file = "xattr-0.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:125dfb9905428162349d3b8b825d9a18280893f0cb0db2a2467d5ef253fa6ce2"}, + {file = "xattr-0.9.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e243524e0dde16d7a2e1b52512ad2c6964df2143dd1c79b820dcb4c6c0822c20"}, + {file = "xattr-0.9.9-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:01ec07d24a14406bdc6a123041c63a88e1c4a3f820e4a7d30f7609d57311b499"}, + {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:85c1df5f1d209345ea96de137419e886a27bb55076b3ae01faacf35aafcf3a61"}, + {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ca74d3eff92d6dc16e271fbad9cbab547fb9a0c983189c4031c3ff3d150dd871"}, + {file = "xattr-0.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7d17505e49ac70c0e71939c5aac96417a863583fb30a2d6304d5ac881230548f"}, + {file = "xattr-0.9.9-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1ae47a6398d3c04623fa386a4aa2f66e5cd3cdb1a7e69d1bfaeb8c73983bf271"}, + {file = "xattr-0.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:809e2537d0aff9fca97dacf3245cbbaf711bbced5d1b0235a8d1906b04e26114"}, + {file = "xattr-0.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:de3af84364f06d67b3662ccf7c1a73e1d389d8d274394e952651e7bf1bbd2718"}, + {file = "xattr-0.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b62cdad232d2d2dedd39b543701db8e3883444ec0d57ce3fab8f75e5f8b0301"}, + {file = "xattr-0.9.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6b11d2eda397d47f7075743409683c233519ca52aa1dac109b413a4d8c15b740"}, + {file = "xattr-0.9.9-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:661c0a939aefdf071887121f534bb10588d69c7b2dfca5c486af2fc81a0786e8"}, + {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5db7c2db320a8d5264d437d71f1eb7270a7e4a6545296e7766161d17752590b7"}, + {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:83203e60cbaca9536d297e5039b285a600ff84e6e9e8536fe2d521825eeeb437"}, + {file = "xattr-0.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:42bfb4e4da06477e739770ac6942edbdc71e9fc3b497b67db5fba712fa8109c2"}, + {file = "xattr-0.9.9-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:67047d04d1c56ad4f0f5886085e91b0077238ab3faaec6492c3c21920c6566eb"}, + {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:885782bc82ded1a3f684d54a1af259ae9fcc347fa54b5a05b8aad82b8a42044c"}, + {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5bc84ccec618b5aa089e7cee8b07fcc92d4069aac4053da604c8143a0d6b1381"}, + {file = "xattr-0.9.9-pp37-pypy37_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baeff3e5dda8ea7e9424cfaee51829f46afe3836c30d02f343f9049c685681ca"}, + {file = "xattr-0.9.9.tar.gz", hash = "sha256:09cb7e1efb3aa1b4991d6be4eb25b73dc518b4fe894f0915f5b0dcede972f346"}, +] +yarl = [ + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:abc06b97407868ef38f3d172762f4069323de52f2b70d133d096a48d72215d28"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:07b21e274de4c637f3e3b7104694e53260b5fc10d51fb3ec5fed1da8e0f754e3"}, + {file = "yarl-1.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9de955d98e02fab288c7718662afb33aab64212ecb368c5dc866d9a57bf48880"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ec362167e2c9fd178f82f252b6d97669d7245695dc057ee182118042026da40"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:20df6ff4089bc86e4a66e3b1380460f864df3dd9dccaf88d6b3385d24405893b"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5999c4662631cb798496535afbd837a102859568adc67d75d2045e31ec3ac497"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed19b74e81b10b592084a5ad1e70f845f0aacb57577018d31de064e71ffa267a"}, + {file = "yarl-1.8.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e4808f996ca39a6463f45182e2af2fae55e2560be586d447ce8016f389f626f"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:2d800b9c2eaf0684c08be5f50e52bfa2aa920e7163c2ea43f4f431e829b4f0fd"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6628d750041550c5d9da50bb40b5cf28a2e63b9388bac10fedd4f19236ef4957"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:f5af52738e225fcc526ae64071b7e5342abe03f42e0e8918227b38c9aa711e28"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:76577f13333b4fe345c3704811ac7509b31499132ff0181f25ee26619de2c843"}, + {file = "yarl-1.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0c03f456522d1ec815893d85fccb5def01ffaa74c1b16ff30f8aaa03eb21e453"}, + {file = "yarl-1.8.1-cp310-cp310-win32.whl", hash = "sha256:ea30a42dc94d42f2ba4d0f7c0ffb4f4f9baa1b23045910c0c32df9c9902cb272"}, + {file = "yarl-1.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:9130ddf1ae9978abe63808b6b60a897e41fccb834408cde79522feb37fb72fb0"}, + {file = "yarl-1.8.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0ab5a138211c1c366404d912824bdcf5545ccba5b3ff52c42c4af4cbdc2c5035"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0fb2cb4204ddb456a8e32381f9a90000429489a25f64e817e6ff94879d432fc"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:85cba594433915d5c9a0d14b24cfba0339f57a2fff203a5d4fd070e593307d0b"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ca7e596c55bd675432b11320b4eacc62310c2145d6801a1f8e9ad160685a231"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d0f77539733e0ec2475ddcd4e26777d08996f8cd55d2aef82ec4d3896687abda"}, + {file = "yarl-1.8.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:29e256649f42771829974e742061c3501cc50cf16e63f91ed8d1bf98242e5507"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7fce6cbc6c170ede0221cc8c91b285f7f3c8b9fe28283b51885ff621bbe0f8ee"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:59ddd85a1214862ce7c7c66457f05543b6a275b70a65de366030d56159a979f0"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:12768232751689c1a89b0376a96a32bc7633c08da45ad985d0c49ede691f5c0d"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:b19255dde4b4f4c32e012038f2c169bb72e7f081552bea4641cab4d88bc409dd"}, + {file = "yarl-1.8.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:6c8148e0b52bf9535c40c48faebb00cb294ee577ca069d21bd5c48d302a83780"}, + {file = "yarl-1.8.1-cp37-cp37m-win32.whl", hash = "sha256:de839c3a1826a909fdbfe05f6fe2167c4ab033f1133757b5936efe2f84904c07"}, + {file = "yarl-1.8.1-cp37-cp37m-win_amd64.whl", hash = "sha256:dd032e8422a52e5a4860e062eb84ac94ea08861d334a4bcaf142a63ce8ad4802"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:19cd801d6f983918a3f3a39f3a45b553c015c5aac92ccd1fac619bd74beece4a"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6347f1a58e658b97b0a0d1ff7658a03cb79bdbda0331603bed24dd7054a6dea1"}, + {file = "yarl-1.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c0da7e44d0c9108d8b98469338705e07f4bb7dab96dbd8fa4e91b337db42548"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5587bba41399854703212b87071c6d8638fa6e61656385875f8c6dff92b2e461"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31a9a04ecccd6b03e2b0e12e82131f1488dea5555a13a4d32f064e22a6003cfe"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:205904cffd69ae972a1707a1bd3ea7cded594b1d773a0ce66714edf17833cdae"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ea513a25976d21733bff523e0ca836ef1679630ef4ad22d46987d04b372d57fc"}, + {file = "yarl-1.8.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d0b51530877d3ad7a8d47b2fff0c8df3b8f3b8deddf057379ba50b13df2a5eae"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d2b8f245dad9e331540c350285910b20dd913dc86d4ee410c11d48523c4fd546"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ab2a60d57ca88e1d4ca34a10e9fb4ab2ac5ad315543351de3a612bbb0560bead"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:449c957ffc6bc2309e1fbe67ab7d2c1efca89d3f4912baeb8ead207bb3cc1cd4"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:a165442348c211b5dea67c0206fc61366212d7082ba8118c8c5c1c853ea4d82e"}, + {file = "yarl-1.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b3ded839a5c5608eec8b6f9ae9a62cb22cd037ea97c627f38ae0841a48f09eae"}, + {file = "yarl-1.8.1-cp38-cp38-win32.whl", hash = "sha256:c1445a0c562ed561d06d8cbc5c8916c6008a31c60bc3655cdd2de1d3bf5174a0"}, + {file = "yarl-1.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:56c11efb0a89700987d05597b08a1efcd78d74c52febe530126785e1b1a285f4"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e80ed5a9939ceb6fda42811542f31c8602be336b1fb977bccb012e83da7e4936"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6afb336e23a793cd3b6476c30f030a0d4c7539cd81649683b5e0c1b0ab0bf350"}, + {file = "yarl-1.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c322cbaa4ed78a8aac89b2174a6df398faf50e5fc12c4c191c40c59d5e28357"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fae37373155f5ef9b403ab48af5136ae9851151f7aacd9926251ab26b953118b"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5395da939ffa959974577eff2cbfc24b004a2fb6c346918f39966a5786874e54"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:076eede537ab978b605f41db79a56cad2e7efeea2aa6e0fa8f05a26c24a034fb"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d1a50e461615747dd93c099f297c1994d472b0f4d2db8a64e55b1edf704ec1c"}, + {file = "yarl-1.8.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7de89c8456525650ffa2bb56a3eee6af891e98f498babd43ae307bd42dca98f6"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4a88510731cd8d4befaba5fbd734a7dd914de5ab8132a5b3dde0bbd6c9476c64"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:2d93a049d29df172f48bcb09acf9226318e712ce67374f893b460b42cc1380ae"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:21ac44b763e0eec15746a3d440f5e09ad2ecc8b5f6dcd3ea8cb4773d6d4703e3"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:d0272228fabe78ce00a3365ffffd6f643f57a91043e119c289aaba202f4095b0"}, + {file = "yarl-1.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:99449cd5366fe4608e7226c6cae80873296dfa0cde45d9b498fefa1de315a09e"}, + {file = "yarl-1.8.1-cp39-cp39-win32.whl", hash = "sha256:8b0af1cf36b93cee99a31a545fe91d08223e64390c5ecc5e94c39511832a4bb6"}, + {file = "yarl-1.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:de49d77e968de6626ba7ef4472323f9d2e5a56c1d85b7c0e2a190b2173d3b9be"}, + {file = "yarl-1.8.1.tar.gz", hash = "sha256:af887845b8c2e060eb5605ff72b6f2dd2aab7a761379373fd89d314f4752abbf"}, +] +zerorpc = [] +zipp = [ + {file = "zipp-3.9.0-py3-none-any.whl", hash = "sha256:972cfa31bc2fedd3fa838a51e9bc7e64b7fb725a8c00e7431554311f180e9980"}, + {file = "zipp-3.9.0.tar.gz", hash = "sha256:3a7af91c3db40ec72dd9d154ae18e008c69efe8ca88dde4f9a731bb82fe2f9eb"}, +] +zope-event = [ + {file = "zope.event-4.5.0-py2.py3-none-any.whl", hash = "sha256:2666401939cdaa5f4e0c08cf7f20c9b21423b95e88f4675b1443973bdb080c42"}, + {file = "zope.event-4.5.0.tar.gz", hash = "sha256:5e76517f5b9b119acf37ca8819781db6c16ea433f7e2062c4afc2b6fbedb1330"}, +] +zope-interface = [ + {file = "zope.interface-5.5.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:2cb3003941f5f4fa577479ac6d5db2b940acb600096dd9ea9bf07007f5cab46f"}, + {file = "zope.interface-5.5.0-cp27-cp27m-win32.whl", hash = "sha256:8c791f4c203ccdbcda588ea4c8a6e4353e10435ea48ddd3d8734a26fe9714cba"}, + {file = "zope.interface-5.5.0-cp27-cp27m-win_amd64.whl", hash = "sha256:3eedf3d04179774d750e8bb4463e6da350956a50ed44d7b86098e452d7ec385e"}, + {file = "zope.interface-5.5.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:58a66c2020a347973168a4a9d64317bac52f9fdfd3e6b80b252be30da881a64e"}, + {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da7912ae76e1df6a1fb841b619110b1be4c86dfb36699d7fd2f177105cdea885"}, + {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:423c074e404f13e6fa07f4454f47fdbb38d358be22945bc812b94289d9142374"}, + {file = "zope.interface-5.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7bdcec93f152e0e1942102537eed7b166d6661ae57835b20a52a2a3d6a3e1bf3"}, + {file = "zope.interface-5.5.0-cp310-cp310-win32.whl", hash = "sha256:03f5ae315db0d0de668125d983e2a819a554f3fdb2d53b7e934e3eb3c3c7375d"}, + {file = "zope.interface-5.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:8b9f153208d74ccfa25449a0c6cb756ab792ce0dc99d9d771d935f039b38740c"}, + {file = "zope.interface-5.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeac590cce44e68ee8ad0b8ecf4d7bf15801f102d564ca1b0eb1f12f584ee656"}, + {file = "zope.interface-5.5.0-cp35-cp35m-win32.whl", hash = "sha256:7d9ec1e6694af39b687045712a8ad14ddcb568670d5eb1b66b48b98b9312afba"}, + {file = "zope.interface-5.5.0-cp35-cp35m-win_amd64.whl", hash = "sha256:d18fb0f6c8169d26044128a2e7d3c39377a8a151c564e87b875d379dbafd3930"}, + {file = "zope.interface-5.5.0-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0eb2b3e84f48dd9cfc8621c80fba905d7e228615c67f76c7df7c716065669bb6"}, + {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:df6593e150d13cfcce69b0aec5df7bc248cb91e4258a7374c129bb6d56b4e5ca"}, + {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:9dc4493aa3d87591e3d2bf1453e25b98038c839ca8e499df3d7106631b66fe83"}, + {file = "zope.interface-5.5.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5c6023ae7defd052cf76986ce77922177b0c2f3913bea31b5b28fbdf6cb7099e"}, + {file = "zope.interface-5.5.0-cp36-cp36m-win32.whl", hash = "sha256:a69c28d85bb7cf557751a5214cb3f657b2b035c8c96d71080c1253b75b79b69b"}, + {file = "zope.interface-5.5.0-cp36-cp36m-win_amd64.whl", hash = "sha256:85dd6dd9aaae7a176948d8bb62e20e2968588fd787c29c5d0d964ab475168d3d"}, + {file = "zope.interface-5.5.0-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:970661ece2029915b8f7f70892e88404340fbdefd64728380cad41c8dce14ff4"}, + {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e3495bb0cdcea212154e558082c256f11b18031f05193ae2fb85d048848db14"}, + {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:3f68404edb1a4fb6aa8a94675521ca26c83ebbdbb90e894f749ae0dc4ca98418"}, + {file = "zope.interface-5.5.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:740f3c1b44380658777669bcc42f650f5348e53797f2cee0d93dc9b0f9d7cc69"}, + {file = "zope.interface-5.5.0-cp37-cp37m-win32.whl", hash = "sha256:006f8dd81fae28027fc28ada214855166712bf4f0bfbc5a8788f9b70982b9437"}, + {file = "zope.interface-5.5.0-cp37-cp37m-win_amd64.whl", hash = "sha256:43490ad65d4c64e45a30e51a2beb7a6b63e1ff395302ad22392224eb618476d6"}, + {file = "zope.interface-5.5.0-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f70726b60009433111fe9928f5d89cbb18962411d33c45fb19eb81b9bbd26fcd"}, + {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cfa614d049667bed1c737435c609c0956c5dc0dbafdc1145ee7935e4658582cb"}, + {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:58a975f89e4584d0223ab813c5ba4787064c68feef4b30d600f5e01de90ae9ce"}, + {file = "zope.interface-5.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:37ec9ade9902f412cc7e7a32d71f79dec3035bad9bd0170226252eed88763c48"}, + {file = "zope.interface-5.5.0-cp38-cp38-win32.whl", hash = "sha256:be11fce0e6af6c0e8d93c10ef17b25aa7c4acb7ec644bff2596c0d639c49e20f"}, + {file = "zope.interface-5.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:cbbf83914b9a883ab324f728de869f4e406e0cbcd92df7e0a88decf6f9ab7d5a"}, + {file = "zope.interface-5.5.0-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:26c1456520fdcafecc5765bec4783eeafd2e893eabc636908f50ee31fe5c738c"}, + {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:47ff078734a1030c48103422a99e71a7662d20258c00306546441adf689416f7"}, + {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:687cab7f9ae18d2c146f315d0ca81e5ffe89a139b88277afa70d52f632515854"}, + {file = "zope.interface-5.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d80f6236b57a95eb19d5e47eb68d0296119e1eff6deaa2971ab8abe3af918420"}, + {file = "zope.interface-5.5.0-cp39-cp39-win32.whl", hash = "sha256:9cdc4e898d3b1547d018829fd4a9f403e52e51bba24be0fbfa37f3174e1ef797"}, + {file = "zope.interface-5.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:6566b3d2657e7609cd8751bcb1eab1202b1692a7af223035a5887d64bb3a2f3b"}, + {file = "zope.interface-5.5.0.tar.gz", hash = "sha256:700ebf9662cf8df70e2f0cb4988e078c53f65ee3eefd5c9d80cf988c4175c8e3"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..47994779b2 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,182 @@ +[tool.poetry] +name = "openpilot" +version = "0.1.0" +description = "an open source driver assistance system" +authors = ["Vehicle Researcher "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/commaai/openpilot" +documentation = "https://docs.comma.ai" + + +[tool.poetry.dependencies] +python = "~3.8" +atomicwrites = "^1.4.0" +casadi = { version = "==3.5.5", platform = "linux" } +cffi = "^1.15.1" +crcmod = "^1.7" +cryptography = "^37.0.4" +Cython = "^0.29.30" +flake8 = "^4.0.1" +Flask = "^2.1.2" +future-fstrings = "^1.2.0" # for acados +gunicorn = "^20.1.0" +hatanaka = "==2.4" +hexdump = "^3.3" +Jinja2 = "^3.1.2" +json-rpc = "^1.13.0" +libusb1 = "^3.0.0" +nose = "^1.3.7" +numpy = "^1.23.0" +onnx = "^1.12.0" +onnxruntime-gpu = { version = "^1.11.1", platform = "linux" } +pillow = "^9.2.0" +poetry = "==1.2.2" +protobuf = "==3.20.1" +psutil = "^5.9.1" +pycapnp = "==1.1.0" +pycryptodome = "^3.15.0" +PyJWT = "^2.5.0" +pylint = "^2.15.4" +pyopencl = "^2022.2.4" +pyserial = "^3.5" +python-dateutil = "^2.8.2" +PyYAML = "^6.0" +pyzmq = "^23.2.0" +requests = "^2.28.1" +scons = "^4.3.0" +sentry-sdk = "^1.6.0" +setproctitle = "^1.2.3" +six = "^1.16.0" +smbus2 = "^0.4.2" +sounddevice = "^0.4.5" +spidev = { version = "^3.6", platform = "linux" } +spidev2 = { version = "^0.9.0", platform = "linux" } +sympy = "^1.10.1" +timezonefinder = "^6.0.1" +tqdm = "^4.64.0" +urllib3 = "^1.26.10" +utm = "^0.7.0" +websocket_client = "^1.3.3" +polyline = "^1.4.0" + + +[tool.poetry.group.dev.dependencies] +av = "^9.2.0" +azure-storage-blob = "~2.1" +breathe = "^4.34.0" +carla = { version = "==0.9.13", platform = "linux" } +control = "^0.9.2" +coverage = "^6.4.1" +dictdiffer = "^0.9.0" +fastcluster = "^1.2.6" +ft4222 = "^1.4.1" +hexdump = "^3.3" +hypothesis = "==6.46.7" +inputs = "^0.5" +lru-dict = "^1.1.7" +lxml = "^4.9.1" +markdown-it-py = "^2.1.0" +matplotlib = "^3.5.2" +mpld3 = "^0.5.8" +mypy = "^0.961" +myst-parser = "^0.18.0" +natsort = "^8.1.0" +numpy = "^1.23.0" +opencv-python-headless = { url = "https://github.com/commaai/opencv-python-builder/releases/download/4.5.5.64%2Bcu118/opencv_python_headless-4.5.5.64-cp38-cp38-manylinux_2_31_x86_64.whl", platform = "linux" } +pandas = "^1.4.3" +parameterized = "^0.8.1" +paramiko = "^2.11.0" +pprofile = "^2.1.0" +pre-commit = "^2.19.0" +pycurl = "^7.45.1" +pygame = "^2.1.2" +pyprof2calltree = "^1.4.5" +pytest = "^7.1.2" +pytest-xdist = "^2.5.0" +reverse_geocoder = "^1.5.1" +scipy = "^1.8.1" +sphinx = "^5.0.2" +sphinx-rtd-theme = "^1.0.0" +sphinx-sitemap = "^2.2.0" +subprocess32 = "^3.5.4" +tabulate = "^0.8.10" +tenacity = "^8.0.1" +types-atomicwrites = "^1.4.5" +types-certifi = "^2021.10.8" +types-pycurl = "^7.45.1" +types-PyYAML = "^6.0" +types-requests = "^2.28.11" +types-tabulate = "^0.8.10" + + +[tool.poetry.group.xx] +optional = true + +[tool.poetry.group.xx.dependencies] +aenum = "^3.1.11" +aiohttp = "^3.8.1" +albumentations = "^1.2.1" +apex = { url = "https://github.com/commaai/apex/releases/download/pytorch2.0.0%2Bcu11.8/apex-0.1-cp38-cp38-linux_x86_64.whl" } +azure-cli-core = "^2.38.0" +azure-common = "^1.1.28" +azure-core = "^1.24.2" +azure-nspkg = "~3.0" +azure-storage-common = "~2.1" +azure-storage-nspkg = "~3.1" +blosc = "==1.9.2" +cloudpickle = "^2.1.0" +configargparse = "^1.5.3" +cupy-cuda11x = "^11.6.0" +datadog = "^0.44.0" +dotmap = "^1.3.30" +einops = "^0.5.0" +elasticsearch = "^8.3.1" +Flask-Cors = "^3.0.10" +Flask-SocketIO = "^5.2.0" +GeoAlchemy2 = "^0.12.1" +imageio = "^2.19.5" +influxdb-client = "^1.30.0" +ipykernel = "^6.15.1" +ipython = "^8.4.0" +joblib = "^1.1.0" +json-logging-py = "^0.2" +jupyter = "^1.0.0" +jupyterlab = "^3.4.4" +jupyterlab-vim = "^0.15.1" +Markdown = "^3.4.1" +msgpack-python = "^0.5.6" +networkx = "~2.8" +nvidia-ml-py3 = "^7.352.0" +onnx2torch = "^1.5.4" +onnxoptimizer = "^0.3.1" +osmium = "^3.3.0" +pillow-avif-plugin = "^1.2.2" +pipenv = "==2022.10.12" +plotly = "^5.9.0" +pycuda = "^2022.1" +Pygments = "^2.12.0" +PyMySQL = "~0.9" +pyproj = "^3.3.1" +python-logstash = "^0.4.8" +redis = "^4.3.4" +s2sphere = "^0.2.5" +scikit-image = "^0.19.3" +scikit-learn = "^1.1.1" +segmentation-models-pytorch = "==0.2.1" +simplejson = "^3.17.6" +SQLAlchemy = "^1.4.39" +torch = { url = "https://download.pytorch.org/whl/cu118/torch-2.0.0%2Bcu118-cp38-cp38-linux_x86_64.whl" } +torchsummary = "^1.5.1" +torchvision = { url = "https://download.pytorch.org/whl/cu118/torchvision-0.15.1%2Bcu118-cp38-cp38-linux_x86_64.whl" } +triton = "^2.0.0" +Werkzeug = "^2.1.2" +zerorpc = { git = "https://github.com/commaai/zerorpc-python.git", branch = "master" } +omegaconf = "^2.3.0" +osmnx = "==1.2.2" +tritonclient = {version = "2.28.0", extras = ["http"]} + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/release/build_release.sh b/release/build_release.sh index 80106eefb2..60f81fce06 100755 --- a/release/build_release.sh +++ b/release/build_release.sh @@ -11,15 +11,19 @@ SOURCE_DIR="$(git rev-parse --show-toplevel)" if [ -f /TICI ]; then FILES_SRC="release/files_tici" - RELEASE_BRANCH=release3-staging - DASHCAM_BRANCH=dashcam3-staging else - exit 0 + echo "no release files set" + exit 1 fi +if [ -z "$RELEASE_BRANCH" ]; then + echo "RELEASE_BRANCH is not set" + exit 1 +fi + + # set git identity source $DIR/identity.sh -export GIT_SSH_COMMAND="ssh -i /data/gitkey" echo "[-] Setting up repo T=$SECONDS" rm -rf $BUILD_DIR @@ -27,7 +31,6 @@ mkdir -p $BUILD_DIR cd $BUILD_DIR git init git remote add origin git@github.com:commaai/openpilot.git -git fetch origin $RELEASE_BRANCH git checkout --orphan $RELEASE_BRANCH # do the files copy @@ -48,7 +51,6 @@ echo "#define COMMA_VERSION \"$VERSION-release\"" > common/version.h echo "[-] committing version $VERSION T=$SECONDS" git add -f . git commit -a -m "openpilot v$VERSION release" -git branch --set-upstream-to=origin/$RELEASE_BRANCH # Build panda firmware pushd panda/ @@ -105,10 +107,12 @@ RELEASE=1 selfdrive/test/test_onroad.py selfdrive/car/tests/test_car_interfaces.py rm -rf $TEST_FILES -if [ ! -z "$PUSH" ]; then - echo "[-] pushing T=$SECONDS" - git push -f origin $RELEASE_BRANCH +if [ ! -z "$RELEASE_BRANCH" ]; then + echo "[-] pushing release T=$SECONDS" + git push -f origin $RELEASE_BRANCH:$RELEASE_BRANCH +fi +if [ ! -z "$DASHCAM_BRANCH" ]; then # Create dashcam git rm selfdrive/car/*/carcontroller.py git commit -m "create dashcam release from release" diff --git a/release/check-submodules.sh b/release/check-submodules.sh index 182042e6b4..5f4e307e49 100755 --- a/release/check-submodules.sh +++ b/release/check-submodules.sh @@ -1,7 +1,7 @@ #!/bin/bash while read hash submodule ref; do - git -C $submodule fetch --depth 100 origin master + git -C $submodule fetch --depth 1000 origin master git -C $submodule branch -r --contains $hash | grep "origin/master" if [ "$?" -eq 0 ]; then echo "$submodule ok" diff --git a/release/files_common b/release/files_common index 07ffaf8501..a20f71018b 100644 --- a/release/files_common +++ b/release/files_common @@ -71,6 +71,7 @@ selfdrive/rtshield.py selfdrive/statsd.py system/logmessaged.py +system/micd.py system/swaglog.py system/version.py @@ -90,8 +91,12 @@ selfdrive/boardd/boardd_api_impl.pyx selfdrive/boardd/can_list_to_can_capnp.cc selfdrive/boardd/panda.cc selfdrive/boardd/panda.h +selfdrive/boardd/spi.cc +selfdrive/boardd/panda_comms.h +selfdrive/boardd/panda_comms.cc selfdrive/boardd/set_time.py selfdrive/boardd/pandad.py +selfdrive/boardd/tests/test_boardd_loopback.py selfdrive/car/__init__.py selfdrive/car/docs_definitions.py @@ -141,6 +146,7 @@ selfdrive/debug/vw_mqb_config.py common/SConscript common/version.h +common/prefix.h common/swaglog.h common/swaglog.cc common/statlog.h @@ -186,8 +192,6 @@ selfdrive/controls/lib/pid.py selfdrive/controls/lib/radar_helpers.py selfdrive/controls/lib/vehicle_model.py -selfdrive/controls/lib/cluster/* - selfdrive/controls/lib/lateral_mpc_lib/.gitignore selfdrive/controls/lib/longitudinal_mpc_lib/.gitignore selfdrive/controls/lib/lateral_mpc_lib/* @@ -212,21 +216,19 @@ system/hardware/tici/amplifier.py system/hardware/tici/updater system/hardware/tici/iwlist.py system/hardware/pc/__init__.py +system/hardware/pc/hardware.h system/hardware/pc/hardware.py -selfdrive/locationd/__init__.py -selfdrive/locationd/.gitignore -selfdrive/locationd/SConscript -selfdrive/locationd/ubloxd.cc -selfdrive/locationd/ublox_msg.cc -selfdrive/locationd/ublox_msg.h -selfdrive/locationd/generated/ubx.cpp -selfdrive/locationd/generated/ubx.h -selfdrive/locationd/generated/gps.cpp -selfdrive/locationd/generated/gps.h +system/ubloxd/.gitignore +system/ubloxd/SConscript +system/ubloxd/generated/* +system/ubloxd/*.h +system/ubloxd/*.cc +selfdrive/locationd/__init__.py +selfdrive/locationd/SConscript +selfdrive/locationd/.gitignore selfdrive/locationd/laikad.py -selfdrive/locationd/laikad_helpers.py selfdrive/locationd/locationd.h selfdrive/locationd/locationd.cc selfdrive/locationd/paramsd.py @@ -252,36 +254,36 @@ system/proclogd/main.cc system/proclogd/proclog.cc system/proclogd/proclog.h -selfdrive/loggerd/.gitignore -selfdrive/loggerd/SConscript -selfdrive/loggerd/encoder/encoder.cc -selfdrive/loggerd/encoder/encoder.h -selfdrive/loggerd/encoder/v4l_encoder.cc -selfdrive/loggerd/encoder/v4l_encoder.h -selfdrive/loggerd/video_writer.cc -selfdrive/loggerd/video_writer.h -selfdrive/loggerd/logger.cc -selfdrive/loggerd/logger.h -selfdrive/loggerd/loggerd.cc -selfdrive/loggerd/loggerd.h -selfdrive/loggerd/encoderd.cc -selfdrive/loggerd/bootlog.cc -selfdrive/loggerd/encoder/ffmpeg_encoder.cc -selfdrive/loggerd/encoder/ffmpeg_encoder.h +system/loggerd/.gitignore +system/loggerd/SConscript +system/loggerd/encoder/encoder.cc +system/loggerd/encoder/encoder.h +system/loggerd/encoder/v4l_encoder.cc +system/loggerd/encoder/v4l_encoder.h +system/loggerd/video_writer.cc +system/loggerd/video_writer.h +system/loggerd/logger.cc +system/loggerd/logger.h +system/loggerd/loggerd.cc +system/loggerd/loggerd.h +system/loggerd/encoderd.cc +system/loggerd/bootlog.cc +system/loggerd/encoder/ffmpeg_encoder.cc +system/loggerd/encoder/ffmpeg_encoder.h -selfdrive/loggerd/__init__.py -selfdrive/loggerd/config.py -selfdrive/loggerd/uploader.py -selfdrive/loggerd/deleter.py -selfdrive/loggerd/xattr_cache.py +system/loggerd/__init__.py +system/loggerd/config.py +system/loggerd/uploader.py +system/loggerd/deleter.py +system/loggerd/xattr_cache.py -selfdrive/sensord/SConscript -selfdrive/sensord/libdiag.h -selfdrive/sensord/sensors_qcom2.cc -selfdrive/sensord/sensors/*.cc -selfdrive/sensord/sensors/*.h -selfdrive/sensord/sensord -selfdrive/sensord/pigeond.py +system/sensord/.gitignore +system/sensord/SConscript +system/sensord/sensors_qcom2.cc +system/sensord/sensors/*.cc +system/sensord/sensors/*.h +system/sensord/sensord +system/sensord/pigeond.py selfdrive/thermald/thermald.py selfdrive/thermald/power_monitoring.py @@ -317,12 +319,17 @@ selfdrive/ui/qt/widgets/*.cc selfdrive/ui/qt/widgets/*.h selfdrive/ui/qt/maps/*.cc selfdrive/ui/qt/maps/*.h +selfdrive/ui/qt/setup/*.cc +selfdrive/ui/qt/setup/*.h + +selfdrive/ui/installer/*.cc +selfdrive/ui/installer/*.h +selfdrive/ui/installer/*.cc system/camerad/SConscript system/camerad/main.cc system/camerad/snapshot/* -system/camerad/include/* system/camerad/cameras/camera_common.h system/camerad/cameras/camera_common.cc system/camerad/cameras/sensor2_i2c.h @@ -344,20 +351,28 @@ selfdrive/manager/test/test_manager.py selfdrive/modeld/__init__.py selfdrive/modeld/SConscript selfdrive/modeld/modeld.cc +selfdrive/modeld/navmodeld.cc selfdrive/modeld/dmonitoringmodeld.cc selfdrive/modeld/constants.py selfdrive/modeld/modeld +selfdrive/modeld/navmodeld selfdrive/modeld/dmonitoringmodeld selfdrive/modeld/models/commonmodel.cc selfdrive/modeld/models/commonmodel.h + selfdrive/modeld/models/driving.cc selfdrive/modeld/models/driving.h +selfdrive/modeld/models/supercombo.onnx + selfdrive/modeld/models/dmonitoring.cc selfdrive/modeld/models/dmonitoring.h -selfdrive/modeld/models/supercombo.onnx selfdrive/modeld/models/dmonitoring_model_q.dlc +selfdrive/modeld/models/nav.cc +selfdrive/modeld/models/nav.h +selfdrive/modeld/models/navmodel_q.dlc + selfdrive/modeld/transforms/loadyuv.cc selfdrive/modeld/transforms/loadyuv.h selfdrive/modeld/transforms/loadyuv.cl @@ -370,7 +385,6 @@ selfdrive/modeld/thneed/thneed.h selfdrive/modeld/thneed/thneed_common.cc selfdrive/modeld/thneed/thneed_qcom2.cc selfdrive/modeld/thneed/serialize.cc -selfdrive/modeld/thneed/include/* selfdrive/modeld/runners/snpemodel.cc selfdrive/modeld/runners/snpemodel.h @@ -398,8 +412,11 @@ selfdrive/assets/offroad/* selfdrive/assets/sounds/* selfdrive/assets/training/* +third_party/.gitignore third_party/SConscript +third_party/cluster/* + third_party/linux/** third_party/opencl/** @@ -422,15 +439,14 @@ third_party/snpe/dsp** third_party/acados/x86_64/** third_party/acados/larch64/** third_party/acados/include/** +third_party/acados/acados_template/** +third_party/bootstrap/** third_party/qt5/larch64/bin/** scripts/update_now.sh scripts/stop_updater.sh -pyextra/.gitignore -pyextra/acados_template/** - rednose/.gitignore rednose/** laika/** @@ -480,6 +496,7 @@ cereal/visionipc/*.pxd panda/.gitignore panda/__init__.py +panda/SConscript panda/board/** panda/certs/** panda/crypto/** @@ -563,15 +580,13 @@ opendbc/tesla_radar.dbc opendbc/tesla_powertrain.dbc tinygrad_repo/openpilot/compile.py -tinygrad_repo/accel/opencl/* tinygrad_repo/extra/onnx.py +tinygrad_repo/extra/onnx_ops.py +tinygrad_repo/extra/thneed.py tinygrad_repo/extra/utils.py -tinygrad_repo/tinygrad/llops/ops_gpu.py -tinygrad_repo/tinygrad/llops/ops_opencl.py -tinygrad_repo/tinygrad/helpers.py -tinygrad_repo/tinygrad/mlops.py -tinygrad_repo/tinygrad/ops.py -tinygrad_repo/tinygrad/shapetracker.py -tinygrad_repo/tinygrad/tensor.py -tinygrad_repo/tinygrad/nn/__init__.py -tinygrad_repo/tinygrad/nn/optim.py +tinygrad_repo/tinygrad/codegen/ast.py +tinygrad_repo/tinygrad/codegen/gpu.py +tinygrad_repo/tinygrad/nn/* +tinygrad_repo/tinygrad/runtime/ops_gpu.py +tinygrad_repo/tinygrad/shape/* +tinygrad_repo/tinygrad/*.py diff --git a/release/files_tici b/release/files_tici index c8abd720d5..b4e0c50516 100644 --- a/release/files_tici +++ b/release/files_tici @@ -13,7 +13,7 @@ system/camerad/cameras/camera_util.cc system/camerad/cameras/camera_util.h system/camerad/cameras/real_debayer.cl -selfdrive/sensord/rawgps/* +system/sensord/rawgps/* selfdrive/ui/qt/spinner_larch64 selfdrive/ui/qt/text_larch64 diff --git a/scripts/cell.sh b/scripts/cell.sh index cae701eccc..3623fe5b16 100755 --- a/scripts/cell.sh +++ b/scripts/cell.sh @@ -1,5 +1,3 @@ #!/usr/bin/bash - -nmcli connection modify --temporary lte ipv4.route-metric 1 -nmcli connection modify --temporary lte ipv6.route-metric 1 -nmcli con up lte +nmcli connection modify --temporary lte ipv4.route-metric 1 ipv6.route-metric 1 +nmcli con up --wait --ask lte diff --git a/selfdrive/assets/assets.qrc b/selfdrive/assets/assets.qrc index 39be41aa65..79a1a1e272 100644 --- a/selfdrive/assets/assets.qrc +++ b/selfdrive/assets/assets.qrc @@ -1,5 +1,6 @@ + ../../third_party/bootstrap/bootstrap-icons.svg img_continue_triangle.svg img_circled_check.svg img_circled_slash.svg diff --git a/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf new file mode 100644 index 0000000000..a6ba5529af Binary files /dev/null and b/selfdrive/assets/fonts/JetBrainsMono-Medium.ttf differ diff --git a/selfdrive/assets/img_couch.svg b/selfdrive/assets/img_couch.svg new file mode 100644 index 0000000000..2e86012809 --- /dev/null +++ b/selfdrive/assets/img_couch.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_driver_face.png b/selfdrive/assets/img_driver_face.png index ddde478cd7..e2d943e537 100644 Binary files a/selfdrive/assets/img_driver_face.png and b/selfdrive/assets/img_driver_face.png differ diff --git a/selfdrive/assets/img_driver_face_static.png b/selfdrive/assets/img_driver_face_static.png new file mode 100644 index 0000000000..d8bc5f1371 Binary files /dev/null and b/selfdrive/assets/img_driver_face_static.png differ diff --git a/selfdrive/assets/img_experimental.svg b/selfdrive/assets/img_experimental.svg new file mode 100644 index 0000000000..0eaec3b3cd --- /dev/null +++ b/selfdrive/assets/img_experimental.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/selfdrive/assets/img_experimental_grey.svg b/selfdrive/assets/img_experimental_grey.svg new file mode 100644 index 0000000000..dc87105ac5 --- /dev/null +++ b/selfdrive/assets/img_experimental_grey.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/img_experimental_white.svg b/selfdrive/assets/img_experimental_white.svg new file mode 100644 index 0000000000..ae4f18fde2 --- /dev/null +++ b/selfdrive/assets/img_experimental_white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/selfdrive/assets/navigation/default_marker.svg b/selfdrive/assets/navigation/default_marker.svg new file mode 100644 index 0000000000..43d5290a96 --- /dev/null +++ b/selfdrive/assets/navigation/default_marker.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 5b351ca0f5..7cb6d5b845 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +from __future__ import annotations + import base64 import bz2 import hashlib @@ -14,14 +16,15 @@ import sys import tempfile import threading import time -from collections import namedtuple +from dataclasses import asdict, dataclass, replace from datetime import datetime from functools import partial -from typing import Any, Dict +from queue import Queue +from typing import BinaryIO, Callable, Dict, List, Optional, Set, Union, cast import requests from jsonrpc import JSONRPCResponseManager, dispatcher -from websocket import (ABNF, WebSocketException, WebSocketTimeoutException, +from websocket import (ABNF, WebSocket, WebSocketException, WebSocketTimeoutException, create_connection) import cereal.messaging as messaging @@ -33,8 +36,8 @@ from common.file_helpers import CallbackReader from common.params import Params from common.realtime import sec_since_boot, set_core_affinity from system.hardware import HARDWARE, PC, AGNOS -from selfdrive.loggerd.config import ROOT -from selfdrive.loggerd.xattr_cache import getxattr, setxattr +from system.loggerd.config import ROOT +from system.loggerd.xattr_cache import getxattr, setxattr from selfdrive.statsd import STATS_DIR from system.swaglog import SWAGLOG_DIR, cloudlog from system.version import get_commit, get_origin, get_short_branch, get_version @@ -54,19 +57,54 @@ WS_FRAME_SIZE = 4096 NetworkType = log.DeviceState.NetworkType +UploadFileDict = Dict[str, Union[str, int, float, bool]] +UploadItemDict = Dict[str, Union[str, bool, int, float, Dict[str, str]]] + +UploadFilesToUrlResponse = Dict[str, Union[int, List[UploadItemDict], List[str]]] + + +@dataclass +class UploadFile: + fn: str + url: str + headers: Dict[str, str] + allow_cellular: bool + + @classmethod + def from_dict(cls, d: Dict) -> UploadFile: + return cls(d.get("fn", ""), d.get("url", ""), d.get("headers", {}), d.get("allow_cellular", False)) + + +@dataclass +class UploadItem: + path: str + url: str + headers: Dict[str, str] + created_at: int + id: Optional[str] + retry_count: int = 0 + current: bool = False + progress: float = 0 + allow_cellular: bool = False + + @classmethod + def from_dict(cls, d: Dict) -> UploadItem: + return cls(d["path"], d["url"], d["headers"], d["created_at"], d["id"], d["retry_count"], d["current"], + d["progress"], d["allow_cellular"]) + + dispatcher["echo"] = lambda s: s -recv_queue: Any = queue.Queue() -send_queue: Any = queue.Queue() -upload_queue: Any = queue.Queue() -low_priority_send_queue: Any = queue.Queue() -log_recv_queue: Any = queue.Queue() -cancelled_uploads: Any = set() -UploadItem = namedtuple('UploadItem', ['path', 'url', 'headers', 'created_at', 'id', 'retry_count', 'current', 'progress', 'allow_cellular'], defaults=(0, False, 0, False)) +recv_queue: Queue[str] = queue.Queue() +send_queue: Queue[str] = queue.Queue() +upload_queue: Queue[UploadItem] = queue.Queue() +low_priority_send_queue: Queue[str] = queue.Queue() +log_recv_queue: Queue[str] = queue.Queue() +cancelled_uploads: Set[str] = set() -cur_upload_items: Dict[int, Any] = {} +cur_upload_items: Dict[int, Optional[UploadItem]] = {} -def strip_bz2_extension(fn): +def strip_bz2_extension(fn: str) -> str: if fn.endswith('.bz2'): return fn[:-4] return fn @@ -76,29 +114,30 @@ class AbortTransferException(Exception): pass -class UploadQueueCache(): +class UploadQueueCache: params = Params() @staticmethod - def initialize(upload_queue): + def initialize(upload_queue: Queue[UploadItem]) -> None: 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)) + upload_queue.put(UploadItem.from_dict(item)) except Exception: cloudlog.exception("athena.UploadQueueCache.initialize.exception") @staticmethod - def cache(upload_queue): + def cache(upload_queue: Queue[UploadItem]) -> None: try: - items = [i._asdict() for i in upload_queue.queue if i.id not in cancelled_uploads] + queue: List[Optional[UploadItem]] = list(upload_queue.queue) + items = [asdict(i) for i in queue if i is not None and (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): +def handle_long_poll(ws: WebSocket) -> None: end_event = threading.Event() threads = [ @@ -126,7 +165,7 @@ def handle_long_poll(ws): thread.join() -def jsonrpc_handler(end_event): +def jsonrpc_handler(end_event: threading.Event) -> None: dispatcher["startLocalProxy"] = partial(startLocalProxy, end_event) while not end_event.is_set(): try: @@ -147,11 +186,12 @@ def jsonrpc_handler(end_event): def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = True) -> None: - if cur_upload_items[tid].retry_count < MAX_RETRY_COUNT: - item = cur_upload_items[tid] + item = cur_upload_items[tid] + if item is not None and item.retry_count < MAX_RETRY_COUNT: new_retry_count = item.retry_count + 1 if increase_count else item.retry_count - item = item._replace( + item = replace( + item, retry_count=new_retry_count, progress=0, current=False @@ -175,44 +215,44 @@ def upload_handler(end_event: threading.Event) -> None: cur_upload_items[tid] = None try: - cur_upload_items[tid] = upload_queue.get(timeout=1)._replace(current=True) + cur_upload_items[tid] = item = replace(upload_queue.get(timeout=1), current=True) - if cur_upload_items[tid].id in cancelled_uploads: - cancelled_uploads.remove(cur_upload_items[tid].id) + if item.id in cancelled_uploads: + cancelled_uploads.remove(item.id) continue # Remove item if too old - age = datetime.now() - datetime.fromtimestamp(cur_upload_items[tid].created_at / 1000) + age = datetime.now() - datetime.fromtimestamp(item.created_at / 1000) if age.total_seconds() > MAX_AGE: - cloudlog.event("athena.upload_handler.expired", item=cur_upload_items[tid], error=True) + cloudlog.event("athena.upload_handler.expired", item=item, error=True) continue # Check if uploading over metered connection is allowed sm.update(0) metered = sm['deviceState'].networkMetered network_type = sm['deviceState'].networkType.raw - if metered and (not cur_upload_items[tid].allow_cellular): + if metered and (not item.allow_cellular): retry_upload(tid, end_event, False) continue try: - def cb(sz, cur): + def cb(sz: int, cur: int) -> None: # Abort transfer if connection changed to metered after starting upload sm.update(0) metered = sm['deviceState'].networkMetered - if metered and (not cur_upload_items[tid].allow_cellular): + if metered and (not item.allow_cellular): raise AbortTransferException - cur_upload_items[tid] = cur_upload_items[tid]._replace(progress=cur / sz if sz else 1) + cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1) - fn = cur_upload_items[tid].path + fn = item.path try: sz = os.path.getsize(fn) except OSError: sz = -1 - cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=cur_upload_items[tid].retry_count) - response = _do_upload(cur_upload_items[tid], cb) + cloudlog.event("athena.upload_handler.upload_start", fn=fn, sz=sz, network_type=network_type, metered=metered, retry_count=item.retry_count) + response = _do_upload(item, cb) if response.status_code not in (200, 201, 401, 403, 412): cloudlog.event("athena.upload_handler.retry", status_code=response.status_code, fn=fn, sz=sz, network_type=network_type, metered=metered) @@ -234,7 +274,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item, callback=None): +def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> requests.Response: path = upload_item.path compress = False @@ -244,27 +284,25 @@ def _do_upload(upload_item, callback=None): compress = True with open(path, "rb") as f: + data: BinaryIO if compress: cloudlog.event("athena.upload_handler.compress", fn=path, fn_orig=upload_item.path) - data = bz2.compress(f.read()) - size = len(data) - data = io.BytesIO(data) + compressed = bz2.compress(f.read()) + size = len(compressed) + data = io.BytesIO(compressed) else: size = os.fstat(f.fileno()).st_size data = f - if callback: - data = CallbackReader(data, callback, size) - return requests.put(upload_item.url, - data=data, + data=CallbackReader(data, callback, size) if callback else data, headers={**upload_item.headers, 'Content-Length': str(size)}, timeout=30) # security: user should be able to request any message from their car @dispatcher.add_method -def getMessage(service=None, timeout=1000): +def getMessage(service: str, timeout: int = 1000) -> Dict: if service is None or service not in service_list: raise Exception("invalid service") @@ -274,7 +312,8 @@ def getMessage(service=None, timeout=1000): if ret is None: raise TimeoutError - return ret.to_dict() + # this is because capnp._DynamicStructReader doesn't have typing information + return cast(Dict, ret.to_dict()) @dispatcher.add_method @@ -288,7 +327,7 @@ def getVersion() -> Dict[str, str]: @dispatcher.add_method -def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=None): +def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optional[str] = None, place_details: Optional[str] = None) -> Dict[str, int]: destination = { "latitude": latitude, "longitude": longitude, @@ -300,8 +339,8 @@ def setNavDestination(latitude=0, longitude=0, place_name=None, place_details=No return {"success": 1} -def scan_dir(path, prefix): - files = list() +def scan_dir(path: str, prefix: str) -> List[str]: + files = [] # only walk directories that match the prefix # (glob and friends traverse entire dir tree) with os.scandir(path) as i: @@ -320,18 +359,18 @@ def scan_dir(path, prefix): return files @dispatcher.add_method -def listDataDirectory(prefix=''): +def listDataDirectory(prefix='') -> List[str]: return scan_dir(ROOT, prefix) @dispatcher.add_method -def reboot(): +def reboot() -> Dict[str, int]: sock = messaging.sub_sock("deviceState", timeout=1000) ret = messaging.recv_one(sock) if ret is None or ret.deviceState.started: raise Exception("Reboot unavailable") - def do_reboot(): + def do_reboot() -> None: time.sleep(2) HARDWARE.reboot() @@ -341,50 +380,53 @@ def reboot(): @dispatcher.add_method -def uploadFileToUrl(fn, url, headers): - return uploadFilesToUrls([{ +def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse: + # this is because mypy doesn't understand that the decorator doesn't change the return type + response: UploadFilesToUrlResponse = uploadFilesToUrls([{ "fn": fn, "url": url, "headers": headers, }]) + return response @dispatcher.add_method -def uploadFilesToUrls(files_data): - items = [] - failed = [] - for file in files_data: - fn = file.get('fn', '') - if len(fn) == 0 or fn[0] == '/' or '..' in fn or 'url' not in file: - failed.append(fn) +def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlResponse: + files = map(UploadFile.from_dict, files_data) + + items: List[UploadItemDict] = [] + failed: List[str] = [] + for file in files: + if len(file.fn) == 0 or file.fn[0] == '/' or '..' in file.fn or len(file.url) == 0: + failed.append(file.fn) continue - path = os.path.join(ROOT, fn) + path = os.path.join(ROOT, file.fn) if not os.path.exists(path) and not os.path.exists(strip_bz2_extension(path)): - failed.append(fn) + failed.append(file.fn) continue # Skip item if already in queue - url = file['url'].split('?')[0] + url = file.url.split('?')[0] if any(url == item['url'].split('?')[0] for item in listUploadQueue()): continue item = UploadItem( path=path, - url=file['url'], - headers=file.get('headers', {}), + url=file.url, + headers=file.headers, created_at=int(time.time() * 1000), id=None, - allow_cellular=file.get('allow_cellular', False), + allow_cellular=file.allow_cellular, ) upload_id = hashlib.sha1(str(item).encode()).hexdigest() - item = item._replace(id=upload_id) + item = replace(item, id=upload_id) upload_queue.put_nowait(item) - items.append(item._asdict()) + items.append(asdict(item)) UploadQueueCache.cache(upload_queue) - resp = {"enqueued": len(items), "items": items} + resp: UploadFilesToUrlResponse = {"enqueued": len(items), "items": items} if failed: resp["failed"] = failed @@ -392,32 +434,32 @@ def uploadFilesToUrls(files_data): @dispatcher.add_method -def listUploadQueue(): +def listUploadQueue() -> List[UploadItemDict]: items = list(upload_queue.queue) + list(cur_upload_items.values()) - return [i._asdict() for i in items if (i is not None) and (i.id not in cancelled_uploads)] + return [asdict(i) for i in items if (i is not None) and (i.id not in cancelled_uploads)] @dispatcher.add_method -def cancelUpload(upload_id): +def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]]: if not isinstance(upload_id, list): upload_id = [upload_id] uploading_ids = {item.id for item in list(upload_queue.queue)} cancelled_ids = uploading_ids.intersection(upload_id) if len(cancelled_ids) == 0: - return 404 + return {"success": 0, "error": "not found"} cancelled_uploads.update(cancelled_ids) return {"success": 1} @dispatcher.add_method -def primeActivated(activated): +def primeActivated(activated: bool) -> Dict[str, int]: return {"success": 1} @dispatcher.add_method -def setBandwithLimit(upload_speed_kbps, download_speed_kbps): +def setBandwithLimit(upload_speed_kbps: int, download_speed_kbps: int) -> Dict[str, Union[int, str]]: if not AGNOS: return {"success": 0, "error": "only supported on AGNOS"} @@ -428,7 +470,7 @@ def setBandwithLimit(upload_speed_kbps, download_speed_kbps): return {"success": 0, "error": "failed to set limit", "stdout": e.stdout, "stderr": e.stderr} -def startLocalProxy(global_end_event, remote_ws_uri, local_port): +def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]: try: if local_port not in LOCAL_PORT_WHITELIST: raise Exception("Requested local port not whitelisted") @@ -462,7 +504,7 @@ def startLocalProxy(global_end_event, remote_ws_uri, local_port): @dispatcher.add_method -def getPublicKey(): +def getPublicKey() -> Optional[str]: if not os.path.isfile(PERSIST + '/comma/id_rsa.pub'): return None @@ -471,7 +513,7 @@ def getPublicKey(): @dispatcher.add_method -def getSshAuthorizedKeys(): +def getSshAuthorizedKeys() -> str: return Params().get("GithubSshKeys", encoding='utf8') or '' @@ -486,7 +528,7 @@ def getNetworkType(): @dispatcher.add_method -def getNetworkMetered(): +def getNetworkMetered() -> bool: network_type = HARDWARE.get_network_type() return HARDWARE.get_network_metered(network_type) @@ -497,7 +539,7 @@ def getNetworks(): @dispatcher.add_method -def takeSnapshot(): +def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: from system.camerad.snapshot.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: @@ -514,16 +556,19 @@ def takeSnapshot(): raise Exception("not available while camerad is started") -def get_logs_to_send_sorted(): +def get_logs_to_send_sorted() -> List[str]: # TODO: scan once then use inotify to detect file creation/deletion curr_time = int(time.time()) logs = [] for log_entry in os.listdir(SWAGLOG_DIR): log_path = os.path.join(SWAGLOG_DIR, log_entry) + time_sent = 0 try: - time_sent = int.from_bytes(getxattr(log_path, LOG_ATTR_NAME), sys.byteorder) + value = getxattr(log_path, LOG_ATTR_NAME) + if value is not None: + time_sent = int.from_bytes(value, sys.byteorder) except (ValueError, TypeError): - time_sent = 0 + pass # assume send failed and we lost the response if sent more than one hour ago if not time_sent or curr_time - time_sent > 3600: logs.append(log_entry) @@ -531,7 +576,7 @@ def get_logs_to_send_sorted(): return sorted(logs)[:-1] -def log_handler(end_event): +def log_handler(end_event: threading.Event) -> None: if PC: return @@ -593,7 +638,7 @@ def log_handler(end_event): cloudlog.exception("athena.log_handler.exception") -def stat_handler(end_event): +def stat_handler(end_event: threading.Event) -> None: while not end_event.is_set(): last_scan = 0 curr_scan = sec_since_boot() @@ -619,7 +664,7 @@ def stat_handler(end_event): time.sleep(0.1) -def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): +def ws_proxy_recv(ws: WebSocket, local_sock: socket.socket, ssock: socket.socket, end_event: threading.Event, global_end_event: threading.Event) -> None: while not (end_event.is_set() or global_end_event.is_set()): try: data = ws.recv() @@ -638,7 +683,7 @@ def ws_proxy_recv(ws, local_sock, ssock, end_event, global_end_event): end_event.set() -def ws_proxy_send(ws, local_sock, signal_sock, end_event): +def ws_proxy_send(ws: WebSocket, local_sock: socket.socket, signal_sock: socket.socket, end_event: threading.Event) -> None: while not end_event.is_set(): try: r, _, _ = select.select((local_sock, signal_sock), (), ()) @@ -663,7 +708,7 @@ def ws_proxy_send(ws, local_sock, signal_sock, end_event): cloudlog.debug("athena.ws_proxy_send done closing sockets") -def ws_recv(ws, end_event): +def ws_recv(ws: WebSocket, end_event: threading.Event) -> None: last_ping = int(sec_since_boot() * 1e9) while not end_event.is_set(): try: @@ -685,7 +730,7 @@ def ws_recv(ws, end_event): end_event.set() -def ws_send(ws, end_event): +def ws_send(ws: WebSocket, end_event: threading.Event) -> None: while not end_event.is_set(): try: try: @@ -704,7 +749,7 @@ def ws_send(ws, end_event): end_event.set() -def backoff(retries): +def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 5e86a2e821..128fde0319 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -8,6 +8,7 @@ import time import threading import queue import unittest +from dataclasses import asdict, replace from datetime import datetime, timedelta from typing import Optional @@ -226,7 +227,7 @@ class TestAthenadMethods(unittest.TestCase): """When an upload times out or fails to connect it should be placed back in the queue""" fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) - item_no_retry = item._replace(retry_count=MAX_RETRY_COUNT) + item_no_retry = replace(item, retry_count=MAX_RETRY_COUNT) end_event = threading.Event() thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) @@ -296,7 +297,7 @@ class TestAthenadMethods(unittest.TestCase): self.assertEqual(len(items), 0) @with_http_server - def test_listUploadQueueCurrent(self, host): + def test_listUploadQueueCurrent(self, host: str): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url=f"{host}/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='', allow_cellular=True) @@ -321,7 +322,7 @@ class TestAthenadMethods(unittest.TestCase): items = dispatcher["listUploadQueue"]() self.assertEqual(len(items), 1) - self.assertDictEqual(items[0], item._asdict()) + self.assertDictEqual(items[0], asdict(item)) self.assertFalse(items[0]['current']) athenad.cancelled_uploads.add(item.id) @@ -346,7 +347,7 @@ class TestAthenadMethods(unittest.TestCase): athenad.UploadQueueCache.initialize(athenad.upload_queue) self.assertEqual(athenad.upload_queue.qsize(), 1) - self.assertDictEqual(athenad.upload_queue.queue[-1]._asdict(), item1._asdict()) + self.assertDictEqual(asdict(athenad.upload_queue.queue[-1]), asdict(item1)) @mock.patch('selfdrive.athena.athenad.create_connection') def test_startLocalProxy(self, mock_create_connection): @@ -417,5 +418,6 @@ class TestAthenadMethods(unittest.TestCase): sl = athenad.get_logs_to_send_sorted() self.assertListEqual(sl, fl[:-1]) + if __name__ == '__main__': unittest.main() diff --git a/selfdrive/boardd/SConscript b/selfdrive/boardd/SConscript index dcbea03d3c..d99e67a9f0 100644 --- a/selfdrive/boardd/SConscript +++ b/selfdrive/boardd/SConscript @@ -1,9 +1,9 @@ Import('env', 'envCython', 'common', 'cereal', 'messaging') libs = ['usb-1.0', common, cereal, messaging, 'pthread', 'zmq', 'capnp', 'kj'] -env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc'], LIBS=libs) +env.Program('boardd', ['main.cc', 'boardd.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) env.Library('libcan_list_to_can_capnp', ['can_list_to_can_capnp.cc']) envCython.Program('boardd_api_impl.so', 'boardd_api_impl.pyx', LIBS=["can_list_to_can_capnp", 'capnp', 'kj'] + envCython["LIBS"]) if GetOption('test'): - env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc'], LIBS=libs) + env.Program('tests/test_boardd_usbprotocol', ['tests/test_boardd_usbprotocol.cc', 'panda.cc', 'panda_comms.cc', 'spi.cc'], LIBS=libs) diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 2d613b68ce..4faa08d536 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -19,8 +20,6 @@ #include #include -#include - #include "cereal/gen/cpp/car.capnp.h" #include "cereal/messaging/messaging.h" #include "common/params.h" @@ -50,8 +49,8 @@ #define MAX_IR_POWER 0.5f #define MIN_IR_POWER 0.0f -#define CUTOFF_IL 200 -#define SATURATE_IL 1600 +#define CUTOFF_IL 400 +#define SATURATE_IL 1000 #define NIBBLE_TO_HEX(n) ((n) < 10 ? (n) + '0' : ((n) - 10) + 'a') using namespace std::chrono_literals; @@ -67,7 +66,7 @@ static std::string get_time_str(const struct tm &time) { bool check_all_connected(const std::vector &pandas) { for (const auto& panda : pandas) { - if (!panda->connected) { + if (!panda->connected()) { do_exit = true; return false; } @@ -107,40 +106,42 @@ void sync_time(Panda *panda, SyncTimeDir dir) { bool safety_setter_thread(std::vector pandas) { LOGD("Starting safety setter thread"); + Params p; + // there should be at least one panda connected if (pandas.size() == 0) { return false; } - // set to ELM327 for fingerprinting + // initialize to ELM327 without OBD multiplexing for fingerprinting + bool obd_multiplexing_enabled = false; for (int i = 0; i < pandas.size(); i++) { - const uint16_t safety_param = (i > 0) ? 1U : 0U; - pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); + pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); } - Params p = Params(); - - // wait for VIN to be read + // openpilot can switch between multiplexing modes for different FW queries while (true) { if (do_exit || !check_all_connected(pandas) || !ignition) { return false; } - std::string value_vin = p.get("CarVin"); - if (value_vin.size() > 0) { - // sanity check VIN format - assert(value_vin.size() == 17); - LOGW("got CarVin %s", value_vin.c_str()); + bool obd_multiplexing_requested = p.getBool("ObdMultiplexingEnabled"); + if (obd_multiplexing_requested != obd_multiplexing_enabled) { + for (int i = 0; i < pandas.size(); i++) { + const uint16_t safety_param = (i > 0 || !obd_multiplexing_requested) ? 1U : 0U; + pandas[i]->set_safety_model(cereal::CarParams::SafetyModel::ELM327, safety_param); + } + obd_multiplexing_enabled = obd_multiplexing_requested; + p.putBool("ObdMultiplexingChanged", true); + } + + if (p.getBool("FirmwareQueryDone")) { + LOGW("finished FW query"); break; } util::sleep_for(20); } - // set to ELM327 for ECU knockouts - for (Panda *panda : pandas) { - panda->set_safety_model(cereal::CarParams::SafetyModel::ELM327, 1U); - } - std::string params; LOGW("waiting for params to set safety model"); while (true) { @@ -184,7 +185,7 @@ bool safety_setter_thread(std::vector pandas) { return true; } -Panda *usb_connect(std::string serial="", uint32_t index=0) { +Panda *connect(std::string serial="", uint32_t index=0) { std::unique_ptr panda; try { panda = std::make_unique(serial, (index * PANDA_BUS_CNT)); @@ -227,9 +228,9 @@ void can_send_thread(std::vector pandas, bool fake_send) { //Dont send if older than 1 second if ((nanos_since_boot() - event.getLogMonoTime() < 1e9) && !fake_send) { for (const auto& panda : pandas) { - LOGT("sending sendcan to panda: %s", (panda->usb_serial).c_str()); + LOGT("sending sendcan to panda: %s", (panda->hw_serial()).c_str()); panda->can_send(event.getSendcan()); - LOGT("sendcan sent to panda: %s", (panda->usb_serial).c_str()); + LOGT("sendcan sent to panda: %s", (panda->hw_serial()).c_str()); } } } @@ -357,7 +358,7 @@ std::optional send_panda_states(PubMaster *pm, const std::vector } #endif - if (!panda->comms_healthy) { + if (!panda->comms_healthy()) { evt.setValid(false); } @@ -433,7 +434,7 @@ void send_peripheral_state(PubMaster *pm, Panda *panda) { // build msg MessageBuilder msg; auto evt = msg.initEvent(); - evt.setValid(panda->comms_healthy); + evt.setValid(panda->comms_healthy()); auto ps = evt.initPeripheralState(); ps.setPandaType(panda->hw_type); @@ -526,13 +527,10 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { FirstOrderFilter integ_lines_filter(0, 30.0, 0.05); - while (!do_exit && panda->connected) { + while (!do_exit && panda->connected()) { cnt++; sm.update(1000); // TODO: what happens if EINTR is sent while in sm.update? - // Other pandas don't have fan/IR to control - if (panda->hw_type != cereal::PandaState::PandaType::UNO && panda->hw_type != cereal::PandaState::PandaType::DOS) continue; - if (sm.updated("deviceState") && !no_fan_control) { // Fan speed uint16_t fan_speed = sm["deviceState"].getDeviceState().getFanSpeedPercentDesired(); @@ -545,9 +543,8 @@ void peripheral_control_thread(Panda *panda, bool no_fan_control) { if (sm.updated("driverCameraState")) { auto event = sm["driverCameraState"]; int cur_integ_lines = event.getDriverCameraState().getIntegLines(); - float cur_gain = event.getDriverCameraState().getGain(); - cur_integ_lines = integ_lines_filter.update(cur_integ_lines * cur_gain); + cur_integ_lines = integ_lines_filter.update(cur_integ_lines); last_front_frame_t = event.getLogMonoTime(); if (cur_integ_lines <= CUTOFF_IL) { @@ -595,7 +592,7 @@ void boardd_main_thread(std::vector serials) { // connect to all provided serials std::vector pandas; for (int i = 0; i < serials.size() && !do_exit; /**/) { - Panda *p = usb_connect(serials[i], i); + Panda *p = connect(serials[i], i); if (!p) { // send empty pandaState & peripheralState and try again send_empty_panda_state(&pm); diff --git a/selfdrive/boardd/panda.cc b/selfdrive/boardd/panda.cc index 0b8630b0c0..647a0d9c78 100644 --- a/selfdrive/boardd/panda.cc +++ b/selfdrive/boardd/panda.cc @@ -4,75 +4,20 @@ #include #include -#include #include "cereal/messaging/messaging.h" -#include "panda/board/dlc_to_len.h" -#include "common/gpio.h" #include "common/swaglog.h" #include "common/util.h" -static int init_usb_ctx(libusb_context **context) { - assert(context != nullptr); - - int err = libusb_init(context); - if (err != 0) { - LOGE("libusb initialization error"); - return err; - } - -#if LIBUSB_API_VERSION >= 0x01000106 - libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); -#else - libusb_set_debug(*context, 3); -#endif - - return err; -} - - Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { - // init libusb - ssize_t num_devices; - libusb_device **dev_list = NULL; - int err = init_usb_ctx(&ctx); - if (err != 0) { goto fail; } - - // connect by serial - num_devices = libusb_get_device_list(ctx, &dev_list); - if (num_devices < 0) { goto fail; } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device_descriptor desc; - libusb_get_device_descriptor(dev_list[i], &desc); - if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { - int ret = libusb_open(dev_list[i], &dev_handle); - if (dev_handle == NULL || ret < 0) { goto fail; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - if (ret < 0) { goto fail; } - - usb_serial = std::string((char *)desc_serial, ret).c_str(); - if (serial.empty() || serial == usb_serial) { - break; - } - libusb_close(dev_handle); - dev_handle = NULL; - } + // try USB first, then SPI + try { + handle = std::make_unique(serial); + } catch (std::exception &e) { +#ifndef __APPLE__ + handle = std::make_unique(serial); +#endif } - if (dev_handle == NULL) goto fail; - libusb_free_device_list(dev_list, 1); - dev_list = nullptr; - - if (libusb_kernel_driver_active(dev_handle, 0) == 1) { - libusb_detach_kernel_driver(dev_handle, 0); - } - - err = libusb_set_configuration(dev_handle, 1); - if (err != 0) { goto fail; } - - err = libusb_claim_interface(dev_handle, 0); - if (err != 0) { goto fail; } hw_type = get_hw_type(); @@ -80,197 +25,64 @@ Panda::Panda(std::string serial, uint32_t bus_offset) : bus_offset(bus_offset) { (hw_type != cereal::PandaState::PandaType::GREY_PANDA)); has_rtc = (hw_type == cereal::PandaState::PandaType::UNO) || - (hw_type == cereal::PandaState::PandaType::DOS); + (hw_type == cereal::PandaState::PandaType::DOS) || + (hw_type == cereal::PandaState::PandaType::TRES); + + can_reset_communications(); return; - -fail: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - cleanup(); - throw std::runtime_error("Error connecting to panda"); } -Panda::~Panda() { - std::lock_guard lk(usb_lock); - cleanup(); - connected = false; +bool Panda::connected() { + return handle->connected; } -void Panda::cleanup() { - if (dev_handle) { - libusb_release_interface(dev_handle, 0); - libusb_close(dev_handle); - } +bool Panda::comms_healthy() { + return handle->comms_healthy; +} - if (ctx) { - libusb_exit(ctx); - } +std::string Panda::hw_serial() { + return handle->hw_serial; } std::vector Panda::list() { - // init libusb - ssize_t num_devices; - libusb_context *context = NULL; - libusb_device **dev_list = NULL; - std::vector serials; + std::vector serials = PandaUsbHandle::list(); - int err = init_usb_ctx(&context); - if (err != 0) { return serials; } - - num_devices = libusb_get_device_list(context, &dev_list); - if (num_devices < 0) { - LOGE("libusb can't get device list"); - goto finish; - } - for (size_t i = 0; i < num_devices; ++i) { - libusb_device *device = dev_list[i]; - libusb_device_descriptor desc; - libusb_get_device_descriptor(device, &desc); - if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { - libusb_device_handle *handle = NULL; - int ret = libusb_open(device, &handle); - if (ret < 0) { goto finish; } - - unsigned char desc_serial[26] = { 0 }; - ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); - libusb_close(handle); - if (ret < 0) { goto finish; } - - serials.push_back(std::string((char *)desc_serial, ret).c_str()); +#ifndef __APPLE__ + for (auto s : PandaSpiHandle::list()) { + if (std::find(serials.begin(), serials.end(), s) == serials.end()) { + serials.push_back(s); } } +#endif -finish: - if (dev_list != NULL) { - libusb_free_device_list(dev_list, 1); - } - if (context) { - libusb_exit(context); - } return serials; } -void Panda::handle_usb_issue(int err, const char func[]) { - LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); - if (err == LIBUSB_ERROR_NO_DEVICE) { - LOGE("lost connection"); - connected = false; - } - // TODO: check other errors, is simply retrying okay? -} - -int Panda::usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(usb_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int Panda::usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { - int err; - const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; - - if (!connected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - std::lock_guard lk(usb_lock); - do { - err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); - if (err < 0) handle_usb_issue(err, __func__); - } while (err < 0 && connected); - - return err; -} - -int Panda::usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(usb_lock); - do { - // Try sending can messages. If the receive buffer on the panda is full it will NAK - // and libusb will try again. After 5ms, it will time out. We will drop the messages. - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - LOGW("Transmit buffer full"); - break; - } else if (err != 0 || length != transferred) { - handle_usb_issue(err, __func__); - } - } while(err != 0 && connected); - - return transferred; -} - -int Panda::usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { - int err; - int transferred = 0; - - if (!connected) { - return 0; - } - - std::lock_guard lk(usb_lock); - - do { - err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); - - if (err == LIBUSB_ERROR_TIMEOUT) { - break; // timeout is okay to exit, recv still happened - } else if (err == LIBUSB_ERROR_OVERFLOW) { - comms_healthy = false; - LOGE_100("overflow got 0x%x", transferred); - } else if (err != 0) { - handle_usb_issue(err, __func__); - } - - } while(err != 0 && connected); - - return transferred; -} - void Panda::set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param) { - usb_write(0xdc, (uint16_t)safety_model, safety_param); + handle->control_write(0xdc, (uint16_t)safety_model, safety_param); } void Panda::set_alternative_experience(uint16_t alternative_experience) { - usb_write(0xdf, alternative_experience, 0); + handle->control_write(0xdf, alternative_experience, 0); } cereal::PandaState::PandaType Panda::get_hw_type() { unsigned char hw_query[1] = {0}; - usb_read(0xc1, 0, 0, hw_query, 1); + handle->control_read(0xc1, 0, 0, hw_query, 1); return (cereal::PandaState::PandaType)(hw_query[0]); } void Panda::set_rtc(struct tm sys_time) { // tm struct has year defined as years since 1900 - usb_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); - usb_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); - usb_write(0xa3, (uint16_t)sys_time.tm_mday, 0); - // usb_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); - usb_write(0xa5, (uint16_t)sys_time.tm_hour, 0); - usb_write(0xa6, (uint16_t)sys_time.tm_min, 0); - usb_write(0xa7, (uint16_t)sys_time.tm_sec, 0); + handle->control_write(0xa1, (uint16_t)(1900 + sys_time.tm_year), 0); + handle->control_write(0xa2, (uint16_t)(1 + sys_time.tm_mon), 0); + handle->control_write(0xa3, (uint16_t)sys_time.tm_mday, 0); + // handle->control_write(0xa4, (uint16_t)(1 + sys_time.tm_wday), 0); + handle->control_write(0xa5, (uint16_t)sys_time.tm_hour, 0); + handle->control_write(0xa6, (uint16_t)sys_time.tm_min, 0); + handle->control_write(0xa7, (uint16_t)sys_time.tm_sec, 0); } struct tm Panda::get_rtc() { @@ -284,7 +96,7 @@ struct tm Panda::get_rtc() { uint8_t second; } rtc_time = {0}; - usb_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); + handle->control_read(0xa0, 0, 0, (unsigned char*)&rtc_time, sizeof(rtc_time)); struct tm new_time = { 0 }; new_time.tm_year = rtc_time.year - 1900; // tm struct has year defined as years since 1900 @@ -298,70 +110,70 @@ struct tm Panda::get_rtc() { } void Panda::set_fan_speed(uint16_t fan_speed) { - usb_write(0xb1, fan_speed, 0); + handle->control_write(0xb1, fan_speed, 0); } uint16_t Panda::get_fan_speed() { uint16_t fan_speed_rpm = 0; - usb_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); + handle->control_read(0xb2, 0, 0, (unsigned char*)&fan_speed_rpm, sizeof(fan_speed_rpm)); return fan_speed_rpm; } void Panda::set_ir_pwr(uint16_t ir_pwr) { - usb_write(0xb0, ir_pwr, 0); + handle->control_write(0xb0, ir_pwr, 0); } std::optional Panda::get_state() { health_t health {0}; - int err = usb_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); + int err = handle->control_read(0xd2, 0, 0, (unsigned char*)&health, sizeof(health)); return err >= 0 ? std::make_optional(health) : std::nullopt; } std::optional Panda::get_can_state(uint16_t can_number) { can_health_t can_health {0}; - int err = usb_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health)); + int err = handle->control_read(0xc2, can_number, 0, (unsigned char*)&can_health, sizeof(can_health)); return err >= 0 ? std::make_optional(can_health) : std::nullopt; } void Panda::set_loopback(bool loopback) { - usb_write(0xe5, loopback, 0); + handle->control_write(0xe5, loopback, 0); } std::optional> Panda::get_firmware_version() { std::vector fw_sig_buf(128); - int read_1 = usb_read(0xd3, 0, 0, &fw_sig_buf[0], 64); - int read_2 = usb_read(0xd4, 0, 0, &fw_sig_buf[64], 64); + int read_1 = handle->control_read(0xd3, 0, 0, &fw_sig_buf[0], 64); + int read_2 = handle->control_read(0xd4, 0, 0, &fw_sig_buf[64], 64); return ((read_1 == 64) && (read_2 == 64)) ? std::make_optional(fw_sig_buf) : std::nullopt; } std::optional Panda::get_serial() { char serial_buf[17] = {'\0'}; - int err = usb_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); + int err = handle->control_read(0xd0, 0, 0, (uint8_t*)serial_buf, 16); return err >= 0 ? std::make_optional(serial_buf) : std::nullopt; } void Panda::set_power_saving(bool power_saving) { - usb_write(0xe7, power_saving, 0); + handle->control_write(0xe7, power_saving, 0); } void Panda::enable_deepsleep() { - usb_write(0xfb, 0, 0); + handle->control_write(0xfb, 0, 0); } void Panda::send_heartbeat(bool engaged) { - usb_write(0xf3, engaged, 0); + handle->control_write(0xf3, engaged, 0); } void Panda::set_can_speed_kbps(uint16_t bus, uint16_t speed) { - usb_write(0xde, bus, (speed * 10)); + handle->control_write(0xde, bus, (speed * 10)); } void Panda::set_data_speed_kbps(uint16_t bus, uint16_t speed) { - usb_write(0xf9, bus, (speed * 10)); + handle->control_write(0xf9, bus, (speed * 10)); } void Panda::set_canfd_non_iso(uint16_t bus, bool non_iso) { - usb_write(0xfc, bus, non_iso); + handle->control_write(0xfc, bus, non_iso); } static uint8_t len_to_dlc(uint8_t len) { @@ -375,17 +187,6 @@ static uint8_t len_to_dlc(uint8_t len) { } } -static void write_packet(uint8_t *dest, int *write_pos, const uint8_t *src, size_t size) { - for (int i = 0, &pos = *write_pos; i < size; ++i, ++pos) { - // Insert counter every 64 bytes (first byte of 64 bytes USB packet) - if (pos % USBPACKET_MAX_SIZE == 0) { - dest[pos] = pos / USBPACKET_MAX_SIZE; - pos++; - } - dest[pos] = src[i]; - } -} - void Panda::pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func) { int32_t pos = 0; @@ -399,8 +200,7 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data } auto can_data = cmsg.getDat(); uint8_t data_len_code = len_to_dlc(can_data.size()); - assert(can_data.size() <= ((hw_type == cereal::PandaState::PandaType::RED_PANDA || - hw_type == cereal::PandaState::PandaType::RED_PANDA_V2) ? 64 : 8)); + assert(can_data.size() <= 64); assert(can_data.size() == dlc_to_len[data_len_code]); can_header header; @@ -408,9 +208,17 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data header.extended = (cmsg.getAddress() >= 0x800) ? 1 : 0; header.data_len_code = data_len_code; header.bus = bus - bus_offset; + header.checksum = 0; + + memcpy(&send_buf[pos], (uint8_t *)&header, sizeof(can_header)); + memcpy(&send_buf[pos + sizeof(can_header)], (uint8_t *)can_data.begin(), can_data.size()); + uint32_t msg_size = sizeof(can_header) + can_data.size(); + + // set checksum + ((can_header *) &send_buf[pos])->checksum = calculate_checksum(&send_buf[pos], msg_size); + + pos += msg_size; - write_packet(send_buf, &pos, (uint8_t *)&header, sizeof(can_header)); - write_packet(send_buf, &pos, (uint8_t *)can_data.begin(), can_data.size()); if (pos >= USB_TX_SOFT_LIMIT) { write_func(send_buf, pos); pos = 0; @@ -423,51 +231,76 @@ void Panda::pack_can_buffer(const capnp::List::Reader &can_data void Panda::can_send(capnp::List::Reader can_data_list) { pack_can_buffer(can_data_list, [=](uint8_t* data, size_t size) { - usb_bulk_write(3, data, size, 5); + handle->bulk_write(3, data, size, 5); }); } bool Panda::can_receive(std::vector& out_vec) { - uint8_t data[RECV_SIZE]; - int recv = usb_bulk_read(0x81, (uint8_t*)data, RECV_SIZE); - if (!comms_healthy) { + // Check if enough space left in buffer to store RECV_SIZE data + assert(receive_buffer_size + RECV_SIZE <= sizeof(receive_buffer)); + + int recv = handle->bulk_read(0x81, &receive_buffer[receive_buffer_size], RECV_SIZE); + if (!comms_healthy()) { return false; } if (recv == RECV_SIZE) { LOGW("Panda receive buffer full"); } + receive_buffer_size += recv; - return (recv <= 0) ? true : unpack_can_buffer(data, recv, out_vec); + return (recv <= 0) ? true : unpack_can_buffer(receive_buffer, receive_buffer_size, out_vec); } -bool Panda::unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec) { - recv_buf.clear(); - for (int i = 0; i < size; i += USBPACKET_MAX_SIZE) { - if (data[i] != i / USBPACKET_MAX_SIZE) { - LOGE("CAN: MALFORMED USB RECV PACKET"); - comms_healthy = false; - return false; - } - int chunk_len = std::min(USBPACKET_MAX_SIZE, (size - i)); - recv_buf.insert(recv_buf.end(), &data[i + 1], &data[i + chunk_len]); - } +void Panda::can_reset_communications() { + handle->control_write(0xc0, 0, 0); +} +bool Panda::unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec) { int pos = 0; - while (pos < recv_buf.size()) { + + while (pos <= size - sizeof(can_header)) { can_header header; - memcpy(&header, &recv_buf[pos], CANPACKET_HEAD_SIZE); + memcpy(&header, &data[pos], sizeof(can_header)); + + const uint8_t data_len = dlc_to_len[header.data_len_code]; + if (pos + sizeof(can_header) + data_len > size) { + // we don't have all the data for this message yet + break; + } can_frame &canData = out_vec.emplace_back(); canData.busTime = 0; canData.address = header.addr; canData.src = header.bus + bus_offset; - if (header.rejected) { canData.src += CANPACKET_REJECTED; } - if (header.returned) { canData.src += CANPACKET_RETURNED; } + if (header.rejected) { + canData.src += CAN_REJECTED_BUS_OFFSET; + } + if (header.returned) { + canData.src += CAN_RETURNED_BUS_OFFSET; + } - const uint8_t data_len = dlc_to_len[header.data_len_code]; - canData.dat.assign((char *)&recv_buf[pos + CANPACKET_HEAD_SIZE], data_len); + if (calculate_checksum(&data[pos], sizeof(can_header) + data_len) != 0) { + LOGE("Panda CAN checksum failed"); + size = 0; + return false; + } - pos += CANPACKET_HEAD_SIZE + data_len; + canData.dat.assign((char *)&data[pos + sizeof(can_header)], data_len); + + pos += sizeof(can_header) + data_len; } + + // move the overflowing data to the beginning of the buffer for the next round + memmove(data, &data[pos], size - pos); + size -= pos; + return true; } + +uint8_t Panda::calculate_checksum(uint8_t *data, uint32_t len) { + uint8_t checksum = 0U; + for (uint32_t i = 0U; i < len; i++) { + checksum ^= data[i]; + } + return checksum; +} diff --git a/selfdrive/boardd/panda.h b/selfdrive/boardd/panda.h index c7a0e7a6c1..69df2e2b66 100644 --- a/selfdrive/boardd/panda.h +++ b/selfdrive/boardd/panda.h @@ -1,30 +1,26 @@ #pragma once -#include #include #include #include #include -#include +#include #include #include -#include - #include "cereal/gen/cpp/car.capnp.h" #include "cereal/gen/cpp/log.capnp.h" #include "panda/board/health.h" +#include "panda/board/can_definitions.h" +#include "selfdrive/boardd/panda_comms.h" -#define TIMEOUT 0 -#define PANDA_CAN_CNT 3 -#define PANDA_BUS_CNT 4 -#define RECV_SIZE (0x4000U) #define USB_TX_SOFT_LIMIT (0x100U) #define USBPACKET_MAX_SIZE (0x40) -#define CANPACKET_HEAD_SIZE 5U -#define CANPACKET_MAX_SIZE 72U -#define CANPACKET_REJECTED (0xC0U) -#define CANPACKET_RETURNED (0x80U) + +#define RECV_SIZE (0x4000U) + +#define CAN_REJECTED_BUS_OFFSET 0xC0U +#define CAN_RETURNED_BUS_OFFSET 0x80U struct __attribute__((packed)) can_header { uint8_t reserved : 1; @@ -34,44 +30,35 @@ struct __attribute__((packed)) can_header { uint8_t returned : 1; uint8_t extended : 1; uint32_t addr : 29; + uint8_t checksum : 8; }; struct can_frame { - long address; - std::string dat; - long busTime; - long src; + long address; + std::string dat; + long busTime; + long src; }; + class Panda { - private: - libusb_context *ctx = NULL; - libusb_device_handle *dev_handle = NULL; - std::mutex usb_lock; - std::vector recv_buf; - void handle_usb_issue(int err, const char func[]); - void cleanup(); +private: + std::unique_ptr handle; - public: +public: Panda(std::string serial="", uint32_t bus_offset=0); - ~Panda(); - std::string usb_serial; - std::atomic connected = true; - std::atomic comms_healthy = true; cereal::PandaState::PandaType hw_type = cereal::PandaState::PandaType::UNKNOWN; bool has_rtc = false; const uint32_t bus_offset; + bool connected(); + bool comms_healthy(); + std::string hw_serial(); + // Static functions static std::vector list(); - // HW communication - int usb_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout=TIMEOUT); - int usb_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout=TIMEOUT); - int usb_bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - int usb_bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); - // Panda functionality cereal::PandaState::PandaType get_hw_type(); void set_safety_model(cereal::CarParams::SafetyModel safety_model, uint16_t safety_param=0U); @@ -94,11 +81,16 @@ class Panda { void set_canfd_non_iso(uint16_t bus, bool non_iso); void can_send(capnp::List::Reader can_data_list); bool can_receive(std::vector& out_vec); + void can_reset_communications(); protected: // for unit tests + uint8_t receive_buffer[RECV_SIZE + sizeof(can_header) + 64]; + uint32_t receive_buffer_size = 0; + Panda(uint32_t bus_offset) : bus_offset(bus_offset) {} void pack_can_buffer(const capnp::List::Reader &can_data_list, std::function write_func); - bool unpack_can_buffer(uint8_t *data, int size, std::vector &out_vec); + bool unpack_can_buffer(uint8_t *data, uint32_t &size, std::vector &out_vec); + uint8_t calculate_checksum(uint8_t *data, uint32_t len); }; diff --git a/selfdrive/boardd/panda_comms.cc b/selfdrive/boardd/panda_comms.cc new file mode 100644 index 0000000000..120d2f67d5 --- /dev/null +++ b/selfdrive/boardd/panda_comms.cc @@ -0,0 +1,232 @@ +#include "selfdrive/boardd/panda.h" + +#include +#include + +#include "common/swaglog.h" + +static int init_usb_ctx(libusb_context **context) { + assert(context != nullptr); + + int err = libusb_init(context); + if (err != 0) { + LOGE("libusb initialization error"); + return err; + } + +#if LIBUSB_API_VERSION >= 0x01000106 + libusb_set_option(*context, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_INFO); +#else + libusb_set_debug(*context, 3); +#endif + + return err; +} + +PandaUsbHandle::PandaUsbHandle(std::string serial) : PandaCommsHandle(serial) { + // init libusb + ssize_t num_devices; + libusb_device **dev_list = NULL; + int err = init_usb_ctx(&ctx); + if (err != 0) { goto fail; } + + // connect by serial + num_devices = libusb_get_device_list(ctx, &dev_list); + if (num_devices < 0) { goto fail; } + for (size_t i = 0; i < num_devices; ++i) { + libusb_device_descriptor desc; + libusb_get_device_descriptor(dev_list[i], &desc); + if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { + int ret = libusb_open(dev_list[i], &dev_handle); + if (dev_handle == NULL || ret < 0) { goto fail; } + + unsigned char desc_serial[26] = { 0 }; + ret = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); + if (ret < 0) { goto fail; } + + hw_serial = std::string((char *)desc_serial, ret); + if (serial.empty() || serial == hw_serial) { + break; + } + libusb_close(dev_handle); + dev_handle = NULL; + } + } + if (dev_handle == NULL) goto fail; + libusb_free_device_list(dev_list, 1); + dev_list = nullptr; + + if (libusb_kernel_driver_active(dev_handle, 0) == 1) { + libusb_detach_kernel_driver(dev_handle, 0); + } + + err = libusb_set_configuration(dev_handle, 1); + if (err != 0) { goto fail; } + + err = libusb_claim_interface(dev_handle, 0); + if (err != 0) { goto fail; } + + return; + +fail: + if (dev_list != NULL) { + libusb_free_device_list(dev_list, 1); + } + cleanup(); + throw std::runtime_error("Error connecting to panda"); +} + +PandaUsbHandle::~PandaUsbHandle() { + std::lock_guard lk(hw_lock); + cleanup(); + connected = false; +} + +void PandaUsbHandle::cleanup() { + if (dev_handle) { + libusb_release_interface(dev_handle, 0); + libusb_close(dev_handle); + } + + if (ctx) { + libusb_exit(ctx); + } +} + +std::vector PandaUsbHandle::list() { + // init libusb + ssize_t num_devices; + libusb_context *context = NULL; + libusb_device **dev_list = NULL; + std::vector serials; + + int err = init_usb_ctx(&context); + if (err != 0) { return serials; } + + num_devices = libusb_get_device_list(context, &dev_list); + if (num_devices < 0) { + LOGE("libusb can't get device list"); + goto finish; + } + for (size_t i = 0; i < num_devices; ++i) { + libusb_device *device = dev_list[i]; + libusb_device_descriptor desc; + libusb_get_device_descriptor(device, &desc); + if (desc.idVendor == 0xbbaa && desc.idProduct == 0xddcc) { + libusb_device_handle *handle = NULL; + int ret = libusb_open(device, &handle); + if (ret < 0) { goto finish; } + + unsigned char desc_serial[26] = { 0 }; + ret = libusb_get_string_descriptor_ascii(handle, desc.iSerialNumber, desc_serial, std::size(desc_serial)); + libusb_close(handle); + if (ret < 0) { goto finish; } + + serials.push_back(std::string((char *)desc_serial, ret).c_str()); + } + } + +finish: + if (dev_list != NULL) { + libusb_free_device_list(dev_list, 1); + } + if (context) { + libusb_exit(context); + } + return serials; +} + +void PandaUsbHandle::handle_usb_issue(int err, const char func[]) { + LOGE_100("usb error %d \"%s\" in %s", err, libusb_strerror((enum libusb_error)err), func); + if (err == LIBUSB_ERROR_NO_DEVICE) { + LOGE("lost connection"); + connected = false; + } + // TODO: check other errors, is simply retrying okay? +} + +int PandaUsbHandle::control_write(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned int timeout) { + int err; + const uint8_t bmRequestType = LIBUSB_ENDPOINT_OUT | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + + if (!connected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + std::lock_guard lk(hw_lock); + do { + err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, NULL, 0, timeout); + if (err < 0) handle_usb_issue(err, __func__); + } while (err < 0 && connected); + + return err; +} + +int PandaUsbHandle::control_read(uint8_t bRequest, uint16_t wValue, uint16_t wIndex, unsigned char *data, uint16_t wLength, unsigned int timeout) { + int err; + const uint8_t bmRequestType = LIBUSB_ENDPOINT_IN | LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_RECIPIENT_DEVICE; + + if (!connected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + std::lock_guard lk(hw_lock); + do { + err = libusb_control_transfer(dev_handle, bmRequestType, bRequest, wValue, wIndex, data, wLength, timeout); + if (err < 0) handle_usb_issue(err, __func__); + } while (err < 0 && connected); + + return err; +} + +int PandaUsbHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + int err; + int transferred = 0; + + if (!connected) { + return 0; + } + + std::lock_guard lk(hw_lock); + do { + // Try sending can messages. If the receive buffer on the panda is full it will NAK + // and libusb will try again. After 5ms, it will time out. We will drop the messages. + err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); + + if (err == LIBUSB_ERROR_TIMEOUT) { + LOGW("Transmit buffer full"); + break; + } else if (err != 0 || length != transferred) { + handle_usb_issue(err, __func__); + } + } while(err != 0 && connected); + + return transferred; +} + +int PandaUsbHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + int err; + int transferred = 0; + + if (!connected) { + return 0; + } + + std::lock_guard lk(hw_lock); + + do { + err = libusb_bulk_transfer(dev_handle, endpoint, data, length, &transferred, timeout); + + if (err == LIBUSB_ERROR_TIMEOUT) { + break; // timeout is okay to exit, recv still happened + } else if (err == LIBUSB_ERROR_OVERFLOW) { + comms_healthy = false; + LOGE_100("overflow got 0x%x", transferred); + } else if (err != 0) { + handle_usb_issue(err, __func__); + } + + } while(err != 0 && connected); + + return transferred; +} diff --git a/selfdrive/boardd/panda_comms.h b/selfdrive/boardd/panda_comms.h new file mode 100644 index 0000000000..506b96b372 --- /dev/null +++ b/selfdrive/boardd/panda_comms.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef __APPLE__ +#include +#endif + +#include + + +#define TIMEOUT 0 +#define SPI_BUF_SIZE 1024 + + +// comms base class +class PandaCommsHandle { +public: + PandaCommsHandle(std::string serial) {}; + virtual ~PandaCommsHandle() {}; + virtual void cleanup() = 0; + + std::string hw_serial; + std::atomic connected = true; + std::atomic comms_healthy = true; + static std::vector list(); + + // HW communication + virtual int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT) = 0; + virtual int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT) = 0; + virtual int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; + virtual int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT) = 0; +}; + +class PandaUsbHandle : public PandaCommsHandle { +public: + PandaUsbHandle(std::string serial); + ~PandaUsbHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); + int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); + int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + void cleanup(); + + static std::vector list(); + +private: + libusb_context *ctx = NULL; + libusb_device_handle *dev_handle = NULL; + std::recursive_mutex hw_lock; + void handle_usb_issue(int err, const char func[]); +}; + +#ifndef __APPLE__ +class PandaSpiHandle : public PandaCommsHandle { +public: + PandaSpiHandle(std::string serial); + ~PandaSpiHandle(); + int control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout=TIMEOUT); + int control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout=TIMEOUT); + int bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + int bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout=TIMEOUT); + void cleanup(); + + static std::vector list(); + +private: + int spi_fd = -1; + uint8_t tx_buf[SPI_BUF_SIZE]; + uint8_t rx_buf[SPI_BUF_SIZE]; + inline static std::recursive_mutex hw_lock; + + int wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack); + int bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len); + int spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len); + int spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len); +}; +#endif diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 971756002b..f292b921cf 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -7,7 +7,7 @@ import subprocess from typing import List, NoReturn from functools import cmp_to_key -from panda import DEFAULT_FW_FN, DEFAULT_H7_FW_FN, MCU_TYPE_H7, Panda, PandaDFU +from panda import Panda, PandaDFU from common.basedir import BASEDIR from common.params import Params from system.hardware import HARDWARE @@ -15,10 +15,8 @@ from system.swaglog import cloudlog def get_expected_signature(panda: Panda) -> bytes: - fn = DEFAULT_H7_FW_FN if (panda.get_mcu_type() == MCU_TYPE_H7) else DEFAULT_FW_FN - try: - return Panda.get_signature_from_firmware(fn) + return Panda.get_signature_from_firmware(panda.get_mcu_type().config.app_path) except Exception: cloudlog.exception("Error computing expected signature") return b"" @@ -86,9 +84,9 @@ def main() -> NoReturn: params.remove("PandaSignatures") # Flash all Pandas in DFU mode - for p in PandaDFU.list(): - cloudlog.info(f"Panda in DFU mode found, flashing recovery {p}") - PandaDFU(p).recover() + for serial in PandaDFU.list(): + cloudlog.info(f"Panda in DFU mode found, flashing recovery {serial}") + PandaDFU(serial).recover() time.sleep(1) panda_serials = Panda.list() diff --git a/selfdrive/boardd/spi.cc b/selfdrive/boardd/spi.cc new file mode 100644 index 0000000000..9a10e30f95 --- /dev/null +++ b/selfdrive/boardd/spi.cc @@ -0,0 +1,346 @@ +#ifndef __APPLE__ +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "common/util.h" +#include "common/timing.h" +#include "common/swaglog.h" +#include "panda/board/comms_definitions.h" +#include "selfdrive/boardd/panda_comms.h" + + +#define SPI_SYNC 0x5AU +#define SPI_HACK 0x79U +#define SPI_DACK 0x85U +#define SPI_NACK 0x1FU +#define SPI_CHECKSUM_START 0xABU + +struct __attribute__((packed)) spi_header { + uint8_t sync; + uint8_t endpoint; + uint16_t tx_len; + uint16_t max_rx_len; +}; + +const int SPI_MAX_RETRIES = 5; +const int SPI_ACK_TIMEOUT = 50; // milliseconds +const std::string SPI_DEVICE = "/dev/spidev0.0"; + +class LockEx { +public: + LockEx(int fd, std::recursive_mutex &m) : fd(fd), m(m) { + m.lock(); + flock(fd, LOCK_EX); + }; + + ~LockEx() { + m.unlock(); + flock(fd, LOCK_UN); + } + +private: + int fd; + std::recursive_mutex &m; +}; + + +PandaSpiHandle::PandaSpiHandle(std::string serial) : PandaCommsHandle(serial) { + int ret; + const int uid_len = 12; + uint8_t uid[uid_len] = {0}; + + uint32_t spi_mode = SPI_MODE_0; + uint32_t spi_speed = 30000000; + uint8_t spi_bits_per_word = 8; + + spi_fd = open(SPI_DEVICE.c_str(), O_RDWR); + if (spi_fd < 0) { + LOGE("failed opening SPI device %d", spi_fd); + goto fail; + } + + // SPI settings + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MODE, &spi_mode); + if (ret < 0) { + LOGE("failed setting SPI mode %d", ret); + goto fail; + } + + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &spi_speed); + if (ret < 0) { + LOGE("failed setting SPI speed"); + goto fail; + } + + ret = util::safe_ioctl(spi_fd, SPI_IOC_WR_BITS_PER_WORD, &spi_bits_per_word); + if (ret < 0) { + LOGE("failed setting SPI bits per word"); + goto fail; + } + + // get hw UID/serial + ret = control_read(0xc3, 0, 0, uid, uid_len); + if (ret == uid_len) { + std::stringstream stream; + for (int i = 0; i < uid_len; i++) { + stream << std::hex << std::setw(2) << std::setfill('0') << int(uid[i]); + } + hw_serial = stream.str(); + } else { + LOGD("failed to get serial %d", ret); + goto fail; + } + + if (!serial.empty() && (serial != hw_serial)) { + goto fail; + } + + return; + +fail: + cleanup(); + throw std::runtime_error("Error connecting to panda"); +} + +PandaSpiHandle::~PandaSpiHandle() { + std::lock_guard lk(hw_lock); + cleanup(); +} + +void PandaSpiHandle::cleanup() { + if (spi_fd != -1) { + close(spi_fd); + spi_fd = -1; + } +} + + + +int PandaSpiHandle::control_write(uint8_t request, uint16_t param1, uint16_t param2, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); + ControlPacket_t packet = { + .request = request, + .param1 = param1, + .param2 = param2, + .length = 0 + }; + return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), NULL, 0); +} + +int PandaSpiHandle::control_read(uint8_t request, uint16_t param1, uint16_t param2, unsigned char *data, uint16_t length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); + ControlPacket_t packet = { + .request = request, + .param1 = param1, + .param2 = param2, + .length = length + }; + return spi_transfer_retry(0, (uint8_t *) &packet, sizeof(packet), data, length); +} + +int PandaSpiHandle::bulk_write(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); + return bulk_transfer(endpoint, data, length, NULL, 0); +} +int PandaSpiHandle::bulk_read(unsigned char endpoint, unsigned char* data, int length, unsigned int timeout) { + LockEx lock(spi_fd, hw_lock); + return bulk_transfer(endpoint, NULL, 0, data, length); +} + +int PandaSpiHandle::bulk_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t rx_len) { + const int xfer_size = 0x40 * 15; + + int ret = 0; + uint16_t length = (tx_data != NULL) ? tx_len : rx_len; + for (int i = 0; i < (int)std::ceil((float)length / xfer_size); i++) { + int d; + if (tx_data != NULL) { + int len = std::min(xfer_size, tx_len - (xfer_size * i)); + d = spi_transfer_retry(endpoint, tx_data + (xfer_size * i), len, NULL, 0); + } else { + uint16_t to_read = std::min(xfer_size, rx_len - ret); + d = spi_transfer_retry(endpoint, NULL, 0, rx_data + (xfer_size * i), to_read); + } + + if (d < 0) { + LOGE("SPI: bulk transfer failed with %d", d); + comms_healthy = false; + return -1; + } + + ret += d; + if ((rx_data != NULL) && d < xfer_size) { + break; + } + } + + return ret; +} + +std::vector PandaSpiHandle::list() { + try { + PandaSpiHandle sh(""); + return {sh.hw_serial}; + } catch (std::exception &e) { + // no panda on SPI + } + return {}; +} + +void add_checksum(uint8_t *data, int data_len) { + data[data_len] = SPI_CHECKSUM_START; + for (int i=0; i < data_len; i++) { + data[data_len] ^= data[i]; + } +} + +bool check_checksum(uint8_t *data, int data_len) { + uint8_t checksum = SPI_CHECKSUM_START; + for (uint16_t i = 0U; i < data_len; i++) { + checksum ^= data[i]; + } + return checksum == 0U; +} + + +int PandaSpiHandle::spi_transfer_retry(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) { + int ret; + + int count = SPI_MAX_RETRIES; + do { + // TODO: handle error + ret = spi_transfer(endpoint, tx_data, tx_len, rx_data, max_rx_len); + count--; + } while (ret < 0 && connected && count > 0); + + return ret; +} + +int PandaSpiHandle::wait_for_ack(spi_ioc_transfer &transfer, uint8_t ack) { + double start_millis = millis_since_boot(); + while (true) { + int ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send ACK request"); + return ret; + } + + if (rx_buf[0] == ack) { + break; + } else if (rx_buf[0] == SPI_NACK) { + LOGW("SPI: got NACK"); + return -1; + } + + // handle timeout + if (millis_since_boot() - start_millis > SPI_ACK_TIMEOUT) { + LOGD("SPI: timed out waiting for ACK"); + return -1; + } + } + + return 0; +} + +int PandaSpiHandle::spi_transfer(uint8_t endpoint, uint8_t *tx_data, uint16_t tx_len, uint8_t *rx_data, uint16_t max_rx_len) { + int ret; + uint16_t rx_data_len; + + // needs to be less, since we need to have space for the checksum + assert(tx_len < SPI_BUF_SIZE); + assert(max_rx_len < SPI_BUF_SIZE); + + spi_header header = { + .sync = SPI_SYNC, + .endpoint = endpoint, + .tx_len = tx_len, + .max_rx_len = max_rx_len + }; + + spi_ioc_transfer transfer = { + .tx_buf = (uint64_t)tx_buf, + .rx_buf = (uint64_t)rx_buf + }; + + // Send header + memcpy(tx_buf, &header, sizeof(header)); + add_checksum(tx_buf, sizeof(header)); + transfer.len = sizeof(header) + 1; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send header"); + goto transfer_fail; + } + + // Wait for (N)ACK + tx_buf[0] = 0x12; + transfer.len = 1; + ret = wait_for_ack(transfer, SPI_HACK); + if (ret < 0) { + goto transfer_fail; + } + + // Send data + if (tx_data != NULL) { + memcpy(tx_buf, tx_data, tx_len); + } + add_checksum(tx_buf, tx_len); + transfer.len = tx_len + 1; + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to send data"); + goto transfer_fail; + } + + // Wait for (N)ACK + tx_buf[0] = 0xab; + transfer.len = 1; + ret = wait_for_ack(transfer, SPI_DACK); + if (ret < 0) { + goto transfer_fail; + } + + // Read data len + transfer.len = 2; + transfer.rx_buf = (uint64_t)(rx_buf + 1); + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to read rx data len"); + goto transfer_fail; + } + rx_data_len = *(uint16_t *)(rx_buf+1); + if (rx_data_len >= SPI_BUF_SIZE) { + LOGE("SPI: RX data len larger than buf size %d", rx_data_len); + goto transfer_fail; + } + + // Read data + transfer.len = rx_data_len + 1; + transfer.rx_buf = (uint64_t)(rx_buf + 2 + 1); + ret = util::safe_ioctl(spi_fd, SPI_IOC_MESSAGE(1), &transfer); + if (ret < 0) { + LOGE("SPI: failed to read rx data"); + goto transfer_fail; + } + if (!check_checksum(rx_buf, rx_data_len + 4)) { + LOGE("SPI: bad checksum"); + goto transfer_fail; + } + + if (rx_data != NULL) { + memcpy(rx_data, rx_buf + 3, rx_data_len); + } + + return rx_data_len; + +transfer_fail: + return ret; +} +#endif diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index e9bbcb4586..d614f0b126 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -51,7 +51,7 @@ class TestBoardd(unittest.TestCase): cp.safetyConfigs = [safety_config]*num_pandas params = Params() - params.put("CarVin", b"0"*17) + params.put_bool("FirmwareQueryDone", True) params.put_bool("ControlsReady", True) params.put("CarParams", cp.to_bytes()) diff --git a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc index 6a13cbd71f..3b78eb7bd2 100644 --- a/selfdrive/boardd/tests/test_boardd_usbprotocol.cc +++ b/selfdrive/boardd/tests/test_boardd_usbprotocol.cc @@ -6,8 +6,6 @@ #include "cereal/messaging/messaging.h" #include "selfdrive/boardd/panda.h" -const unsigned char dlc_to_len[] = {0U, 1U, 2U, 3U, 4U, 5U, 6U, 7U, 8U, 12U, 16U, 20U, 24U, 32U, 48U, 64U}; - int random_int(int min, int max) { std::random_device dev; std::mt19937 rng(dev()); @@ -18,7 +16,8 @@ int random_int(int min, int max) { struct PandaTest : public Panda { PandaTest(uint32_t bus_offset, int can_list_size, cereal::PandaState::PandaType hw_type); void test_can_send(); - void test_can_recv(); + void test_can_recv(uint32_t chunk_size = 0); + void test_chunked_can_recv(); std::map test_data; int can_list_size = 0; @@ -50,7 +49,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState can.setAddress(i); can.setSrc(random_int(0, 3) + bus_offset); can.setDat(kj::ArrayPtr((uint8_t *)dat.data(), dat.size())); - total_pakets_size += CANPACKET_HEAD_SIZE + dat.size(); + total_pakets_size += sizeof(can_header) + dat.size(); } can_data_list = can_list.asReader(); @@ -60,14 +59,7 @@ PandaTest::PandaTest(uint32_t bus_offset_, int can_list_size, cereal::PandaState void PandaTest::test_can_send() { std::vector unpacked_data; this->pack_can_buffer(can_data_list, [&](uint8_t *chunk, size_t size) { - int size_left = size; - for (int i = 0, counter = 0; i < size; i += USBPACKET_MAX_SIZE, counter++) { - REQUIRE(chunk[i] == counter); - - const int len = std::min(USBPACKET_MAX_SIZE, size_left); - unpacked_data.insert(unpacked_data.end(), &chunk[i + 1], &chunk[i + len]); - size_left -= len; - } + unpacked_data.insert(unpacked_data.end(), chunk, &chunk[size]); }); REQUIRE(unpacked_data.size() == total_pakets_size); @@ -75,23 +67,37 @@ void PandaTest::test_can_send() { INFO("test can message integrity"); for (int pos = 0, pckt_len = 0; pos < unpacked_data.size(); pos += pckt_len) { can_header header; - memcpy(&header, &unpacked_data[pos], CANPACKET_HEAD_SIZE); + memcpy(&header, &unpacked_data[pos], sizeof(can_header)); const uint8_t data_len = dlc_to_len[header.data_len_code]; - pckt_len = CANPACKET_HEAD_SIZE + data_len; + pckt_len = sizeof(can_header) + data_len; REQUIRE(header.addr == cnt); REQUIRE(test_data.find(data_len) != test_data.end()); const std::string &dat = test_data[data_len]; - REQUIRE(memcmp(dat.data(), &unpacked_data[pos + 5], dat.size()) == 0); + REQUIRE(memcmp(dat.data(), &unpacked_data[pos + sizeof(can_header)], dat.size()) == 0); ++cnt; } REQUIRE(cnt == can_list_size); } -void PandaTest::test_can_recv() { +void PandaTest::test_can_recv(uint32_t rx_chunk_size) { std::vector frames; - this->pack_can_buffer(can_data_list, [&](uint8_t *data, size_t size) { - this->unpack_can_buffer(data, size, frames); + this->pack_can_buffer(can_data_list, [&](uint8_t *data, uint32_t size) { + if (rx_chunk_size == 0) { + REQUIRE(this->unpack_can_buffer(data, size, frames)); + } else { + this->receive_buffer_size = 0; + uint32_t pos = 0; + + while(pos < size) { + uint32_t chunk_size = std::min(rx_chunk_size, size - pos); + memcpy(&this->receive_buffer[this->receive_buffer_size], &data[pos], chunk_size); + this->receive_buffer_size += chunk_size; + pos += chunk_size; + + REQUIRE(this->unpack_can_buffer(this->receive_buffer, this->receive_buffer_size, frames)); + } + } }); REQUIRE(frames.size() == can_list_size); @@ -114,6 +120,9 @@ TEST_CASE("send/recv CAN 2.0 packets") { SECTION("can_receive") { test.test_can_recv(); } + SECTION("chunked_can_receive") { + test.test_can_recv(0x40); + } } TEST_CASE("send/recv CAN FD packets") { @@ -127,4 +136,7 @@ TEST_CASE("send/recv CAN FD packets") { SECTION("can_receive") { test.test_can_recv(); } + SECTION("chunked_can_receive") { + test.test_can_recv(0x40); + } } diff --git a/selfdrive/boardd/tests/test_pandad.py b/selfdrive/boardd/tests/test_pandad.py new file mode 100755 index 0000000000..3b2369b39b --- /dev/null +++ b/selfdrive/boardd/tests/test_pandad.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +import time +import unittest + +import cereal.messaging as messaging +from panda import Panda +from selfdrive.test.helpers import phone_only +from selfdrive.manager.process_config import managed_processes +from system.hardware import HARDWARE + + +class TestPandad(unittest.TestCase): + + def tearDown(self): + managed_processes['pandad'].stop() + + def _wait_for_boardd(self): + sm = messaging.SubMaster(['peripheralState']) + for _ in range(30): + sm.update(1000) + if sm.updated['peripheralState']: + break + + if not sm.updated['peripheralState']: + raise Exception("boardd failed to start") + + @phone_only + def test_in_dfu(self): + HARDWARE.recover_internal_panda() + time.sleep(1) + + managed_processes['pandad'].start() + self._wait_for_boardd() + + @phone_only + def test_in_bootstub(self): + with Panda() as p: + p.reset(enter_bootstub=True) + assert p.bootstub + managed_processes['pandad'].start() + self._wait_for_boardd() + + #def test_out_of_date_fw(self): + # pass + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/CARS_template.md b/selfdrive/car/CARS_template.md index 2fce0f9036..1c80ea3d93 100644 --- a/selfdrive/car/CARS_template.md +++ b/selfdrive/car/CARS_template.md @@ -1,5 +1,6 @@ {% set footnote_tag = '[{}](#footnotes)' -%} {% set star_icon = '[![star](assets/icon-star-{}.svg)](##)' -%} +{% set video_icon = '' -%} @@ -12,7 +13,7 @@ A supported vehicle is one that just works when you install a comma three. All s |{{Column | map(attribute='value') | join('|')}}| |---|---|---|{% for _ in range((Column | length) - 3) %}{{':---:|'}}{% endfor +%} {% for car_info in all_car_info %} -|{% for column in Column %}{{car_info.get_column(column, star_icon, footnote_tag)}}|{% endfor %} +|{% for column in Column %}{{car_info.get_column(column, star_icon, video_icon, footnote_tag)}}|{% endfor %} {% endfor %} diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index 491c551b1b..58cde85817 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,8 +1,9 @@ # functions common among cars import capnp +from collections import namedtuple from cereal import car -from common.numpy_fast import clip +from common.numpy_fast import clip, interp from typing import Dict # kg of standard extra cargo to count for drive, gas, etc... @@ -10,6 +11,7 @@ STD_CARGO_KG = 136. ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName +AngleRateLimit = namedtuple('AngleRateLimit', ['speed_bp', 'angle_v']) def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float: @@ -71,7 +73,7 @@ def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> Dict[str, st return {'pt': pt_dbc, 'radar': radar_dbc, 'chassis': chassis_dbc, 'body': body_dbc} -def apply_std_steer_torque_limits(apply_torque, apply_torque_last, driver_torque, LIMITS): +def apply_driver_steer_torque_limits(apply_torque, apply_torque_last, driver_torque, LIMITS): # limits due to driver torque driver_max_torque = LIMITS.STEER_MAX + (LIMITS.STEER_DRIVER_ALLOWANCE + driver_torque * LIMITS.STEER_DRIVER_FACTOR) * LIMITS.STEER_DRIVER_MULTIPLIER @@ -91,24 +93,41 @@ def apply_std_steer_torque_limits(apply_torque, apply_torque_last, driver_torque return int(round(float(apply_torque))) -def apply_toyota_steer_torque_limits(apply_torque, apply_torque_last, motor_torque, LIMITS): - # limits due to comparison of commanded torque VS motor reported torque - max_lim = min(max(motor_torque + LIMITS.STEER_ERROR_MAX, LIMITS.STEER_ERROR_MAX), LIMITS.STEER_MAX) - min_lim = max(min(motor_torque - LIMITS.STEER_ERROR_MAX, -LIMITS.STEER_ERROR_MAX), -LIMITS.STEER_MAX) +def apply_dist_to_meas_limits(val, val_last, val_meas, + STEER_DELTA_UP, STEER_DELTA_DOWN, + STEER_ERROR_MAX, STEER_MAX): + # limits due to comparison of commanded val VS measured val (torque/angle/curvature) + max_lim = min(max(val_meas + STEER_ERROR_MAX, STEER_ERROR_MAX), STEER_MAX) + min_lim = max(min(val_meas - STEER_ERROR_MAX, -STEER_ERROR_MAX), -STEER_MAX) - apply_torque = clip(apply_torque, min_lim, max_lim) + val = clip(val, min_lim, max_lim) - # slow rate if steer torque increases in magnitude - if apply_torque_last > 0: - apply_torque = clip(apply_torque, - max(apply_torque_last - LIMITS.STEER_DELTA_DOWN, -LIMITS.STEER_DELTA_UP), - apply_torque_last + LIMITS.STEER_DELTA_UP) + # slow rate if val increases in magnitude + if val_last > 0: + val = clip(val, + max(val_last - STEER_DELTA_DOWN, -STEER_DELTA_UP), + val_last + STEER_DELTA_UP) else: - apply_torque = clip(apply_torque, - apply_torque_last - LIMITS.STEER_DELTA_UP, - min(apply_torque_last + LIMITS.STEER_DELTA_DOWN, LIMITS.STEER_DELTA_UP)) + val = clip(val, + val_last - STEER_DELTA_UP, + min(val_last + STEER_DELTA_DOWN, STEER_DELTA_UP)) - return int(round(float(apply_torque))) + return float(val) + + +def apply_meas_steer_torque_limits(apply_torque, apply_torque_last, motor_torque, LIMITS): + return int(round(apply_dist_to_meas_limits(apply_torque, apply_torque_last, motor_torque, + LIMITS.STEER_DELTA_UP, LIMITS.STEER_DELTA_DOWN, + LIMITS.STEER_ERROR_MAX, LIMITS.STEER_MAX))) + + +def apply_std_steer_angle_limits(apply_angle, apply_angle_last, v_ego, LIMITS): + # pick angle rate limits based on wind up/down + steer_up = apply_angle_last * apply_angle >= 0. and abs(apply_angle) > abs(apply_angle_last) + rate_limits = LIMITS.ANGLE_RATE_LIMIT_UP if steer_up else LIMITS.ANGLE_RATE_LIMIT_DOWN + + angle_rate_lim = interp(v_ego, rate_limits.speed_bp, rate_limits.angle_v) + return clip(apply_angle, apply_angle_last - angle_rate_lim, apply_angle_last + angle_rate_lim) def crc8_pedal(data): diff --git a/selfdrive/car/body/carcontroller.py b/selfdrive/car/body/carcontroller.py index 0d5d780bd3..bcaf6f6f93 100644 --- a/selfdrive/car/body/carcontroller.py +++ b/selfdrive/car/body/carcontroller.py @@ -35,7 +35,7 @@ class CarController: torque -= deadband return torque - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): torque_l = 0 torque_r = 0 @@ -85,6 +85,7 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.accel = torque_l new_actuators.steer = torque_r + new_actuators.steerOutputCan = torque_r self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/body/interface.py b/selfdrive/car/body/interface.py index ae7ab89aab..850a3538ad 100644 --- a/selfdrive/car/body/interface.py +++ b/selfdrive/car/body/interface.py @@ -2,16 +2,13 @@ import math from cereal import car from common.realtime import DT_CTRL -from selfdrive.car import scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.body.values import SPEED_FROM_RPM class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=None, car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.notCar = True ret.carName = "body" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.body)] @@ -27,14 +24,10 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeedFactor = SPEED_FROM_RPM ret.centerToFront = ret.wheelbase * 0.44 - ret.radarOffCan = True + ret.radarUnavailable = True ret.openpilotLongitudinalControl = True ret.steerControlType = car.CarParams.SteerControlType.angle - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): @@ -50,5 +43,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 66f1b947a8..4fef966374 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -17,6 +17,9 @@ class CarControllerParams: LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower STEER_THRESHOLD = 1.0 + def __init__(self, CP): + pass + class CAR: BODY = "COMMA BODY" @@ -26,6 +29,12 @@ CAR_INFO: Dict[str, CarInfo] = { CAR.BODY: CarInfo("comma body", package="All"), } +FINGERPRINTS = { + CAR.BODY: [{ + 513: 8, 516: 8, 514: 3, 515: 4, + }], +} + FW_QUERY_CONFIG = FwQueryConfig( requests=[ Request( @@ -40,10 +49,13 @@ FW_VERSIONS = { CAR.BODY: { (Ecu.engine, 0x720, None): [ b'0.0.01', - b'02/27/2022' + b'02/27/2022', + b'0.3.00a', ], + # git hash of the firmware used (Ecu.debug, 0x721, None): [ - b'166bd860' # git hash of the firmware used + b'166bd860', + b'dc780f85', ], }, } diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 61e7a3d55d..6a131c77de 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -8,7 +8,7 @@ from system.version import is_comma_remote, is_tested_branch from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars from selfdrive.car.vin import get_vin, is_valid_vin, VIN_UNKNOWN -from selfdrive.car.fw_versions import get_fw_versions_ordered, match_fw_to_car, get_present_ecus +from selfdrive.car.fw_versions import get_fw_versions_ordered, get_present_ecus, match_fw_to_car, set_obd_multiplexing from system.swaglog import cloudlog import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint @@ -80,12 +80,13 @@ def fingerprint(logcan, sendcan, num_pandas): fixed_fingerprint = os.environ.get('FINGERPRINT', "") skip_fw_query = os.environ.get('SKIP_FW_QUERY', False) ecu_rx_addrs = set() + params = Params() if not skip_fw_query: # Vin query only reliably works through OBDII bus = 1 - cached_params = Params().get("CarParamsCache") + cached_params = params.get("CarParamsCache") if cached_params is not None: cached_params = car.CarParams.from_bytes(cached_params) if cached_params.carName == "mock": @@ -98,8 +99,9 @@ def fingerprint(logcan, sendcan, num_pandas): cached = True else: cloudlog.warning("Getting VIN & FW versions") + set_obd_multiplexing(params, True) vin_rx_addr, vin = get_vin(logcan, sendcan, bus) - ecu_rx_addrs = get_present_ecus(logcan, sendcan) + ecu_rx_addrs = get_present_ecus(logcan, sendcan, num_pandas=num_pandas) car_fw = get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, num_pandas=num_pandas) cached = False @@ -113,7 +115,11 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.event("Malformed VIN", vin=vin, error=True) vin = VIN_UNKNOWN cloudlog.warning("VIN %s", vin) - Params().put("CarVin", vin) + params.put("CarVin", vin) + + # disable OBD multiplexing for potential ECU knockouts + set_obd_multiplexing(params, False) + params.put_bool("FirmwareQueryDone", True) finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 @@ -173,17 +179,15 @@ def fingerprint(logcan, sendcan, num_pandas): return car_fingerprint, finger, vin, car_fw, source, exact_match -def get_car(logcan, sendcan, num_pandas=1): +def get_car(logcan, sendcan, experimental_long_allowed, num_pandas=1): candidate, fingerprints, vin, car_fw, source, exact_match = fingerprint(logcan, sendcan, num_pandas) if candidate is None: - cloudlog.warning("car doesn't match any fingerprints: %r", fingerprints) + cloudlog.event("car doesn't match any fingerprints", fingerprints=fingerprints, error=True) candidate = "mock" - experimental_long = Params().get_bool("ExperimentalLongitudinalEnabled") - CarInterface, CarController, CarState = interfaces[candidate] - CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long) + CP = CarInterface.get_params(candidate, fingerprints, car_fw, experimental_long_allowed) CP.carVin = vin CP.carFw = car_fw CP.fingerprintSource = source diff --git a/selfdrive/car/chrysler/carcontroller.py b/selfdrive/car/chrysler/carcontroller.py index 5a2d90c64c..b418179e0e 100644 --- a/selfdrive/car/chrysler/carcontroller.py +++ b/selfdrive/car/chrysler/carcontroller.py @@ -1,8 +1,8 @@ from opendbc.can.packer import CANPacker from common.realtime import DT_CTRL -from selfdrive.car import apply_toyota_steer_torque_limits +from selfdrive.car import apply_meas_steer_torque_limits from selfdrive.car.chrysler.chryslercan import create_lkas_hud, create_lkas_command, create_cruise_buttons -from selfdrive.car.chrysler.values import CAR, RAM_CARS, CarControllerParams +from selfdrive.car.chrysler.values import RAM_CARS, CarControllerParams, ChryslerFlags class CarController: @@ -19,7 +19,7 @@ class CarController: self.packer = CANPacker(dbc_name) self.params = CarControllerParams(CP) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] lkas_active = CC.latActive and self.lkas_control_bit_prev @@ -45,13 +45,13 @@ class CarController: self.hud_count += 1 # steering - if self.frame % 2 == 0: + if self.frame % self.params.STEER_STEP == 0: # TODO: can we make this more sane? why is it different for all the cars? lkas_control_bit = self.lkas_control_bit_prev if CS.out.vEgo > self.CP.minSteerSpeed: lkas_control_bit = True - elif self.CP.carFingerprint in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): + elif self.CP.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: if CS.out.vEgo < (self.CP.minSteerSpeed - 3.0): lkas_control_bit = False elif self.CP.carFingerprint in RAM_CARS: @@ -67,7 +67,7 @@ class CarController: # steer torque new_steer = int(round(CC.actuators.steer * self.params.STEER_MAX)) - apply_steer = apply_toyota_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, self.params) + apply_steer = apply_meas_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorqueEps, self.params) if not lkas_active or not lkas_control_bit: apply_steer = 0 self.apply_steer_last = apply_steer @@ -78,5 +78,6 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last return new_actuators, can_sends diff --git a/selfdrive/car/chrysler/carstate.py b/selfdrive/car/chrysler/carstate.py index 0f0d30782a..fdc5aa338a 100644 --- a/selfdrive/car/chrysler/carstate.py +++ b/selfdrive/car/chrysler/carstate.py @@ -81,8 +81,9 @@ class CarState(CarStateBase): if self.CP.carFingerprint in RAM_CARS: self.auto_high_beam = cp_cam.vl["DAS_6"]['AUTO_HIGH_BEAM_ON'] # Auto High Beam isn't Located in this message on chrysler or jeep currently located in 729 message - ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1 + ret.steerFaultTemporary = cp.vl["EPS_3"]["DASM_FAULT"] == 1 else: + ret.steerFaultTemporary = cp.vl["EPS_2"]["LKAS_TEMPORARY_FAULT"] == 1 ret.steerFaultPermanent = cp.vl["EPS_2"]["LKAS_STATE"] == 4 # blindspot sensors @@ -135,6 +136,7 @@ class CarState(CarStateBase): ("COUNTER", "EPS_2",), ("COLUMN_TORQUE", "EPS_2"), ("EPS_TORQUE_MOTOR", "EPS_2"), + ("LKAS_TEMPORARY_FAULT", "EPS_2"), ("LKAS_STATE", "EPS_2"), ("COUNTER", "CRUISE_BUTTONS"), ] diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 245e10650c..303f563c90 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,21 +1,19 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.chrysler.values import CAR, DBC, RAM_HD, RAM_DT +from selfdrive.car import STD_CARGO_KG, get_safety_config +from selfdrive.car.chrysler.values import CAR, RAM_HD, RAM_DT, RAM_CARS, ChryslerFlags from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "chrysler" - ret.dashcamOnly = candidate in RAM_HD - ret.radarOffCan = DBC[candidate]['radar'] is None - + # radar parsing needs some work, see https://github.com/commaai/openpilot/issues/26842 + ret.radarUnavailable = True # DBC[candidate]['radar'] is None ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 @@ -27,15 +25,21 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[0].safetyParam |= Panda.FLAG_CHRYSLER_RAM_DT ret.minSteerSpeed = 3.8 # m/s - if candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019): - # TODO: allow 2019 cars to steer down to 13 m/s if already engaged. - ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + if candidate not in RAM_CARS: + # Newer FW versions standard on the following platforms, or flashed by a dealer onto older platforms have a higher minimum steering speed. + new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_CHEROKEE_2019) + new_eps_firmware = any(fw.ecu == 'eps' and fw.fwVersion[:4] >= b"6841" for fw in car_fw) + if new_eps_platform or new_eps_firmware: + ret.flags |= ChryslerFlags.HIGHER_MIN_STEERING_SPEED.value # Chrysler if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020): ret.mass = 2242. + STD_CARGO_KG ret.wheelbase = 3.089 ret.steerRatio = 16.2 # Pacifica Hybrid 2017 + + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] ret.lateralTuning.pid.kf = 0.00006 @@ -46,6 +50,8 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.71 ret.steerRatio = 16.7 ret.steerActuatorDelay = 0.2 + + ret.lateralTuning.init('pid') ret.lateralTuning.pid.kpBP, ret.lateralTuning.pid.kiBP = [[9., 20.], [9., 20.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.15, 0.30], [0.03, 0.05]] ret.lateralTuning.pid.kf = 0.00006 @@ -56,12 +62,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 3.88 ret.steerRatio = 16.3 ret.mass = 2493. + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) ret.minSteerSpeed = 14.5 - if car_fw is not None: - for fw in car_fw: - if fw.ecu == 'eps' and fw.fwVersion[:8] in (b"68312176", b"68273275"): - ret.minSteerSpeed = 0. + # Older EPS FW allow steer to zero + if any(fw.ecu == 'eps' and fw.fwVersion[:4] <= b"6831" for fw in car_fw): + ret.minSteerSpeed = 0. elif candidate == CAR.RAM_HD: ret.steerActuatorDelay = 0.2 @@ -74,15 +78,11 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") + if ret.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: + # TODO: allow these cars to steer down to 13 m/s if already engaged. + ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. + ret.centerToFront = ret.wheelbase * 0.44 - - # starting with reasonable value for civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - ret.enableBsm = 720 in fingerprint[0] return ret @@ -105,5 +105,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/chrysler/radar_interface.py b/selfdrive/car/chrysler/radar_interface.py index 348e3c3632..0ab8c10b44 100755 --- a/selfdrive/car/chrysler/radar_interface.py +++ b/selfdrive/car/chrysler/radar_interface.py @@ -45,12 +45,13 @@ def _address_to_track(address): class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) + self.CP = CP self.rcp = _create_radar_can_parser(CP.carFingerprint) self.updated_messages = set() self.trigger_msg = LAST_MSG def update(self, can_strings): - if self.rcp is None: + if self.rcp is None or self.CP.radarUnavailable: return super().update(None) vls = self.rcp.update_strings(can_strings) diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index c703ef6cb8..8799f073c9 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,3 +1,4 @@ +from enum import IntFlag from dataclasses import dataclass from enum import Enum from typing import Dict, List, Optional, Union @@ -11,6 +12,10 @@ from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu +class ChryslerFlags(IntFlag): + HIGHER_MIN_STEERING_SPEED = 1 + + class CAR: # Chrysler PACIFICA_2017_HYBRID = "CHRYSLER PACIFICA HYBRID 2017" @@ -30,6 +35,7 @@ class CAR: class CarControllerParams: def __init__(self, CP): + self.STEER_STEP = 2 # 50 Hz self.STEER_ERROR_MAX = 80 if CP.carFingerprint in RAM_HD: self.STEER_DELTA_UP = 14 @@ -44,17 +50,20 @@ class CarControllerParams: self.STEER_DELTA_DOWN = 3 self.STEER_MAX = 261 # higher than this faults the EPS + STEER_THRESHOLD = 120 RAM_DT = {CAR.RAM_1500, } RAM_HD = {CAR.RAM_HD, } RAM_CARS = RAM_DT | RAM_HD + @dataclass class ChryslerCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" harness: Enum = Harness.fca + CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017-18"), CAR.PACIFICA_2018_HYBRID: None, # same platforms @@ -66,10 +75,10 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], CAR.JEEP_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), - CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-22", harness=Harness.ram), + CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-23", harness=Harness.ram), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-22", harness=Harness.ram), - ChryslerCarInfo("Ram 3500 2020-22", harness=Harness.ram), + ChryslerCarInfo("Ram 3500 2019-22", harness=Harness.ram), ], } @@ -86,13 +95,13 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { FINGERPRINTS = { CAR.PACIFICA_2017_HYBRID: [{ - 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 788:3, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 3, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1892: 8, 2016: 8, 2024: 8 + 168: 8, 257: 5, 258: 8, 264: 8, 268: 8, 270: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 291: 8, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 515: 7, 516: 7, 517: 7, 518: 7, 520: 8, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 653: 8, 654: 8, 655: 8, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 701: 8, 704: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 746: 5, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 782: 8, 784: 8, 788: 3, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 840: 8, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 878: 8, 882: 8, 897: 8, 908: 8, 924: 3, 926: 3, 929: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 956: 8, 958: 8, 959: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1082: 8, 1083: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1225: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1284: 8, 1537: 8, 1538: 8, 1562: 8, 1568: 8, 1856: 8, 1858: 8, 1860: 8, 1865: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1892: 8, 2016: 8, 2024: 8 }], CAR.PACIFICA_2018: [{ 55: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 8, 926: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 }, { - 55: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 + 55: 8, 58: 6, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 4, 564: 4, 571: 3, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 678: 8, 680: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 779: 8, 784: 8, 792: 8, 799: 8, 800: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 924: 3, 926: 3, 937: 8, 947: 8, 948: 8, 956: 8, 969: 4, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1537: 8, 1538: 8, 1562: 8 }], CAR.PACIFICA_2020: [{ 55: 8, 179: 8, 181: 8, 257: 5, 258: 8, 264: 8, 268: 8, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 294: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 469: 8, 480: 8, 500: 8, 501: 8, 512: 8, 514: 8, 516: 7, 517: 7, 520: 8, 524: 8, 526: 6, 528: 8, 532: 8, 536: 8, 542: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 624: 8, 625: 8, 632: 8, 639: 8, 650: 8, 656: 4, 658: 6, 660: 8, 669: 3, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 711: 8, 719: 8, 720: 6, 729: 5, 736: 8, 746: 5, 752: 2, 754: 8, 760: 8, 764: 8, 766: 8, 770: 8, 773: 8, 776: 8, 779: 8, 782: 8, 784: 8, 792: 8, 793: 8, 794: 8, 795: 8, 799: 8, 800: 8, 801: 8, 802: 8, 803: 8, 804: 8, 808: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 832: 8, 838: 2, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 886: 8, 897: 8, 906: 8, 924: 8, 926: 3, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 962: 8, 969: 4, 973: 8, 974: 5, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 7, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1246: 8, 1250: 8, 1251: 8, 1252: 8, 1284: 8, 1543: 8, 1568: 8, 1570: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1886: 8, 1890: 8, 1891: 8, 1892: 8, 1898: 8, 2015: 8, 2016: 8, 2017:8, 2024: 8, 2025: 8 @@ -128,7 +137,7 @@ FINGERPRINTS = { }], CAR.JEEP_CHEROKEE_2019: [{ # Jeep Grand Cherokee 2019, including most 2020 models - 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 + 55: 8, 168: 8, 179: 8, 181: 8, 256: 4, 257: 5, 258: 8, 264: 8, 268: 8, 272: 6, 273: 6, 274: 2, 280: 8, 284: 8, 288: 7, 290: 6, 292: 8, 300: 8, 308: 8, 320: 8, 324: 8, 331: 8, 332: 8, 341: 8, 344: 8, 352: 8, 362: 8, 368: 8, 376: 3, 384: 8, 388: 4, 416: 7, 448: 6, 456: 4, 464: 8, 500: 8, 501: 8, 512: 8, 514: 8, 520: 8, 530: 8, 532: 8, 544: 8, 557: 8, 559: 8, 560: 8, 564: 8, 571: 3, 579: 8, 584: 8, 608: 8, 618: 8, 624: 8, 625: 8, 632: 8, 639: 8, 640: 1, 656: 4, 658: 6, 660: 8, 671: 8, 672: 8, 676: 8, 678: 8, 680: 8, 683: 8, 684: 8, 703: 8, 705: 8, 706: 8, 709: 8, 710: 8, 719: 8, 720: 6, 729: 5, 736: 8, 737: 8, 738: 8, 746: 5, 752: 2, 754: 8, 760: 8, 761: 8, 764: 8, 766: 8, 773: 8, 776: 8, 779: 8, 782: 8, 783: 8, 784: 8, 785: 8, 792: 8, 799: 8, 800: 8, 804: 8, 806: 2, 808: 8, 810: 8, 816: 8, 817: 8, 820: 8, 825: 2, 826: 8, 831: 6, 832: 8, 838: 2, 840: 8, 844: 5, 847: 1, 848: 8, 853: 8, 856: 4, 860: 6, 863: 8, 874: 2, 882: 8, 897: 8, 906: 8, 924: 8, 937: 8, 938: 8, 939: 8, 940: 8, 941: 8, 942: 8, 943: 8, 947: 8, 948: 8, 960: 4, 968: 8, 969: 4, 970: 8, 973: 8, 974: 5, 976: 8, 977: 4, 979: 8, 980: 8, 981: 8, 982: 8, 983: 8, 984: 8, 992: 8, 993: 7, 995: 8, 996: 8, 1000: 8, 1001: 8, 1002: 8, 1003: 8, 1008: 8, 1009: 8, 1010: 8, 1011: 8, 1012: 8, 1013: 8, 1014: 8, 1015: 8, 1024: 8, 1025: 8, 1026: 8, 1031: 8, 1033: 8, 1050: 8, 1059: 8, 1062: 8, 1098: 8, 1100: 8, 1216: 8, 1218: 8, 1220: 8, 1223: 8, 1225: 8, 1227: 8, 1235: 8, 1242: 8, 1250: 8, 1251: 8, 1252: 8, 1254: 8, 1264: 8, 1284: 8, 1536: 8, 1537: 8, 1538: 8, 1543: 8, 1545: 8, 1562: 8, 1568: 8, 1570: 8, 1572: 8, 1593: 8, 1856: 8, 1858: 8, 1860: 8, 1863: 8, 1865: 8, 1867: 8, 1875: 8, 1882: 8, 1890: 8, 1891: 8, 1892: 8, 1894: 8, 1896: 8, 1904: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], } @@ -173,78 +182,153 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { - CAR.RAM_1500: { + CAR.JEEP_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ - b'68294063AH', - b'68294063AG', - b'68434860AC', - b'68527375AD', - b'68453503AC', + b'68402971AD', ], (Ecu.srs, 0x744, None): [ - b'68441329AB', - b'68490898AA', - b'68428609AB', - b'68500728AA', + b'68355363AB', ], (Ecu.abs, 0x747, None): [ - b'68432418AD', - b'68432418AB', - b'68436004AE', - b'68438454AD', - b'68436004AD', - b'68535469AB', - b'68438454AC', + b'68408639AD', ], (Ecu.fwdRadar, 0x753, None): [ - b'68320950AL', - b'68320950AJ', - b'68454268AB', - b'68475160AG', - b'04672892AB', - b'68475160AE', + b'68456722AC', ], (Ecu.eps, 0x75A, None): [ - b'68273275AG', - b'68469901AA', - b'68552788AA', + b'68453431AA', ], (Ecu.engine, 0x7e0, None): [ - b'68448163AJ', - b'68500630AD', - b'68539650AD', + b'05035674AB ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05035707AA', + ], + }, + + CAR.RAM_1500: { + (Ecu.combinationMeter, 0x742, None): [ + b'68294051AG', + b'68294051AI', + b'68294052AG', + b'68294063AG', + b'68294063AH', + b'68294063AI', + b'68434846AC', + b'68434858AC', + b'68434860AC', + b'68453503AC', + b'68453505AC', + b'68453511AC', + b'68453513AD', + b'68453514AD', + b'68510283AG', + b'68527375AD', + ], + (Ecu.srs, 0x744, None): [ + b'68428609AB', + b'68441329AB', + b'68473844AB', + b'68490898AA', + b'68500728AA', + b'68615033AA', + ], + (Ecu.abs, 0x747, None): [ + b'68292406AH', + b'68432418AB', + b'68432418AD', + b'68436004AD', + b'68436004AE', + b'68438454AC', + b'68438454AD', + b'68438456AE', + b'68438456AF', + b'68535469AB', + b'68535470AC', + b'68586307AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'04672892AB', + b'04672932AB', + b'68320950AH', + b'68320950AI', + b'68320950AJ', + b'68320950AL', + b'68320950AM', + b'68454268AB', + b'68475160AE', + b'68475160AF', + b'68475160AG', + ], + (Ecu.eps, 0x75A, None): [ + b'68273275AF', + b'68273275AG', + b'68312176AE', + b'68312176AG', + b'68440789AC', + b'68466110AB', + b'68469901AA', + b'68522583AB', + b'68522585AB', + b'68552788AA', + b'68552790AA', + b'68585112AB', + ], + (Ecu.engine, 0x7e0, None): [ + b'05036065AE ', + b'05036066AE ', + b'68378701AI ', b'68378758AM ', + b'68448163AJ', + b'68448165AK', + b'68500630AD', + b'68500630AE', + b'68539650AD', ], (Ecu.transmission, 0x7e1, None): [ b'68360078AL', - b'68384328AD', - b'68360085AL', + b'68360080AM', b'68360081AM', - b'68502994AD', + b'68360085AL', + b'68384328AD', + b'68384332AD', b'68445533AB', - b'68540431AB', b'68484467AC', + b'68502994AD', + b'68540431AB', ], }, CAR.RAM_HD: { (Ecu.combinationMeter, 0x742, None): [ b'68361606AH', + b'68437735AC', b'68492693AD', + b'68525485AB', + b'68525487AB', + b'68525498AB', ], (Ecu.srs, 0x744, None): [ b'68399794AC', b'68428503AA', b'68428505AA', + b'68428507AA', ], (Ecu.abs, 0x747, None): [ b'68334977AH', + b'68455481AC', + b'68504022AA', b'68504022AB', + b'68504022AC', b'68530686AB', + b'68530686AC', ], (Ecu.fwdRadar, 0x753, None): [ b'04672895AB', b'56029827AG', + b'56029827AH', + b'68462657AE', + b'68484694AD', b'68484694AE', ], (Ecu.eps, 0x761, None): [ @@ -252,7 +336,13 @@ FW_VERSIONS = { b'68507906AB', ], (Ecu.engine, 0x7e0, None): [ + b'52370131AF', + b'52370231AF', + b'52370231AG', + b'52370931CT', + b'52401032AE', b'52421132AF', + b'68527616AD ', b'M2370131MB', b'M2421132MB', ], diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index e948698cb4..bc03619d0d 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -7,14 +7,15 @@ from enum import Enum from natsort import natsorted from typing import Dict, List +from cereal import car from common.basedir import BASEDIR from selfdrive.car import gen_empty_fingerprint -from selfdrive.car.docs_definitions import CarInfo, Column +from selfdrive.car.docs_definitions import CarInfo, Column, CommonFootnote from selfdrive.car.car_helpers import interfaces, get_interface_attr def get_all_footnotes() -> Dict[Enum, int]: - all_footnotes = [] + all_footnotes = list(CommonFootnote) for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): all_footnotes.extend(footnotes) return {fn: idx + 1 for idx, fn in enumerate(all_footnotes)} @@ -28,7 +29,7 @@ def get_all_car_info() -> List[CarInfo]: all_car_info: List[CarInfo] = [] footnotes = get_all_footnotes() for model, car_info in get_interface_attr("CAR_INFO", combine_brands=True).items(): - CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), experimental_long=True) + CP = interfaces[model][0].get_params(model, fingerprint=gen_empty_fingerprint(), car_fw=[car.CarParams.CarFw(ecu="unknown")], experimental_long=False) if CP.dashcamOnly or car_info is None: continue @@ -39,6 +40,7 @@ def get_all_car_info() -> List[CarInfo]: for _car_info in car_info: if not hasattr(_car_info, "row"): + _car_info.init_make(CP) _car_info.init(CP, footnotes) all_car_info.append(_car_info) diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index 015877dcb4..c0fb4420df 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -21,6 +21,7 @@ class Column(Enum): STEERING_TORQUE = "Steering Torque" AUTO_RESUME = "Resume from stop" HARNESS = "Harness" + VIDEO = "Video" class Star(Enum): @@ -68,7 +69,17 @@ class Harness(Enum): none = "None" -CarFootnote = namedtuple("CarFootnote", ["text", "column"], defaults=[None]) +CarFootnote = namedtuple("CarFootnote", ["text", "column", "docs_only", "shop_footnote"], defaults=(False, False)) + + +class CommonFootnote(Enum): + EXP_LONG_AVAIL = CarFootnote( + "Experimental openpilot longitudinal control is available behind a toggle; the toggle is only available in non-release branches such as `devel` or `master-ci`. ", + Column.LONGITUDINAL, docs_only=True) + EXP_LONG_DSU = CarFootnote( + "By default, this car will use the stock Adaptive Cruise Control (ACC) for longitudinal control. If the Driver Support Unit (DSU) is disconnected, openpilot ACC will replace " + + "stock ACC. NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", + Column.LONGITUDINAL) def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]: @@ -115,6 +126,22 @@ class CarInfo: harness: Enum = Harness.none def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): + self.car_name = CP.carName + self.car_fingerprint = CP.carFingerprint + self.make, self.model, self.years = split_name(self.name) + + # longitudinal column + op_long = "Stock" + if CP.openpilotLongitudinalControl and not CP.enableDsu: + op_long = "openpilot" + elif CP.experimentalLongitudinalAvailable or CP.enableDsu: + op_long = "openpilot available" + if CP.enableDsu: + self.footnotes.append(CommonFootnote.EXP_LONG_DSU) + else: + self.footnotes.append(CommonFootnote.EXP_LONG_AVAIL) + + # min steer & enable speed columns # TODO: set all the min steer speeds in carParams and remove this if self.min_steer_speed is not None: assert CP.minSteerSpeed == 0, f"{CP.carFingerprint}: Minimum steer speed set in both CarInfo and CarParams" @@ -125,19 +152,23 @@ class CarInfo: if self.min_enable_speed is None: self.min_enable_speed = CP.minEnableSpeed - self.car_name = CP.carName - self.car_fingerprint = CP.carFingerprint - self.make, self.model, self.years = split_name(self.name) + # harness column + harness_col = self.harness.value + if self.harness is not Harness.none: + model_years = self.model + (' ' + self.years if self.years else '') + harness_col = f'{harness_col}' + self.row = { Column.MAKE: self.make, Column.MODEL: self.model, Column.PACKAGE: self.package, - Column.LONGITUDINAL: "openpilot" if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable else "Stock", + Column.LONGITUDINAL: op_long, Column.FSR_LONGITUDINAL: f"{max(self.min_enable_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.FSR_STEERING: f"{max(self.min_steer_speed * CV.MS_TO_MPH, 0):.0f} mph", Column.STEERING_TORQUE: Star.EMPTY, Column.AUTO_RESUME: Star.FULL if CP.autoResumeSng else Star.EMPTY, - Column.HARNESS: self.harness.value, + Column.HARNESS: harness_col, + Column.VIDEO: self.video_link if self.video_link is not None else "", # replaced with an image and link from template in get_column } # Set steering torque star from max lateral acceleration @@ -151,6 +182,9 @@ class CarInfo: return self + def init_make(self, CP: car.CarParams): + """CarInfo subclasses can add make-specific logic for harness selection, footnotes, etc.""" + def get_detail_sentence(self, CP): if not CP.notCar: sentence_builder = "openpilot upgrades your {car_model} with automated lane centering{alc} and adaptive cruise control{acc}." @@ -170,6 +204,13 @@ class CarInfo: if self.row[Column.STEERING_TORQUE] != Star.FULL: sentence_builder += " This car may not be able to take tight turns on its own." + # experimental mode + exp_link = "Experimental mode" + if CP.openpilotLongitudinalControl or CP.experimentalLongitudinalAvailable: + sentence_builder += f" Traffic light and stop sign handling is also available in {exp_link}." + else: + sentence_builder += f" {exp_link}, with traffic light and stop sign handling, is not currently available for this car, but may be added in a future software update." + return sentence_builder.format(car_model=f"{self.make} {self.model}", alc=alc, acc=acc) else: @@ -178,12 +219,14 @@ class CarInfo: else: raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}") - def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str: + def get_column(self, column: Column, star_icon: str, video_icon: str, footnote_tag: str) -> str: item: Union[str, Star] = self.row[column] if isinstance(item, Star): item = star_icon.format(item.value) elif column == Column.MODEL and len(self.years): item += f" {self.years}" + elif column == Column.VIDEO and len(item) > 0: + item = video_icon.format(item) footnotes = get_footnotes(self.footnotes, column) if len(footnotes): diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index 9f6ace2b5f..86bae91b06 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -9,6 +9,8 @@ from selfdrive.car import make_can_msg from selfdrive.boardd.boardd import can_list_to_can_capnp from system.swaglog import cloudlog +EcuAddrBusType = Tuple[int, Optional[int], int] + def make_tester_present_msg(addr, bus, subaddr=None): dat = [0x02, SERVICE_TYPE.TESTER_PRESENT, 0x0] @@ -33,16 +35,16 @@ def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subadd return False -def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[Tuple[int, Optional[int], int]]: +def get_all_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, bus: int, timeout: float = 1, debug: bool = True) -> Set[EcuAddrBusType]: addr_list = [0x700 + i for i in range(256)] + [0x18da00f1 + (i << 8) for i in range(256)] - queries: Set[Tuple[int, Optional[int], int]] = {(addr, None, bus) for addr in addr_list} + queries: Set[EcuAddrBusType] = {(addr, None, bus) for addr in addr_list} responses = queries return get_ecu_addrs(logcan, sendcan, queries, responses, timeout=timeout, debug=debug) -def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[Tuple[int, Optional[int], int]], - responses: Set[Tuple[int, Optional[int], int]], timeout: float = 1, debug: bool = False) -> Set[Tuple[int, Optional[int], int]]: - ecu_responses: Set[Tuple[int, Optional[int], int]] = set() # set((addr, subaddr, bus),) +def get_ecu_addrs(logcan: messaging.SubSocket, sendcan: messaging.PubSocket, queries: Set[EcuAddrBusType], + responses: Set[EcuAddrBusType], timeout: float = 1, debug: bool = False) -> Set[EcuAddrBusType]: + ecu_responses: Set[EcuAddrBusType] = set() # set((addr, subaddr, bus),) try: msgs = [make_tester_present_msg(addr, bus, subaddr) for addr, subaddr, bus in queries] @@ -87,5 +89,5 @@ if __name__ == "__main__": for addr, subaddr, bus in ecu_addrs: msg = f" 0x{hex(addr)}" if subaddr is not None: - msg += f" (sub-address: 0x{hex(subaddr)})" + msg += f" (sub-address: {hex(subaddr)})" print(msg) diff --git a/selfdrive/car/ford/carcontroller.py b/selfdrive/car/ford/carcontroller.py index f18014601c..99072ae975 100644 --- a/selfdrive/car/ford/carcontroller.py +++ b/selfdrive/car/ford/carcontroller.py @@ -1,28 +1,14 @@ -import math from cereal import car -from common.numpy_fast import clip, interp +from common.numpy_fast import clip from opendbc.can.packer import CANPacker -from selfdrive.car.ford import fordcan -from selfdrive.car.ford.values import CANBUS, CarControllerParams +from selfdrive.car import apply_std_steer_angle_limits +from selfdrive.car.ford.fordcan import create_acc_command, create_acc_ui_msg, create_button_msg, create_lat_ctl_msg, \ + create_lat_ctl2_msg, create_lka_msg, create_lkas_ui_msg +from selfdrive.car.ford.values import CANBUS, CANFD_CARS, CarControllerParams VisualAlert = car.CarControl.HUDControl.VisualAlert -def apply_ford_steer_angle_limits(apply_angle, apply_angle_last, vEgo): - # rate limit - steer_up = apply_angle_last * apply_angle > 0. and abs(apply_angle) > abs(apply_angle_last) - rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN - max_angle_diff = interp(vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points) - apply_angle = clip(apply_angle, (apply_angle_last - max_angle_diff), (apply_angle_last + max_angle_diff)) - - # absolute limit (LatCtlPath_An_Actl) - apply_path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO - apply_path_angle = clip(apply_path_angle, -0.5, 0.5235) - apply_angle = math.degrees(apply_path_angle) * CarControllerParams.LKAS_STEER_RATIO - - return apply_angle - - class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -30,12 +16,12 @@ class CarController: self.packer = CANPacker(dbc_name) self.frame = 0 - self.apply_angle_last = 0 + self.apply_curvature_last = 0 self.main_on_last = False self.lkas_enabled_last = False self.steer_alert_last = False - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] actuators = CC.actuators @@ -46,66 +32,69 @@ class CarController: ### acc buttons ### if CC.cruiseControl.cancel: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True)) - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, cancel=True, bus=CANBUS.main)) elif CC.cruiseControl.resume and (self.frame % CarControllerParams.BUTTONS_STEP) == 0: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True)) - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True)) + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, resume=True, bus=CANBUS.main)) # if stock lane centering isn't off, send a button press to toggle it off # the stock system checks for steering pressed, and eventually disengages cruise control elif CS.acc_tja_status_stock_values["Tja_D_Stat"] != 0 and (self.frame % CarControllerParams.ACC_UI_STEP) == 0: - can_sends.append(fordcan.create_button_command(self.packer, CS.buttons_stock_values, tja_toggle=True)) - + can_sends.append(create_button_msg(self.packer, CS.buttons_stock_values, tja_toggle=True)) ### lateral control ### - if CC.latActive: - lca_rq = 1 - apply_angle = apply_ford_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo) - else: - lca_rq = 0 - apply_angle = 0. - # send steering commands at 20Hz - if (self.frame % CarControllerParams.LKAS_STEER_STEP) == 0: - # use LatCtlPath_An_Actl to actuate steering - path_angle = math.radians(apply_angle) / CarControllerParams.LKAS_STEER_RATIO - - # set slower ramp type when small steering angle change - # 0=Slow, 1=Medium, 2=Fast, 3=Immediately - steer_change = abs(CS.out.steeringAngleDeg - actuators.steeringAngleDeg) - if steer_change < 2.0: - ramp_type = 0 - elif steer_change < 4.0: - ramp_type = 1 - elif steer_change < 6.0: - ramp_type = 2 + if (self.frame % CarControllerParams.STEER_STEP) == 0: + if CC.latActive: + # apply limits to curvature and clip to signal range + apply_curvature = apply_std_steer_angle_limits(actuators.curvature, self.apply_curvature_last, CS.out.vEgo, CarControllerParams) + apply_curvature = clip(apply_curvature, -CarControllerParams.CURVATURE_MAX, CarControllerParams.CURVATURE_MAX) else: - ramp_type = 3 - precision = 1 # 0=Comfortable, 1=Precise (the stock system always uses comfortable) + apply_curvature = 0. - self.apply_angle_last = apply_angle - can_sends.append(fordcan.create_lka_command(self.packer, 0, 0)) - can_sends.append(fordcan.create_tja_command(self.packer, lca_rq, ramp_type, precision, - 0, path_angle, 0, 0)) + self.apply_curvature_last = apply_curvature + can_sends.append(create_lka_msg(self.packer)) + if self.CP.carFingerprint in CANFD_CARS: + # TODO: extended mode + mode = 1 if CC.latActive else 0 + counter = self.frame // CarControllerParams.STEER_STEP + can_sends.append(create_lat_ctl2_msg(self.packer, mode, 0., 0., -apply_curvature, 0., counter)) + else: + can_sends.append(create_lat_ctl_msg(self.packer, CC.latActive, 0., 0., -apply_curvature, 0.)) + + ### longitudinal control ### + # send acc command at 50Hz + if self.CP.openpilotLongitudinalControl and (self.frame % CarControllerParams.ACC_CONTROL_STEP) == 0: + accel = clip(actuators.accel, CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX) + + precharge_brake = accel < -0.1 + if accel > -0.5: + gas = accel + decel = False + else: + gas = -5.0 + decel = True + + can_sends.append(create_acc_command(self.packer, CC.longActive, gas, accel, precharge_brake, decel)) ### ui ### send_ui = (self.main_on_last != main_on) or (self.lkas_enabled_last != CC.latActive) or (self.steer_alert_last != steer_alert) # send lkas ui command at 1Hz or if ui state changes if (self.frame % CarControllerParams.LKAS_UI_STEP) == 0 or send_ui: - can_sends.append(fordcan.create_lkas_ui_command(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) + can_sends.append(create_lkas_ui_msg(self.packer, main_on, CC.latActive, steer_alert, hud_control, CS.lkas_status_stock_values)) # send acc ui command at 20Hz or if ui state changes if (self.frame % CarControllerParams.ACC_UI_STEP) == 0 or send_ui: - can_sends.append(fordcan.create_acc_ui_command(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values)) + can_sends.append(create_acc_ui_msg(self.packer, main_on, CC.latActive, hud_control, CS.acc_tja_status_stock_values)) self.main_on_last = main_on self.lkas_enabled_last = CC.latActive self.steer_alert_last = steer_alert new_actuators = actuators.copy() - new_actuators.steeringAngleDeg = self.apply_angle_last + new_actuators.curvature = self.apply_curvature_last self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 2276b1208a..21af1062f3 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -14,7 +14,7 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) if CP.transmissionType == TransmissionType.automatic: - self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"] + self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"] def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -37,7 +37,7 @@ class CarState(CarStateBase): # steering wheel ret.steeringAngleDeg = cp.vl["SteeringPinion_Data"]["StePinComp_An_Est"] ret.steeringTorque = cp.vl["EPAS_INFO"]["SteeringColumnTorque"] - ret.steeringPressed = abs(ret.steeringTorque) > CarControllerParams.STEER_DRIVER_ALLOWANCE + ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > CarControllerParams.STEER_DRIVER_ALLOWANCE, 5) ret.steerFaultTemporary = cp.vl["EPAS_INFO"]["EPAS_Failure"] == 1 ret.steerFaultPermanent = cp.vl["EPAS_INFO"]["EPAS_Failure"] in (2, 3) # ret.espDisabled = False # TODO: find traction control signal @@ -51,7 +51,7 @@ class CarState(CarStateBase): # gear if self.CP.transmissionType == TransmissionType.automatic: - gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnGear_D_RqDrv"], None) + gear = self.shifter_values.get(cp.vl["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"]) ret.gearShifter = self.parse_gear_shifter(gear) elif self.CP.transmissionType == TransmissionType.manual: ret.clutchPressed = cp.vl["Engine_Clutch_Data"]["CluPdlPos_Pc_Meas"] > 0 @@ -118,7 +118,7 @@ class CarState(CarStateBase): ("DrStatRl_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear left ("DrStatRr_B_Actl", "BodyInfo_3_FD1"), # BCM Door open, rear right ("FirstRowBuckleDriver", "RCMStatusMessage2_FD1"), # RCM Seatbelt status, driver - ("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthru the remaining buttons + ("HeadLghtHiFlash_D_Stat", "Steering_Data_FD1"), # SCCM Passthrough the remaining buttons ("WiprFront_D_Stat", "Steering_Data_FD1"), ("LghtAmb_D_Sns", "Steering_Data_FD1"), ("AccButtnGapDecPress", "Steering_Data_FD1"), @@ -171,7 +171,7 @@ class CarState(CarStateBase): if CP.transmissionType == TransmissionType.automatic: signals += [ - ("TrnGear_D_RqDrv", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position + ("TrnRng_D_RqGsm", "Gear_Shift_by_Wire_FD1"), # GWM transmission gear position ] checks += [ ("Gear_Shift_by_Wire_FD1", 10), diff --git a/selfdrive/car/ford/fordcan.py b/selfdrive/car/ford/fordcan.py index 373ce096c6..594d50f59f 100644 --- a/selfdrive/car/ford/fordcan.py +++ b/selfdrive/car/ford/fordcan.py @@ -4,64 +4,120 @@ from selfdrive.car.ford.values import CANBUS HUDControl = car.CarControl.HUDControl -def create_lka_command(packer, angle_deg: float, curvature: float): +def calculate_lat_ctl2_checksum(mode: int, counter: int, dat: bytearray): + checksum = mode + counter + checksum += dat[2] + ((dat[3] & 0xE0) >> 5) # curvature + checksum += dat[6] + ((dat[7] & 0xE0) >> 5) # curvature rate + checksum += (dat[3] & 0x1F) + ((dat[4] & 0xFC) >> 2) # path angle + checksum += (dat[4] & 0x3) + dat[5] # path offset + return 0xFF - (checksum & 0xFF) + + +def create_lka_msg(packer): """ - Creates a CAN message for the Ford LKAS Command. + Creates an empty CAN message for the Ford LKA Command. This command can apply "Lane Keeping Aid" manoeuvres, which are subject to the PSCM lockout. Frequency is 20Hz. """ - values = { - "LkaDrvOvrrd_D_Rq": 0, # driver override level? [0|3] - "LkaActvStats_D2_Req": 0, # action [0|7] - "LaRefAng_No_Req": angle_deg, # angle [-102.4|102.3] degrees - "LaRampType_B_Req": 0, # Ramp speed: 0=Smooth, 1=Quick - "LaCurvature_No_Calc": curvature, # curvature [-0.01024|0.01023] 1/meter - "LdwActvStats_D_Req": 0, # LDW status [0|7] - "LdwActvIntns_D_Req": 0, # LDW intensity [0|3], shake alert strength - } - return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, values) + return packer.make_can_msg("Lane_Assist_Data1", CANBUS.main, {}) -def create_tja_command(packer, lca_rq: int, ramp_type: int, precision: int, path_offset: float, path_angle: float, curvature_rate: float, curvature: float): +def create_lat_ctl_msg(packer, lat_active: bool, path_offset: float, path_angle: float, curvature: float, + curvature_rate: float): """ Creates a CAN message for the Ford TJA/LCA Command. - This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam - assist and highway driving. It is not subject to the PSCM lockout. + This command can apply "Lane Centering" manoeuvres: continuous lane centering for traffic jam assist and highway + driving. It is not subject to the PSCM lockout. - Ford lane centering command uses a third order polynomial to describe the road centerline. The - polynomial is defined by the following coefficients: - c0: lateral offset between the vehicle and the centerline - c1: heading angle between the vehicle and the centerline - c2: curvature of the centerline + Ford lane centering command uses a third order polynomial to describe the road centerline. The polynomial is defined + by the following coefficients: + c0: lateral offset between the vehicle and the centerline (positive is right) + c1: heading angle between the vehicle and the centerline (positive is right) + c2: curvature of the centerline (positive is left) c3: rate of change of curvature of the centerline - As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and - speed, the steering angle cannot be easily controlled. + As the PSCM combines this information with other sensor data, such as the vehicle's yaw rate and speed, the steering + angle cannot be easily controlled. - The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. - This can be done using tools such as Forscan. + The PSCM should be configured to accept TJA/LCA commands before these commands will be processed. This can be done + using tools such as Forscan. Frequency is 20Hz. """ values = { - "LatCtlRng_L_Max": 0, # Unknown [0|126] meter - "HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1] - "LatCtl_D_Rq": lca_rq, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, 3=InterventionRight, 4-7=NotUsed [0|7] - "LatCtlRampType_D_Rq": ramp_type, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] - "LatCtlPrecision_D_Rq": precision, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] - "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter - "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians - "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 - "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter + "LatCtlRng_L_Max": 0, # Unknown [0|126] meter + "HandsOffCnfm_B_Rq": 0, # Unknown: 0=Inactive, 1=Active [0|1] + "LatCtl_D_Rq": 1 if lat_active else 0, # Mode: 0=None, 1=ContinuousPathFollowing, 2=InterventionLeft, + # 3=InterventionRight, 4-7=NotUsed [0|7] + "LatCtlRampType_D_Rq": 0, # Ramp speed: 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] + # Makes no difference with curvature control + "LatCtlPrecision_D_Rq": 1, # Precision: 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] + # The stock system always uses comfortable + "LatCtlPathOffst_L_Actl": path_offset, # Path offset [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # Path angle [-0.5|0.5235] radians + "LatCtlCurv_NoRate_Actl": curvature_rate, # Curvature rate [-0.001024|0.00102375] 1/meter^2 + "LatCtlCurv_No_Actl": curvature, # Curvature [-0.02|0.02094] 1/meter } return packer.make_can_msg("LateralMotionControl", CANBUS.main, values) -def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict): +def create_lat_ctl2_msg(packer, mode: int, path_offset: float, path_angle: float, curvature: float, + curvature_rate: float, counter: int): + """ + Create a CAN message for the new Ford Lane Centering command. + + This message is used on the CAN FD platform and replaces the old LateralMotionControl message. It is similar but has + additional signals for a counter and checksum. + + Frequency is 20Hz. + """ + + values = { + "LatCtl_D2_Rq": mode, # Mode: 0=None, 1=PathFollowingLimitedMode, 2=PathFollowingExtendedMode, + # 3=SafeRampOut, 4-7=NotUsed [0|7] + "LatCtlRampType_D_Rq": 0, # 0=Slow, 1=Medium, 2=Fast, 3=Immediate [0|3] + "LatCtlPrecision_D_Rq": 1, # 0=Comfortable, 1=Precise, 2/3=NotUsed [0|3] + "LatCtlPathOffst_L_Actl": path_offset, # [-5.12|5.11] meter + "LatCtlPath_An_Actl": path_angle, # [-0.5|0.5235] radians + "LatCtlCurv_No_Actl": curvature, # [-0.02|0.02094] 1/meter + "LatCtlCrv_NoRate2_Actl": curvature_rate, # [-0.001024|0.001023] 1/meter^2 + "HandsOffCnfm_B_Rq": 0, # 0=Inactive, 1=Active [0|1] + "LatCtlPath_No_Cnt": counter, # [0|15] + "LatCtlPath_No_Cs": 0, # [0|255] + } + + # calculate checksum + dat = packer.make_can_msg("LateralMotionControl2", CANBUS.main, values)[2] + values["LatCtlPath_No_Cs"] = calculate_lat_ctl2_checksum(mode, counter, dat) + + return packer.make_can_msg("LateralMotionControl2", CANBUS.main, values) + + +def create_acc_command(packer, long_active: bool, gas: float, accel: float, precharge_brake: bool, decel: bool): + """ + Creates a CAN message for the Ford ACC Command. + + This command can be used to enable ACC, to set the ACC gas/brake/decel values + and to disable ACC. + + Frequency is 50Hz. + """ + + values = { + "AccBrkTot_A_Rq": accel, # Brake total accel request: [-20|11.9449] m/s^2 + "Cmbb_B_Enbl": 1 if long_active else 0, # Enabled: 0=No, 1=Yes + "AccPrpl_A_Rq": gas, # Acceleration request: [-5|5.23] m/s^2 + "AccBrkPrchg_B_Rq": 1 if precharge_brake else 0, # Pre-charge brake request: 0=No, 1=Yes + "AccBrkDecel_B_Rq": 1 if decel else 0, # Deceleration request: 0=Inactive, 1=Active + } + return packer.make_can_msg("ACCDATA", CANBUS.main, values) + + +def create_lkas_ui_msg(packer, main_on: bool, enabled: bool, steer_alert: bool, hud_control, stock_values: dict): """ Creates a CAN message for the Ford IPC IPMA/LKAS status. @@ -107,16 +163,15 @@ def create_lkas_ui_command(packer, main_on: bool, enabled: bool, steer_alert: bo values = { **stock_values, - "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31] - "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed + "LaActvStats_D_Dsply": lines, # LKAS status (lines) [0|31] + "LaHandsOff_D_Dsply": hands_on_wheel_dsply, # 0=HandsOn, 1=Level1 (w/o chime), 2=Level2 (w/ chime), 3=Suppressed } return packer.make_can_msg("IPMA_Data", CANBUS.main, values) -def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): +def create_acc_ui_msg(packer, main_on: bool, enabled: bool, hud_control, stock_values: dict): """ - Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam - assist status. + Creates a CAN message for the Ford IPC adaptive cruise, forward collision warning and traffic jam assist status. Stock functionality is maintained by passing through unmodified signals. @@ -148,7 +203,8 @@ def create_acc_ui_command(packer, main_on: bool, enabled: bool, hud_control, sto return packer.make_can_msg("ACCDATA_3", CANBUS.main, values) -def create_button_command(packer, stock_values: dict, cancel = False, resume = False, tja_toggle = False, bus: int = CANBUS.camera): +def create_button_msg(packer, stock_values: dict, cancel=False, resume=False, tja_toggle=False, + bus: int = CANBUS.camera): """ Creates a CAN message for the Ford SCCM buttons/switches. diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 4943db076f..9e1366618c 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -1,46 +1,53 @@ #!/usr/bin/env python3 from cereal import car from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.ford.values import CAR, Ecu, TransmissionType, GearShifter +from selfdrive.car import STD_CARGO_KG, get_safety_config +from selfdrive.car.ford.values import CAR, Ecu from selfdrive.car.interfaces import CarInterfaceBase -CarParams = car.CarParams +TransmissionType = car.CarParams.TransmissionType +GearShifter = car.CarState.GearShifter class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - if car_fw is None: - car_fw = [] - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "ford" + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.ford)] + + # These cars are dashcam only until the port is finished ret.dashcamOnly = True - ret.safetyConfigs = [get_safety_config(CarParams.SafetyModel.ford)] - # Angle-based steering - ret.steerControlType = CarParams.SteerControlType.angle - ret.steerActuatorDelay = 0.4 + ret.radarUnavailable = True + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.steerActuatorDelay = 0.2 ret.steerLimitTimer = 1.0 - tire_stiffness_factor = 1.0 - if candidate == CAR.ESCAPE_MK4: + if candidate == CAR.BRONCO_SPORT_MK1: + ret.wheelbase = 2.67 + ret.steerRatio = 17.7 + ret.mass = 1625 + STD_CARGO_KG + + elif candidate == CAR.ESCAPE_MK4: ret.wheelbase = 2.71 - ret.steerRatio = 14.3 # Copied from Focus + ret.steerRatio = 16.7 ret.mass = 1750 + STD_CARGO_KG elif candidate == CAR.EXPLORER_MK6: ret.wheelbase = 3.025 - ret.steerRatio = 16.8 # learned + ret.steerRatio = 16.8 ret.mass = 2050 + STD_CARGO_KG elif candidate == CAR.FOCUS_MK4: ret.wheelbase = 2.7 - ret.steerRatio = 13.8 # learned + ret.steerRatio = 15.0 ret.mass = 1350 + STD_CARGO_KG + elif candidate == CAR.MAVERICK_MK1: + ret.wheelbase = 3.076 + ret.steerRatio = 17.0 + ret.mass = 1650 + STD_CARGO_KG + else: raise ValueError(f"Unsupported car: {candidate}") @@ -60,10 +67,7 @@ class CarInterface(CarInterfaceBase): ret.minSteerSpeed = 0. ret.autoResumeSng = ret.minEnableSpeed == -1. - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.44 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) return ret def _update(self, c): @@ -74,5 +78,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/ford/radar_interface.py b/selfdrive/car/ford/radar_interface.py index c942703002..ee4efb311d 100644 --- a/selfdrive/car/ford/radar_interface.py +++ b/selfdrive/car/ford/radar_interface.py @@ -47,7 +47,7 @@ class RadarInterface(RadarInterfaceBase): self.updated_messages = set() self.track_id = 0 self.radar = DBC[CP.carFingerprint]['radar'] - if self.radar is None: + if self.radar is None or CP.radarUnavailable: self.rcp = None elif self.radar == RADAR.DELPHI_ESR: self.rcp = _create_delphi_esr_radar_can_parser() diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index 7b3140fbbf..50c7d93987 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,23 +1,21 @@ -from collections import defaultdict, namedtuple +from collections import defaultdict from dataclasses import dataclass from enum import Enum -from typing import Dict, List, Union +from typing import Dict, List, Set, Union from cereal import car -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu -TransmissionType = car.CarParams.TransmissionType -GearShifter = car.CarState.GearShifter - -AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) class CarControllerParams: # Messages: Lane_Assist_Data1, LateralMotionControl - LKAS_STEER_STEP = 5 + STEER_STEP = 5 + # Message: ACCDATA + ACC_CONTROL_STEP = 2 # Message: IPMA_Data LKAS_UI_STEP = 100 # Message: ACCDATA_3 @@ -25,12 +23,20 @@ class CarControllerParams: # Message: Steering_Data_FD1, but send twice as fast BUTTONS_STEP = 10 / 2 - LKAS_STEER_RATIO = 2.75 # Approximate ratio between LatCtlPath_An_Actl and steering angle in radians - # TODO: remove this once we understand how the EPS calculates the steering angle better - STEER_DRIVER_ALLOWANCE = 0.8 # Driver intervention threshold in Nm + CURVATURE_MAX = 0.02 # Max curvature for steering command, m^-1 + STEER_DRIVER_ALLOWANCE = 1.0 # Driver intervention threshold, Nm - RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) - RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) + # Curvature rate limits + # TODO: unify field names used by curvature and angle control cars + # ~2 m/s^3 up, ~-3 m/s^3 down + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.004, 0.00044, 0.00016]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[5, 15, 25], angle_v=[0.006, 0.00066, 0.00024]) + + ACCEL_MAX = 2.0 # m/s^s max acceleration + ACCEL_MIN = -3.5 # m/s^s max deceleration + + def __init__(self, CP): + pass class CANBUS: @@ -40,9 +46,14 @@ class CANBUS: class CAR: + BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN" ESCAPE_MK4 = "FORD ESCAPE 4TH GEN" EXPLORER_MK6 = "FORD EXPLORER 6TH GEN" FOCUS_MK4 = "FORD FOCUS 4TH GEN" + MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" + + +CANFD_CARS: Set[str] = set() class RADAR: @@ -60,12 +71,20 @@ class FordCarInfo(CarInfo): CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { + CAR.BRONCO_SPORT_MK1: FordCarInfo("Ford Bronco Sport 2021-22"), CAR.ESCAPE_MK4: [ - FordCarInfo("Ford Escape 2020"), - FordCarInfo("Ford Kuga EU", "Driver Assistance Pack"), + FordCarInfo("Ford Escape 2020-22"), + FordCarInfo("Ford Escape Plug-in Hybrid 2020-22"), + FordCarInfo("Ford Kuga 2020-21", "Driver Assistance Pack"), + FordCarInfo("Ford Kuga Plug-in Hybrid 2020-22", "Driver Assistance Pack"), + ], + CAR.EXPLORER_MK6: [ + FordCarInfo("Ford Explorer 2020-22"), + FordCarInfo("Lincoln Aviator 2021", "Co-Pilot360 Plus"), + FordCarInfo("Lincoln Aviator Plug-in Hybrid 2021", "Co-Pilot360 Plus"), ], - CAR.EXPLORER_MK6: FordCarInfo("Ford Explorer 2020-21"), CAR.FOCUS_MK4: FordCarInfo("Ford Focus EU 2019", "Driver Assistance Pack"), + CAR.MAVERICK_MK1: FordCarInfo("Ford Maverick 2022", "Co-Pilot360 Assist"), } FW_QUERY_CONFIG = FwQueryConfig( @@ -85,33 +104,71 @@ FW_QUERY_CONFIG = FwQueryConfig( ) FW_VERSIONS = { - CAR.ESCAPE_MK4: { + CAR.BRONCO_SPORT_MK1: { (Ecu.eps, 0x730, None): [ b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'LX6C-2D053-RD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-RE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'M1PT-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'M1PA-14C204-GF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'N1PA-14C204-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.shiftByWire, 0x732, None): [ + b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ1P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, + CAR.ESCAPE_MK4: { + (Ecu.eps, 0x730, None): [ + b'LX6C-14D003-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ b'LX6C-2D053-NS\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-NY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6C-2D053-SA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x706, None): [ b'LJ6T-14F397-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LJ6T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7E0, None): [ + b'LX6A-14C204-BJV\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LX6A-14C204-ESG\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MX6A-14C204-BEF\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NX6A-14C204-BLE\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.shiftByWire, 0x732, None): [ + b'LX6P-14G395-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LX6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PZ1P-14G395-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.EXPLORER_MK6: { (Ecu.eps, 0x730, None): [ b'L1MC-14D003-AK\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'M1MC-14D003-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x760, None): [ + b'L1MC-2D053-BA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MC-2D053-BF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MC-2D053-KB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x764, None): [ b'LB5T-14D049-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -119,14 +176,20 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x706, None): [ b'LB5T-14F397-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5T-14F397-AF\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'LC5T-14F397-AH\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7E0, None): [ + b'LB5A-14C204-BUJ\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'LB5A-14C204-EAC\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'MB5A-14C204-MD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'MB5A-14C204-RC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NB5A-14C204-HB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.shiftByWire, 0x732, None): [ + b'L1MP-14C561-AB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'L1MP-14G395-AE\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'L1MP-14G395-JB\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], }, CAR.FOCUS_MK4: { @@ -148,4 +211,26 @@ FW_VERSIONS = { (Ecu.shiftByWire, 0x732, None): [ ], }, + CAR.MAVERICK_MK1: { + (Ecu.eps, 0x730, None): [ + b'NZ6C-14D003-AL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x760, None): [ + b'NZ6C-2D053-AG\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x764, None): [ + b'NZ6T-14D049-AA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x706, None): [ + b'NZ6T-14F397-AC\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7E0, None): [ + b'NZ6A-14C204-AAA\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-PA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'NZ6A-14C204-ZA\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.shiftByWire, 0x732, None): [ + b'NZ6P-14G395-AD\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, } diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index c7e4d4eb30..7ae9bee404 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import capnp +import copy from dataclasses import dataclass, field import struct from typing import Dict, List, Optional, Tuple @@ -57,12 +58,26 @@ class Request: whitelist_ecus: List[int] = field(default_factory=list) rx_offset: int = 0x8 bus: int = 1 + # Whether this query should be run on the first auxiliary panda (CAN FD cars for example) + auxiliary: bool = False + # FW responses from these queries will not be used for fingerprinting + logging: bool = False + # boardd toggles OBD multiplexing on/off as needed + obd_multiplexing: bool = True @dataclass class FwQueryConfig: requests: List[Request] + # TODO: make this automatic and remove hardcoded lists, or do fingerprinting with ecus # Overrides and removes from essential ecus for specific models and ecus (exact matching) non_essential_ecus: Dict[capnp.lib.capnp._EnumModule, List[str]] = field(default_factory=dict) # Ecus added for data collection, not to be fingerprinted on extra_ecus: List[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]]] = field(default_factory=list) + + def __post_init__(self): + for i in range(len(self.requests)): + if self.requests[i].auxiliary: + new_request = copy.deepcopy(self.requests[i]) + new_request.bus += 4 + self.requests.append(new_request) diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index f4d92ab960..f85b7f6b7d 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 from collections import defaultdict -from typing import Any, Optional, Set, Tuple +from typing import Any, Dict, List, Set from tqdm import tqdm import panda.python.uds as uds from cereal import car -from selfdrive.car.ecu_addrs import get_ecu_addrs +from common.params import Params +from selfdrive.car.ecu_addrs import EcuAddrBusType, get_ecu_addrs from selfdrive.car.interfaces import get_interface_attr from selfdrive.car.fingerprints import FW_VERSIONS from selfdrive.car.isotp_parallel_query import IsoTpParallelQuery @@ -18,7 +19,7 @@ FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True) VERSIONS = get_interface_attr('FW_VERSIONS', ignore_none=True) MODEL_TO_BRAND = {c: b for b, e in VERSIONS.items() for c in e} -REQUESTS = [(brand, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests] +REQUESTS = [(brand, config, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests] def chunks(l, n=128): @@ -29,16 +30,17 @@ def chunks(l, n=128): def build_fw_dict(fw_versions, filter_brand=None): fw_versions_dict = defaultdict(set) for fw in fw_versions: - if filter_brand is None or fw.brand == filter_brand: - addr = fw.address + if (filter_brand is None or fw.brand == filter_brand) and not fw.logging: sub_addr = fw.subAddress if fw.subAddress != 0 else None - fw_versions_dict[(addr, sub_addr)].add(fw.fwVersion) + fw_versions_dict[(fw.address, sub_addr)].add(fw.fwVersion) return dict(fw_versions_dict) def get_brand_addrs(): brand_addrs = defaultdict(set) for brand, cars in VERSIONS.items(): + # Add ecus in database + extra ecus to match against + brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in FW_QUERY_CONFIGS[brand].extra_ecus} for fw in cars.values(): brand_addrs[brand] |= {(addr, sub_addr) for _, addr, sub_addr in fw.keys()} return brand_addrs @@ -90,7 +92,7 @@ def match_fw_to_car_fuzzy(fw_versions_dict, log=True, exclude=None): return set() -def match_fw_to_car_exact(fw_versions_dict): +def match_fw_to_car_exact(fw_versions_dict) -> Set[str]: """Do an exact FW match. Returns all cars that match the given FW versions for a list of "essential" ECUs. If an ECU is not considered essential the FW version can be missing to get a fingerprint, but if it's present it @@ -146,34 +148,43 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True): return True, set() -def get_present_ecus(logcan, sendcan): - queries = list() - parallel_queries = list() +def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: + params = Params() + # queries are split by OBD multiplexing mode + queries: Dict[bool, List[List[EcuAddrBusType]]] = {True: [], False: []} + parallel_queries: Dict[bool, List[EcuAddrBusType]] = {True: [], False: []} responses = set() - for brand, r in REQUESTS: + for brand, config, r in REQUESTS: + # Skip query if no panda available + if r.bus > num_pandas * 4 - 1: + continue + for brand_versions in VERSIONS[brand].values(): - for ecu_type, addr, sub_addr in brand_versions: + for ecu_type, addr, sub_addr in list(brand_versions) + config.extra_ecus: # Only query ecus in whitelist if whitelist is not empty if len(r.whitelist_ecus) == 0 or ecu_type in r.whitelist_ecus: a = (addr, sub_addr, r.bus) # Build set of queries if sub_addr is None: - if a not in parallel_queries: - parallel_queries.append(a) + if a not in parallel_queries[r.obd_multiplexing]: + parallel_queries[r.obd_multiplexing].append(a) else: # subaddresses must be queried one by one - if [a] not in queries: - queries.append([a]) + if [a] not in queries[r.obd_multiplexing]: + queries[r.obd_multiplexing].append([a]) # Build set of expected responses to filter response_addr = uds.get_rx_addr_for_tx_addr(addr, r.rx_offset) responses.add((response_addr, sub_addr, r.bus)) - queries.insert(0, parallel_queries) + for obd_multiplexing in queries: + queries[obd_multiplexing].insert(0, parallel_queries[obd_multiplexing]) - ecu_responses: Set[Tuple[int, Optional[int], int]] = set() - for query in queries: - ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) + ecu_responses = set() + for obd_multiplexing in queries: + set_obd_multiplexing(params, obd_multiplexing) + for query in queries[obd_multiplexing]: + ecu_responses.update(get_ecu_addrs(logcan, sendcan, set(query), responses, timeout=0.1)) return ecu_responses @@ -181,9 +192,9 @@ def get_brand_ecu_matches(ecu_rx_addrs): """Returns dictionary of brands and matches with ECUs in their FW versions""" brand_addrs = get_brand_addrs() - brand_matches = {brand: set() for brand, _ in REQUESTS} + brand_matches = {brand: set() for brand, _, _ in REQUESTS} - brand_rx_offsets = set((brand, r.rx_offset) for brand, r in REQUESTS) + brand_rx_offsets = set((brand, r.rx_offset) for brand, _, r in REQUESTS) for addr, sub_addr, _ in ecu_rx_addrs: # Since we can't know what request an ecu responded to, add matches for all possible rx offsets for brand, rx_offset in brand_rx_offsets: @@ -194,6 +205,15 @@ def get_brand_ecu_matches(ecu_rx_addrs): return brand_matches +def set_obd_multiplexing(params: Params, obd_multiplexing: bool): + if params.get_bool("ObdMultiplexingEnabled") != obd_multiplexing: + cloudlog.warning(f"Setting OBD multiplexing to {obd_multiplexing}") + params.remove("ObdMultiplexingChanged") + params.put_bool("ObdMultiplexingEnabled", obd_multiplexing) + params.get_bool("ObdMultiplexingChanged", block=True) + cloudlog.warning("OBD multiplexing set successfully") + + def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False): """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" @@ -213,6 +233,7 @@ def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pand def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, num_pandas=1, debug=False, progress=False): versions = VERSIONS.copy() + params = Params() # Each brand can define extra ECUs to query for data collection for brand, config in FW_QUERY_CONFIGS.items(): @@ -231,8 +252,8 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, ecu_types = {} for brand, brand_versions in versions.items(): - for c in brand_versions.values(): - for ecu_type, addr, sub_addr in c.keys(): + for ecu in brand_versions.values(): + for ecu_type, addr, sub_addr in ecu.keys(): a = (brand, addr, sub_addr) if a not in ecu_types: ecu_types[a] = ecu_type @@ -248,14 +269,18 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, # Get versions and build capnp list to put into CarParams car_fw = [] - requests = [(brand, r) for brand, r in REQUESTS if query_brand is None or brand == query_brand] + requests = [(brand, config, r) for brand, config, r in REQUESTS if query_brand is None or brand == query_brand] for addr in tqdm(addrs, disable=not progress): for addr_chunk in chunks(addr): - for brand, r in requests: + for brand, config, r in requests: # Skip query if no panda available if r.bus > num_pandas * 4 - 1: continue + # Toggle OBD multiplexing for each request + if r.bus % 4 == 1: + set_obd_multiplexing(params, r.obd_multiplexing) + try: addrs = [(a, s) for (b, a, s) in addr_chunk if b in (brand, 'any') and (len(r.whitelist_ecus) == 0 or ecu_types[(b, a, s)] in r.whitelist_ecus)] @@ -272,6 +297,8 @@ def get_fw_versions(logcan, sendcan, query_brand=None, extra=None, timeout=0.1, f.request = r.request f.brand = brand f.bus = r.bus + f.logging = r.logging or (f.ecu, tx_addr, sub_addr) in config.extra_ecus + f.obdMultiplexing = r.obd_multiplexing if sub_addr is not None: f.subAddress = sub_addr diff --git a/selfdrive/car/gm/carcontroller.py b/selfdrive/car/gm/carcontroller.py index 7c883dacc0..2a996c0ff6 100644 --- a/selfdrive/car/gm/carcontroller.py +++ b/selfdrive/car/gm/carcontroller.py @@ -3,12 +3,18 @@ from common.conversions import Conversions as CV from common.numpy_fast import interp from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_torque_limits +from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.gm import gmcan -from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons, EV_CAR +from selfdrive.car.gm.values import DBC, CanBus, CarControllerParams, CruiseButtons VisualAlert = car.CarControl.HUDControl.VisualAlert NetworkLocation = car.CarParams.NetworkLocation +LongCtrlState = car.CarControl.Actuators.LongControlState + +# Camera cancels up to 0.1s after brake is pressed, ECM allows 0.5s +CAMERA_CANCEL_DELAY_FRAMES = 10 +# Enforce a minimum interval between steering messages to avoid a fault +MIN_STEER_MSG_INTERVAL_MS = 15 class CarController: @@ -19,19 +25,20 @@ class CarController: self.apply_gas = 0 self.apply_brake = 0 self.frame = 0 + self.last_steer_frame = 0 self.last_button_frame = 0 + self.cancel_counter = 0 self.lka_steering_cmd_counter = 0 - self.sent_lka_steering_cmd = False self.lka_icon_status_last = (False, False) - self.params = CarControllerParams() + self.params = CarControllerParams(self.CP) self.packer_pt = CANPacker(DBC[self.CP.carFingerprint]['pt']) self.packer_obj = CANPacker(DBC[self.CP.carFingerprint]['radar']) self.packer_ch = CANPacker(DBC[self.CP.carFingerprint]['chassis']) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl hud_alert = hud_control.visualAlert @@ -42,23 +49,35 @@ class CarController: # Send CAN commands. can_sends = [] - # Steering (50Hz) - # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we just received the - # next Panda loopback confirmation in the current CS frame. - if CS.loopback_lka_steering_cmd_updated: - self.lka_steering_cmd_counter += 1 - self.sent_lka_steering_cmd = True - elif (self.frame % self.params.STEER_STEP) == 0: - # Initialize ASCMLKASteeringCmd counter using the camera - if not self.sent_lka_steering_cmd and self.CP.networkLocation == NetworkLocation.fwdCamera: - self.lka_steering_cmd_counter = CS.camera_lka_steering_cmd_counter + 1 + # Steering (Active: 50Hz, inactive: 10Hz) + steer_step = self.params.STEER_STEP if CC.latActive else self.params.INACTIVE_STEER_STEP + + if self.CP.networkLocation == NetworkLocation.fwdCamera: + # Also send at 50Hz: + # - on startup, first few msgs are blocked + # - until we're in sync with camera so counters align when relay closes, preventing a fault. + # openpilot can subtly drift, so this is activated throughout a drive to stay synced + out_of_sync = self.lka_steering_cmd_counter % 4 != (CS.cam_lka_steering_cmd_counter + 1) % 4 + if CS.loopback_lka_steering_cmd_ts_nanos == 0 or out_of_sync: + steer_step = self.params.STEER_STEP + + self.lka_steering_cmd_counter += 1 if CS.loopback_lka_steering_cmd_updated else 0 + + # Avoid GM EPS faults when transmitting messages too close together: skip this transmit if we + # received the ASCMLKASteeringCmd loopback confirmation too recently + last_lka_steer_msg_ms = (now_nanos - CS.loopback_lka_steering_cmd_ts_nanos) * 1e-6 + if (self.frame - self.last_steer_frame) >= steer_step and last_lka_steer_msg_ms > MIN_STEER_MSG_INTERVAL_MS: + # Initialize ASCMLKASteeringCmd counter using the camera until we get a msg on the bus + if CS.loopback_lka_steering_cmd_ts_nanos == 0: + self.lka_steering_cmd_counter = CS.pt_lka_steering_cmd_counter + 1 if CC.latActive: new_steer = int(round(actuators.steer * self.params.STEER_MAX)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) + apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) else: apply_steer = 0 + self.last_steer_frame = self.frame self.apply_steer_last = apply_steer idx = self.lka_steering_cmd_counter % 4 can_sends.append(gmcan.create_steering_control(self.packer_pt, CanBus.POWERTRAIN, apply_steer, idx, CC.latActive)) @@ -67,24 +86,27 @@ class CarController: # Gas/regen, brakes, and UI commands - all at 25Hz if self.frame % 4 == 0: if not CC.longActive: - # Stock ECU sends max regen when not enabled - self.apply_gas = self.params.MAX_ACC_REGEN + # ASCM sends max regen when not enabled + self.apply_gas = self.params.INACTIVE_REGEN self.apply_brake = 0 else: - if self.CP.carFingerprint in EV_CAR: - self.apply_gas = int(round(interp(actuators.accel, self.params.EV_GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) - self.apply_brake = int(round(interp(actuators.accel, self.params.EV_BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) - else: - self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) - self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) + self.apply_gas = int(round(interp(actuators.accel, self.params.GAS_LOOKUP_BP, self.params.GAS_LOOKUP_V))) + self.apply_brake = int(round(interp(actuators.accel, self.params.BRAKE_LOOKUP_BP, self.params.BRAKE_LOOKUP_V))) idx = (self.frame // 4) % 4 at_full_stop = CC.longActive and CS.out.standstill near_stop = CC.longActive and (CS.out.vEgo < self.params.NEAR_STOP_BRAKE_PHASE) + friction_brake_bus = CanBus.CHASSIS + # GM Camera exceptions + # TODO: can we always check the longControlState? + if self.CP.networkLocation == NetworkLocation.fwdCamera: + at_full_stop = at_full_stop and actuators.longControlState == LongCtrlState.stopping + friction_brake_bus = CanBus.POWERTRAIN + # GasRegenCmdActive needs to be 1 to avoid cruise faults. It describes the ACC state, not actuation can_sends.append(gmcan.create_gas_regen_command(self.packer_pt, CanBus.POWERTRAIN, self.apply_gas, idx, CC.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_friction_brake_command(self.packer_ch, friction_brake_bus, self.apply_brake, idx, CC.enabled, near_stop, at_full_stop, self.CP)) # Send dashboard UI commands (ACC status) send_fcw = hud_alert == VisualAlert.fcw @@ -93,7 +115,7 @@ class CarController: # Radar needs to know current speed and yaw rate (50hz), # and that ADAS is alive (10hz) - if not self.CP.radarOffCan: + if not self.CP.radarUnavailable: tt = self.frame * DT_CTRL time_and_headlights_step = 10 if self.frame % time_and_headlights_step == 0: @@ -111,12 +133,21 @@ class CarController: can_sends += gmcan.create_adas_keepalive(CanBus.POWERTRAIN) else: + # While car is braking, cancel button causes ECM to enter a soft disable state with a fault status. + # A delayed cancellation allows camera to cancel and avoids a fault when user depresses brake quickly + self.cancel_counter = self.cancel_counter + 1 if CC.cruiseControl.cancel else 0 + # Stock longitudinal, integrated at camera if (self.frame - self.last_button_frame) * DT_CTRL > 0.04: - if CC.cruiseControl.cancel: + if self.cancel_counter > CAMERA_CANCEL_DELAY_FRAMES: self.last_button_frame = self.frame can_sends.append(gmcan.create_buttons(self.packer_pt, CanBus.CAMERA, CS.buttons_counter, CruiseButtons.CANCEL)) + if self.CP.networkLocation == NetworkLocation.fwdCamera: + # Silence "Take Steering" alert sent by camera, forward PSCMStatus with HandsOffSWlDetectionStatus=1 + if self.frame % 10 == 0: + can_sends.append(gmcan.create_pscm_status(self.packer_pt, CanBus.CAMERA, CS.pscm_status)) + # Show green icon when LKA torque is applied, and # alarming orange icon when approaching torque limit. # If not sent again, LKA icon disappears in about 5 seconds. @@ -133,6 +164,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.params.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last new_actuators.gas = self.apply_gas new_actuators.brake = self.apply_brake diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index f531914877..2f9c952876 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -1,3 +1,4 @@ +import copy from cereal import car from common.conversions import Conversions as CV from common.numpy_fast import mean @@ -16,8 +17,13 @@ class CarState(CarStateBase): super().__init__(CP) can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["ECMPRDNL2"]["PRNDL2"] + self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. + self.cluster_min_speed = CV.KPH_TO_MS / 2. + self.loopback_lka_steering_cmd_updated = False - self.camera_lka_steering_cmd_counter = 0 + self.loopback_lka_steering_cmd_ts_nanos = 0 + self.pt_lka_steering_cmd_counter = 0 + self.cam_lka_steering_cmd_counter = 0 self.buttons_counter = 0 def update(self, pt_cp, cam_cp, loopback_cp): @@ -26,11 +32,16 @@ class CarState(CarStateBase): self.prev_cruise_buttons = self.cruise_buttons self.cruise_buttons = pt_cp.vl["ASCMSteeringButton"]["ACCButtons"] self.buttons_counter = pt_cp.vl["ASCMSteeringButton"]["RollingCounter"] + self.pscm_status = copy.copy(pt_cp.vl["PSCMStatus"]) + self.moving_backward = pt_cp.vl["EBCMWheelSpdRear"]["MovingBackward"] != 0 # Variables used for avoiding LKAS faults - self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]) > 0 + self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 + if self.loopback_lka_steering_cmd_updated: + self.loopback_lka_steering_cmd_ts_nanos = loopback_cp.ts_nanos["ASCMLKASteeringCmd"]["RollingCounter"] if self.CP.networkLocation == NetworkLocation.fwdCamera: - self.camera_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + self.pt_lka_steering_cmd_counter = pt_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] + self.cam_lka_steering_cmd_counter = cam_cp.vl["ASCMLKASteeringCmd"]["RollingCounter"] ret.wheelSpeeds = self.get_wheel_speeds( pt_cp.vl["EBCMWheelSpdFront"]["FLWheelSpd"], @@ -48,21 +59,19 @@ class CarState(CarStateBase): else: ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(pt_cp.vl["ECMPRDNL2"]["PRNDL2"], None)) - # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe - # that the brake is being intermittently pressed without user interaction. - # To avoid a cruise fault we need to match the ECM's brake pressed signal and threshold - # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf ret.brake = pt_cp.vl["ECMAcceleratorPos"]["BrakePedalPos"] - if self.CP.networkLocation != NetworkLocation.fwdCamera: - ret.brakePressed = ret.brake >= 8 + if self.CP.networkLocation == NetworkLocation.fwdCamera: + ret.brakePressed = pt_cp.vl["ECMEngineStatus"]["BrakePressed"] != 0 else: - # While car is braking, cancel button causes ECM to enter a soft disable state with a fault status. - # Match ECM threshold at a standstill to allow the camera to cancel earlier - ret.brakePressed = ret.brake >= 20 + # Some Volt 2016-17 have loose brake pedal push rod retainers which causes the ECM to believe + # that the brake is being intermittently pressed without user interaction. + # To avoid a cruise fault we need to use a conservative brake position threshold + # https://static.nhtsa.gov/odi/tsbs/2017/MC-10137629-9999.pdf + ret.brakePressed = ret.brake >= 8 # Regen braking is braking if self.CP.transmissionType == TransmissionType.direct: - ret.brakePressed = ret.brakePressed or pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0 + ret.regenBraking = pt_cp.vl["EBCMRegenPaddle"]["RegenPaddle"] != 0 ret.gas = pt_cp.vl["AcceleratorPedal2"]["AcceleratorPedal2"] / 254. ret.gasPressed = ret.gas > 1e-5 @@ -92,13 +101,17 @@ class CarState(CarStateBase): ret.parkingBrake = pt_cp.vl["VehicleIgnitionAlt"]["ParkBrake"] == 1 ret.cruiseState.available = pt_cp.vl["ECMEngineStatus"]["CruiseMainOn"] != 0 ret.espDisabled = pt_cp.vl["ESPStatus"]["TractionControlOn"] != 1 - ret.accFaulted = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED + ret.accFaulted = (pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.FAULTED or + pt_cp.vl["EBCMFrictionBrakeStatus"]["FrictionBrakeUnavailable"] == 1) ret.cruiseState.enabled = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] != AccState.OFF ret.cruiseState.standstill = pt_cp.vl["AcceleratorPedal2"]["CruiseState"] == AccState.STANDSTILL if self.CP.networkLocation == NetworkLocation.fwdCamera: ret.cruiseState.speed = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCSpeedSetpoint"] * CV.KPH_TO_MS ret.stockAeb = cam_cp.vl["AEBCmd"]["AEBCmdActive"] != 0 + # openpilot controls nonAdaptive when not pcmCruise + if self.CP.pcmCruise: + ret.cruiseState.nonAdaptive = cam_cp.vl["ASCMActiveCruiseControlStatus"]["ACCCruiseState"] not in (2, 3) return ret @@ -111,6 +124,7 @@ class CarState(CarStateBase): ("AEBCmdActive", "AEBCmd"), ("RollingCounter", "ASCMLKASteeringCmd"), ("ACCSpeedSetpoint", "ASCMActiveCruiseControlStatus"), + ("ACCCruiseState", "ASCMActiveCruiseControlStatus"), ] checks += [ ("AEBCmd", 10), @@ -142,14 +156,22 @@ class CarState(CarStateBase): ("FRWheelSpd", "EBCMWheelSpdFront"), ("RLWheelSpd", "EBCMWheelSpdRear"), ("RRWheelSpd", "EBCMWheelSpdRear"), + ("MovingBackward", "EBCMWheelSpdRear"), + ("FrictionBrakeUnavailable", "EBCMFrictionBrakeStatus"), ("PRNDL2", "ECMPRDNL2"), ("ManualMode", "ECMPRDNL2"), ("LKADriverAppldTrq", "PSCMStatus"), ("LKATorqueDelivered", "PSCMStatus"), ("LKATorqueDeliveredStatus", "PSCMStatus"), + ("HandsOffSWlDetectionStatus", "PSCMStatus"), + ("HandsOffSWDetectionMode", "PSCMStatus"), + ("LKATotalTorqueDelivered", "PSCMStatus"), + ("PSCMStatusChecksum", "PSCMStatus"), + ("RollingCounter", "PSCMStatus"), ("TractionControlOn", "ESPStatus"), ("ParkBrake", "VehicleIgnitionAlt"), ("CruiseMainOn", "ECMEngineStatus"), + ("BrakePressed", "ECMEngineStatus"), ] checks = [ @@ -161,6 +183,7 @@ class CarState(CarStateBase): ("VehicleIgnitionAlt", 10), ("EBCMWheelSpdFront", 20), ("EBCMWheelSpdRear", 20), + ("EBCMFrictionBrakeStatus", 20), ("AcceleratorPedal2", 33), ("ASCMSteeringButton", 33), ("ECMEngineStatus", 100), @@ -168,6 +191,15 @@ class CarState(CarStateBase): ("ECMAcceleratorPos", 80), ] + # Used to read back last counter sent to PT by camera + if CP.networkLocation == NetworkLocation.fwdCamera: + signals += [ + ("RollingCounter", "ASCMLKASteeringCmd"), + ] + checks += [ + ("ASCMLKASteeringCmd", 0), + ] + if CP.transmissionType == TransmissionType.direct: signals.append(("RegenPaddle", "EBCMRegenPaddle")) checks.append(("EBCMRegenPaddle", 50)) diff --git a/selfdrive/car/gm/gmcan.py b/selfdrive/car/gm/gmcan.py index 20e4c4ab6e..63189bcd89 100644 --- a/selfdrive/car/gm/gmcan.py +++ b/selfdrive/car/gm/gmcan.py @@ -1,14 +1,32 @@ from selfdrive.car import make_can_msg +from selfdrive.car.gm.values import CAR + def create_buttons(packer, bus, idx, button): values = { "ACCButtons": button, "RollingCounter": idx, + "ACCAlwaysOne": 1, + "DistanceButton": 0, } + + checksum = 240 + int(values["ACCAlwaysOne"] * 0xf) + checksum += values["RollingCounter"] * (0x4ef if values["ACCAlwaysOne"] != 0 else 0x3f0) + checksum -= int(values["ACCButtons"] - 1) << 4 # not correct if value is 0 + checksum -= 2 * values["DistanceButton"] + + values["SteeringButtonChecksum"] = checksum return packer.make_can_msg("ASCMSteeringButton", bus, values) -def create_steering_control(packer, bus, apply_steer, idx, lkas_active): +def create_pscm_status(packer, bus, pscm_status): + checksum_mod = int(1 - pscm_status["HandsOffSWlDetectionStatus"]) << 5 + pscm_status["HandsOffSWlDetectionStatus"] = 1 + pscm_status["PSCMStatusChecksum"] += checksum_mod + return packer.make_can_msg("PSCMStatus", bus, pscm_status) + + +def create_steering_control(packer, bus, apply_steer, idx, lkas_active): values = { "LKASteeringCmdActive": lkas_active, "LKASteeringCmd": apply_steer, @@ -18,15 +36,17 @@ def create_steering_control(packer, bus, apply_steer, idx, lkas_active): return packer.make_can_msg("ASCMLKASteeringCmd", bus, values) + def create_adas_keepalive(bus): dat = b"\x00\x00\x00\x00\x00\x00\x00" return [make_can_msg(0x409, dat, bus), make_can_msg(0x40a, dat, bus)] -def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_stop): + +def create_gas_regen_command(packer, bus, throttle, idx, enabled, at_full_stop): values = { - "GasRegenCmdActive": acc_engaged, + "GasRegenCmdActive": enabled, "RollingCounter": idx, - "GasRegenCmdActiveInv": 1 - acc_engaged, + "GasRegenCmdActiveInv": 1 - enabled, "GasRegenCmd": throttle, "GasRegenFullStopActive": at_full_stop, "GasRegenAlwaysOne": 1, @@ -41,15 +61,21 @@ def create_gas_regen_command(packer, bus, throttle, idx, acc_engaged, at_full_st return packer.make_can_msg("ASCMGasRegenCmd", bus, values) -def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_full_stop): + +def create_friction_brake_command(packer, bus, apply_brake, idx, enabled, near_stop, at_full_stop, CP): mode = 0x1 + + # TODO: Understand this better. Volts and ICE Camera ACC cars are 0x1 when enabled with no brake + if enabled and CP.carFingerprint in (CAR.BOLT_EUV,): + mode = 0x9 + if apply_brake > 0: mode = 0xa if at_full_stop: mode = 0xd # TODO: this is to have GM bringing the car to complete stop, - # but currently it conflicts with OP controls, so turned off. + # but currently it conflicts with OP controls, so turned off. Not set by all cars #elif near_stop: # mode = 0xb @@ -57,44 +83,48 @@ def create_friction_brake_command(packer, bus, apply_brake, idx, near_stop, at_f checksum = (0x10000 - (mode << 12) - brake - idx) & 0xffff values = { - "RollingCounter" : idx, - "FrictionBrakeMode" : mode, + "RollingCounter": idx, + "FrictionBrakeMode": mode, "FrictionBrakeChecksum": checksum, - "FrictionBrakeCmd" : -apply_brake + "FrictionBrakeCmd": -apply_brake } return packer.make_can_msg("EBCMFrictionBrakeCmd", bus, values) -def create_acc_dashboard_command(packer, bus, acc_engaged, target_speed_kph, lead_car_in_sight, fcw): + +def create_acc_dashboard_command(packer, bus, enabled, target_speed_kph, lead_car_in_sight, fcw): target_speed = min(target_speed_kph, 255) values = { - "ACCAlwaysOne" : 1, - "ACCResumeButton" : 0, - "ACCSpeedSetpoint" : target_speed, - "ACCGapLevel" : 3 * acc_engaged, # 3 "far", 0 "inactive" - "ACCCmdActive" : acc_engaged, - "ACCAlwaysOne2" : 1, - "ACCLeadCar" : lead_car_in_sight, + "ACCAlwaysOne": 1, + "ACCResumeButton": 0, + "ACCSpeedSetpoint": target_speed, + "ACCGapLevel": 3 * enabled, # 3 "far", 0 "inactive" + "ACCCmdActive": enabled, + "ACCAlwaysOne2": 1, + "ACCLeadCar": lead_car_in_sight, "FCWAlert": 0x3 if fcw else 0 } return packer.make_can_msg("ASCMActiveCruiseControlStatus", bus, values) + def create_adas_time_status(bus, tt, idx): dat = [(tt >> 20) & 0xff, (tt >> 12) & 0xff, (tt >> 4) & 0xff, - ((tt & 0xf) << 4) + (idx << 2)] + ((tt & 0xf) << 4) + (idx << 2)] chksum = 0x1000 - dat[0] - dat[1] - dat[2] - dat[3] chksum = chksum & 0xfff dat += [0x40 + (chksum >> 8), chksum & 0xff, 0x12] return make_can_msg(0xa1, bytes(dat), bus) + def create_adas_steering_status(bus, idx): dat = [idx << 6, 0xf0, 0x20, 0, 0, 0] chksum = 0x60 + sum(dat) dat += [chksum >> 8, chksum & 0xff] return make_can_msg(0x306, bytes(dat), bus) + def create_adas_accelerometer_speed_status(bus, speed_ms, idx): spd = int(speed_ms * 16) & 0xfff accel = 0 & 0xfff @@ -108,6 +138,7 @@ def create_adas_accelerometer_speed_status(bus, speed_ms, idx): dat += [(idx << 5) + (far_range_mode << 4) + (near_range_mode << 3) + (chksum >> 8), chksum & 0xff] return make_can_msg(0x308, bytes(dat), bus) + def create_adas_headlights_status(packer, bus): values = { "Always42": 0x42, @@ -115,6 +146,7 @@ def create_adas_headlights_status(packer, bus): } return packer.make_can_msg("ASCMHeadlight", bus, values) + def create_lka_icon_command(bus, active, critical, steer): if active and steer == 1: if critical: diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index e25f203772..f501c8c7dd 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -4,8 +4,9 @@ from math import fabs from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config -from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config +from selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG +from selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -20,8 +21,7 @@ BUTTONS_DICT = {CruiseButtons.RES_ACCEL: ButtonType.accelCruise, CruiseButtons.D class CarInterface(CarInterfaceBase): @staticmethod def get_pid_accel_limits(CP, current_speed, cruise_speed): - params = CarControllerParams() - return params.ACCEL_MIN, params.ACCEL_MAX + return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX # Determined by iteratively plotting and minimizing error for f(angle, speed) = steer. @staticmethod @@ -45,53 +45,74 @@ class CarInterface(CarInterfaceBase): return CarInterfaceBase.get_steer_feedforward_default @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "gm" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.gm)] ret.autoResumeSng = False + use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs if candidate in EV_CAR: ret.transmissionType = TransmissionType.direct else: ret.transmissionType = TransmissionType.automatic + ret.longitudinalTuning.deadzoneBP = [0.] + ret.longitudinalTuning.deadzoneV = [0.15] + + ret.longitudinalTuning.kpBP = [5., 35.] + ret.longitudinalTuning.kiBP = [0.] + if candidate in CAMERA_ACC_CAR: - ret.openpilotLongitudinalControl = False + ret.experimentalLongitudinalAvailable = True ret.networkLocation = NetworkLocation.fwdCamera - ret.radarOffCan = True # no radar + ret.radarUnavailable = True # no radar ret.pcmCruise = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM ret.minEnableSpeed = 5 * CV.KPH_TO_MS + ret.minSteerSpeed = 10 * CV.KPH_TO_MS + + # Tuning for experimental long + ret.longitudinalTuning.kpV = [2.0, 1.5] + ret.longitudinalTuning.kiV = [0.72] + ret.stopAccel = -2.0 + ret.stoppingDecelRate = 2.0 # reach brake quickly after enabling + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 + + if experimental_long: + ret.pcmCruise = False + ret.openpilotLongitudinalControl = True + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_GM_HW_CAM_LONG + else: # ASCM, OBD-II harness ret.openpilotLongitudinalControl = True ret.networkLocation = NetworkLocation.gateway - ret.radarOffCan = False + ret.radarUnavailable = RADAR_HEADER_MSG not in fingerprint[CanBus.OBSTACLE] and not use_off_car_defaults ret.pcmCruise = False # stock non-adaptive cruise control is kept off # supports stop and go, but initial engage must (conservatively) be above 18mph ret.minEnableSpeed = 18 * CV.MPH_TO_MS + ret.minSteerSpeed = 7 * CV.MPH_TO_MS + + # Tuning + ret.longitudinalTuning.kpV = [2.4, 1.5] + ret.longitudinalTuning.kiV = [0.36] # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX, CAR.BOLT_EV} + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL, CAR.EQUINOX} or \ + (ret.networkLocation == NetworkLocation.gateway and ret.radarUnavailable) # Start with a baseline tuning for all GM vehicles. Override tuning as needed in each model section below. - # Some GMs need some tolerance above 10 kph to avoid a fault - ret.minSteerSpeed = 10.1 * CV.KPH_TO_MS ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.00]] ret.lateralTuning.pid.kf = 0.00004 # full torque for 20 deg at 80mph means 0.00007818594 ret.steerActuatorDelay = 0.1 # Default delay, not measured yet tire_stiffness_factor = 0.444 # not optimized yet - ret.longitudinalTuning.kpBP = [5., 35.] - ret.longitudinalTuning.kpV = [2.4, 1.5] - ret.longitudinalTuning.kiBP = [0.] - ret.longitudinalTuning.kiV = [0.36] - ret.steerLimitTimer = 0.4 ret.radarTimeStep = 0.0667 # GM radar runs at 15Hz instead of standard 20Hz + ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking if candidate == CAR.VOLT: ret.mass = 1607. + STD_CARGO_KG @@ -127,7 +148,13 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.4 # end to end is 13.46 ret.centerToFront = ret.wheelbase * 0.4 ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_acadia() - ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking + + elif candidate == CAR.BUICK_LACROSSE: + ret.mass = 1712. + STD_CARGO_KG + ret.wheelbase = 2.91 + ret.steerRatio = 15.8 + ret.centerToFront = ret.wheelbase * 0.4 # wild guess + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.BUICK_REGAL: ret.mass = 3779. * CV.LB_TO_KG + STD_CARGO_KG # (3849+3708)/2 @@ -141,6 +168,14 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 15.3 ret.centerToFront = ret.wheelbase * 0.5 + elif candidate == CAR.ESCALADE: + ret.minEnableSpeed = -1. # engage speed is decided by pcm + ret.mass = 5653. * CV.LB_TO_KG + STD_CARGO_KG # (5552+5815)/2 + ret.wheelbase = 2.95 # 116 inches in meters + ret.steerRatio = 17.3 + ret.centerToFront = ret.wheelbase * 0.5 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.ESCALADE_ESV: ret.minEnableSpeed = -1. # engage speed is decided by pcm ret.mass = 2739. + STD_CARGO_KG @@ -152,11 +187,11 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 0.000045 tire_stiffness_factor = 1.0 - elif candidate in (CAR.BOLT_EV, CAR.BOLT_EUV): + elif candidate == CAR.BOLT_EUV: ret.mass = 1669. + STD_CARGO_KG ret.wheelbase = 2.63779 ret.steerRatio = 16.8 - ret.centerToFront = 2.15 # measured + ret.centerToFront = ret.wheelbase * 0.4 tire_stiffness_factor = 1.0 ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) @@ -176,10 +211,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -192,22 +223,26 @@ class CarInterface(CarInterfaceBase): ret = self.CS.update(self.cp, self.cp_cam, self.cp_loopback) if self.CS.cruise_buttons != self.CS.prev_cruise_buttons and self.CS.prev_cruise_buttons != CruiseButtons.INIT: - be = create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS) + buttonEvents = [create_button_event(self.CS.cruise_buttons, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)] + # Handle ACCButtons changing buttons mid-press + if self.CS.cruise_buttons != CruiseButtons.UNPRESS and self.CS.prev_cruise_buttons != CruiseButtons.UNPRESS: + buttonEvents.append(create_button_event(CruiseButtons.UNPRESS, self.CS.prev_cruise_buttons, BUTTONS_DICT, CruiseButtons.UNPRESS)) - # Suppress resume button if we're resuming from stop so we don't adjust speed. - if be.type == ButtonType.accelCruise and (ret.cruiseState.enabled and ret.standstill): - be.type = ButtonType.unknown - - ret.buttonEvents = [be] + ret.buttonEvents = buttonEvents + # The ECM allows enabling on falling edge of set, but only rising edge of resume events = self.create_common_events(ret, extra_gears=[GearShifter.sport, GearShifter.low, GearShifter.eco, GearShifter.manumatic], - pcm_enable=self.CP.pcmCruise) + pcm_enable=self.CP.pcmCruise, enable_buttons=(ButtonType.decelCruise,)) + if not self.CP.pcmCruise: + if any(b.type == ButtonType.accelCruise and b.pressed for b in ret.buttonEvents): + events.add(EventName.buttonEnable) # Enabling at a standstill with brake is allowed # TODO: verify 17 Volt can enable for the first time at a stop and allow for all GMs - if ret.vEgo < self.CP.minEnableSpeed and not (ret.standstill and ret.brake >= 20 and - self.CP.networkLocation == NetworkLocation.fwdCamera): + below_min_enable_speed = ret.vEgo < self.CP.minEnableSpeed or self.CS.moving_backward + if below_min_enable_speed and not (ret.standstill and ret.brake >= 20 and + self.CP.networkLocation == NetworkLocation.fwdCamera): events.add(EventName.belowEngageSpeed) if ret.cruiseState.standstill: events.add(EventName.resumeRequired) @@ -218,5 +253,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/gm/radar_interface.py b/selfdrive/car/gm/radar_interface.py index 6904e6f899..b86a85f915 100755 --- a/selfdrive/car/gm/radar_interface.py +++ b/selfdrive/car/gm/radar_interface.py @@ -3,7 +3,7 @@ import math from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser -from selfdrive.car.gm.values import DBC, CAR, CanBus +from selfdrive.car.gm.values import DBC, CanBus from selfdrive.car.interfaces import RadarInterfaceBase RADAR_HEADER_MSG = 1120 @@ -16,9 +16,6 @@ LAST_RADAR_MSG = RADAR_HEADER_MSG + NUM_SLOTS def create_radar_can_parser(car_fingerprint): - if car_fingerprint not in (CAR.VOLT, CAR.MALIBU, CAR.HOLDEN_ASTRA, CAR.ACADIA, CAR.CADILLAC_ATS, CAR.ESCALADE_ESV): - return None - # C1A-ARS3-A by Continental radar_targets = list(range(SLOT_1_MSG, SLOT_1_MSG + NUM_SLOTS)) signals = list(zip(['FLRRNumValidTargets', @@ -34,11 +31,12 @@ def create_radar_can_parser(car_fingerprint): return CANParser(DBC[car_fingerprint]['radar'], signals, checks, CanBus.OBSTACLE) + class RadarInterface(RadarInterfaceBase): def __init__(self, CP): super().__init__(CP) - self.rcp = create_radar_can_parser(CP.carFingerprint) + self.rcp = None if CP.radarUnavailable else create_radar_can_parser(CP.carFingerprint) self.trigger_msg = LAST_RADAR_MSG self.updated_messages = set() diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 999dabfee3..0d13cb9827 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,5 +1,5 @@ from collections import defaultdict -from dataclasses import dataclass, field +from dataclasses import dataclass from enum import Enum from typing import Dict, List, Union @@ -11,9 +11,10 @@ Ecu = car.CarParams.Ecu class CarControllerParams: STEER_MAX = 300 # GM limit is 3Nm. Used by carcontroller to generate LKA output - STEER_STEP = 2 # Control frames per command (50hz) - STEER_DELTA_UP = 7 # Delta rates require review due to observed EPS weakness - STEER_DELTA_DOWN = 17 + STEER_STEP = 3 # Active control frames per command (~33hz) + INACTIVE_STEER_STEP = 10 # Inactive control frames per command (10hz) + STEER_DELTA_UP = 10 # Delta rates require review due to observed EPS weakness + STEER_DELTA_DOWN = 25 STEER_DRIVER_ALLOWANCE = 50 STEER_DRIVER_MULTIPLIER = 4 STEER_DRIVER_FACTOR = 100 @@ -23,14 +24,6 @@ class CarControllerParams: ADAS_KEEPALIVE_STEP = 100 CAMERA_KEEPALIVE_STEP = 100 - # Volt gas/brake lookups - # TODO: These values should be confirmed on non-Volt vehicles. - # MAX_GAS should achieve 2 m/s^2 and MAX_BRAKE with regen should achieve -4.0 m/s^2 - MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. - ZERO_GAS = 2048 # Coasting - MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen - MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen - # Allow small margin below -3.5 m/s^2 from ISO 15622:2018 since we # perform the closed loop control, and might need some # to apply some more braking if we're on a downhill slope. @@ -39,15 +32,32 @@ class CarControllerParams: ACCEL_MAX = 2. # m/s^2 ACCEL_MIN = -4. # m/s^2 - # ICE has much less engine braking force compared to regen in EVs, - # lower threshold removes some braking deadzone - GAS_LOOKUP_BP = [-0.1, 0., ACCEL_MAX] - EV_GAS_LOOKUP_BP = [-1., 0., ACCEL_MAX] - GAS_LOOKUP_V = [MAX_ACC_REGEN, ZERO_GAS, MAX_GAS] + def __init__(self, CP): + # Gas/brake lookups + self.ZERO_GAS = 2048 # Coasting + self.MAX_BRAKE = 400 # ~ -4.0 m/s^2 with regen - BRAKE_LOOKUP_BP = [ACCEL_MIN, -0.1] - EV_BRAKE_LOOKUP_BP = [ACCEL_MIN, -1.] - BRAKE_LOOKUP_V = [MAX_BRAKE, 0.] + if CP.carFingerprint in CAMERA_ACC_CAR: + self.MAX_GAS = 3400 + self.MAX_ACC_REGEN = 1514 + self.INACTIVE_REGEN = 1554 + # Camera ACC vehicles have no regen while enabled. + # Camera transitions to MAX_ACC_REGEN from ZERO_GAS and uses friction brakes instantly + max_regen_acceleration = 0. + + else: + self.MAX_GAS = 3072 # Safety limit, not ACC max. Stock ACC >4096 from standstill. + self.MAX_ACC_REGEN = 1404 # Max ACC regen is slightly less than max paddle regen + self.INACTIVE_REGEN = 1404 + # ICE has much less engine braking force compared to regen in EVs, + # lower threshold removes some braking deadzone + max_regen_acceleration = -1. if CP.carFingerprint in EV_CAR else -0.1 + + self.GAS_LOOKUP_BP = [max_regen_acceleration, 0., self.ACCEL_MAX] + self.GAS_LOOKUP_V = [self.MAX_ACC_REGEN, self.ZERO_GAS, self.MAX_GAS] + + self.BRAKE_LOOKUP_BP = [self.ACCEL_MIN, max_regen_acceleration] + self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] class CAR: @@ -56,9 +66,10 @@ class CAR: CADILLAC_ATS = "CADILLAC ATS Premium Performance 2018" MALIBU = "CHEVROLET MALIBU PREMIER 2017" ACADIA = "GMC ACADIA DENALI 2018" + BUICK_LACROSSE = "BUICK LACROSSE 2017" BUICK_REGAL = "BUICK REGAL ESSENCE 2018" + ESCALADE = "CADILLAC ESCALADE 2017" ESCALADE_ESV = "CADILLAC ESCALADE ESV 2016" - BOLT_EV = "CHEVROLET BOLT EV 2022" BOLT_EUV = "CHEVROLET BOLT EUV 2022" SILVERADO = "CHEVROLET SILVERADO 1500 2020" EQUINOX = "CHEVROLET EQUINOX 2019" @@ -66,7 +77,7 @@ class CAR: class Footnote(Enum): OBD_II = CarFootnote( - 'Requires a community built ASCM harness. ' + + 'Requires a community built ASCM harness. ' + 'NOTE: disconnecting the ASCM disables Automatic Emergency Braking (AEB).', Column.MODEL) @@ -74,25 +85,34 @@ class Footnote(Enum): @dataclass class GMCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC)" - harness: Enum = Harness.obd_ii - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.OBD_II]) + + def init_make(self, CP: car.CarParams): + if CP.networkLocation == car.CarParams.NetworkLocation.fwdCamera: + self.harness = Harness.gm + else: + self.harness = Harness.obd_ii + self.footnotes.append(Footnote.OBD_II) CAR_INFO: Dict[str, Union[GMCarInfo, List[GMCarInfo]]] = { CAR.HOLDEN_ASTRA: GMCarInfo("Holden Astra 2017"), - CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0), + CAR.VOLT: GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"), CAR.CADILLAC_ATS: GMCarInfo("Cadillac ATS Premium Performance 2018"), CAR.MALIBU: GMCarInfo("Chevrolet Malibu Premier 2017"), CAR.ACADIA: GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), + CAR.BUICK_LACROSSE: GMCarInfo("Buick LaCrosse 2017-19", "Driver Confidence Package 2"), CAR.BUICK_REGAL: GMCarInfo("Buick Regal Essence 2018"), + CAR.ESCALADE: GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"), CAR.ESCALADE_ESV: GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EV: GMCarInfo("Chevrolet Bolt EV 2022-23", footnotes=[], harness=Harness.gm), - CAR.BOLT_EUV: GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210", footnotes=[], harness=Harness.gm), - CAR.SILVERADO: [ - GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II", footnotes=[], harness=Harness.gm), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", footnotes=[], harness=Harness.gm), + CAR.BOLT_EUV: [ + GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", "https://youtu.be/xvwzGMUA210"), + GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), ], - CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22", footnotes=[], harness=Harness.gm), + CAR.SILVERADO: [ + GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", "https://youtu.be/5HbNoBLzRwE"), + ], + CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"), } @@ -133,6 +153,15 @@ FINGERPRINTS = { # Volt Premier w/ ACC 2018 { 170: 8, 171: 8, 189: 7, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 288: 5, 298: 8, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 479: 3, 481: 7, 485: 8, 489: 8, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 528: 4, 532: 6, 546: 7, 550: 8, 554: 3, 558: 8, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 577: 8, 578: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 715: 8, 717: 5, 761: 7, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1516: 8, 1601: 8, 1618: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7, 1930: 7, 2016: 8, 2018: 8, 2020: 8, 2024: 8, 2028: 8 + }, + # Volt Premier 2018 w/ flashed firmware, no radar + { + 170: 8, 171: 8, 189: 7, 190: 6, 192: 5, 193: 8, 197: 8, 199: 4, 201: 6, 209: 7, 211: 2, 241: 6, 288: 5, 289: 1, 290: 1, 298: 2, 304: 1, 308: 4, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 368: 8, 381: 2, 384: 8, 386: 5, 388: 8, 389: 2, 390: 7, 417: 7, 419: 1, 426: 7, 451: 8, 452: 8, 453: 6, 454: 8, 456: 8, 458: 8, 479: 3, 481: 7, 485: 8, 489: 5, 493: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 3, 508: 8, 512: 3, 528: 4, 530: 8, 532: 6, 537: 5, 539: 8, 542: 7, 546: 7, 550: 8, 554: 3, 558: 8, 560: 6, 562: 4, 563: 5, 564: 5, 565: 5, 566: 5, 567: 3, 568: 1, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 3, 707: 8, 711: 6, 761: 7, 810: 8, 821: 4, 823: 7, 832: 8, 840: 5, 842: 5, 844: 8, 853: 8, 866: 4, 961: 8, 967: 4, 969: 8, 977: 8, 979: 7, 988: 6, 989: 8, 995: 7, 1001: 5, 1003: 5, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1187: 4, 1217: 8, 1221: 5, 1223: 3, 1225: 7, 1227: 4, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1273: 3, 1275: 3, 1280: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1905: 7, 1906: 7, 1907: 7, 1910: 7, 1912: 7, 1922: 7, 1927: 7 + }], + CAR.BUICK_LACROSSE: [ + # LaCrosse Premium AWD 2017 + { + 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 353: 3, 381: 6, 386: 8, 388: 8, 393: 7, 398: 8, 407: 7, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 455: 7, 456: 8, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 495: 4, 497: 8, 499: 3, 500: 6, 501: 8, 503: 1, 508: 8, 510: 8, 528: 5, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 565: 5, 567: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 5, 707: 8, 753: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 872: 1, 882: 8, 890: 1, 892: 2, 893: 1, 894: 1, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1011: 6, 1013: 3, 1017: 8, 1019: 2, 1020: 8, 1022: 1, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1243: 3, 1249: 8, 1257: 6, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1280: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1904: 7, 1906: 7, 1907: 7, 1912: 7, 1913: 7, 1914: 7, 1916: 7, 1918: 7, 1919: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 }], CAR.BUICK_REGAL : [ # Regal TourX Essence w/ ACC 2018 @@ -158,6 +187,10 @@ FINGERPRINTS = { { 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 313: 8, 320: 3, 322: 7, 328: 1, 338: 6, 340: 6, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 393: 8, 398: 8, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 567: 5, 573: 1, 577: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1225: 8, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1328: 4, 1417: 8, 1601: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1919: 7, 1920: 7, 1930: 7, 2016: 8, 2024: 8 }], + CAR.ESCALADE: [ + { + 170: 8, 190: 6, 193: 8, 197: 8, 199: 4, 201: 8, 208: 8, 209: 7, 211: 2, 241: 6, 249: 8, 288: 5, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 322: 7, 328: 1, 352: 5, 381: 6, 384: 4, 386: 8, 388: 8, 393: 7, 398: 8, 407: 4, 413: 8, 417: 7, 419: 1, 422: 4, 426: 7, 431: 8, 442: 8, 451: 8, 452: 8, 453: 6, 454: 8, 455: 7, 460: 5, 462: 4, 463: 3, 479: 3, 481: 7, 485: 8, 487: 8, 489: 8, 497: 8, 499: 3, 500: 6, 501: 8, 508: 8, 510: 8, 532: 6, 534: 2, 554: 3, 560: 8, 562: 8, 563: 5, 564: 5, 573: 1, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 647: 6, 707: 8, 715: 8, 717: 5, 719: 5, 761: 7, 801: 8, 804: 3, 810: 8, 840: 5, 842: 5, 844: 8, 866: 4, 869: 4, 880: 6, 961: 8, 967: 4, 969: 8, 977: 8, 979: 8, 985: 5, 1001: 8, 1005: 6, 1009: 8, 1017: 8, 1019: 2, 1020: 8, 1033: 7, 1034: 7, 1105: 6, 1217: 8, 1221: 5, 1223: 2, 1225: 7, 1233: 8, 1249: 8, 1257: 6, 1265: 8, 1267: 1, 1280: 4, 1296: 4, 1300: 8, 1322: 6, 1323: 4, 1328: 4, 1417: 8, 1609: 8, 1613: 8, 1649: 8, 1792: 8, 1798: 8, 1824: 8, 1825: 8, 1840: 8, 1842: 8, 1858: 8, 1860: 8, 1863: 8, 1872: 8, 1875: 8, 1882: 8, 1888: 8, 1889: 8, 1892: 8, 1906: 7, 1907: 7, 1912: 7, 1914: 7, 1917: 7, 1918: 7, 1919: 7, 1920: 7, 1930: 7, 1937: 8, 1953: 8, 1968: 8, 2001: 8, 2017: 8, 2018: 8, 2020: 8, 2026: 8 + }], CAR.ESCALADE_ESV: [ { 309: 1, 848: 8, 849: 8, 850: 8, 851: 8, 852: 8, 853: 8, 854: 3, 1056: 6, 1057: 8, 1058: 8, 1059: 8, 1060: 8, 1061: 8, 1062: 8, 1063: 8, 1064: 8, 1065: 8, 1066: 8, 1067: 8, 1068: 8, 1120: 8, 1121: 8, 1122: 8, 1123: 8, 1124: 8, 1125: 8, 1126: 8, 1127: 8, 1128: 8, 1129: 8, 1130: 8, 1131: 8, 1132: 8, 1133: 8, 1134: 8, 1135: 8, 1136: 8, 1137: 8, 1138: 8, 1139: 8, 1140: 8, 1141: 8, 1142: 8, 1143: 8, 1146: 8, 1147: 8, 1148: 8, 1149: 8, 1150: 8, 1151: 8, 1216: 8, 1217: 8, 1218: 8, 1219: 8, 1220: 8, 1221: 8, 1222: 8, 1223: 8, 1224: 8, 1225: 8, 1226: 8, 1232: 8, 1233: 8, 1234: 8, 1235: 8, 1236: 8, 1237: 8, 1238: 8, 1239: 8, 1240: 8, 1241: 8, 1242: 8, 1787: 8, 1788: 8 @@ -178,9 +211,9 @@ FINGERPRINTS = { DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) -EV_CAR = {CAR.VOLT, CAR.BOLT_EV, CAR.BOLT_EUV} +EV_CAR = {CAR.VOLT, CAR.BOLT_EUV} # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) -CAMERA_ACC_CAR = {CAR.BOLT_EV, CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} +CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX} STEER_THRESHOLD = 1.0 diff --git a/selfdrive/car/honda/carcontroller.py b/selfdrive/car/honda/carcontroller.py index 5fa475fe08..4dc1dc8131 100644 --- a/selfdrive/car/honda/carcontroller.py +++ b/selfdrive/car/honda/carcontroller.py @@ -99,6 +99,12 @@ HUDData = namedtuple("HUDData", "lanes_visible", "fcw", "acc_alert", "steer_required"]) +def rate_limit_steer(new_steer, last_steer): + # TODO just hardcoded ramp to min/max in 0.33s for all Honda + MAX_DELTA = 3 * DT_CTRL + return clip(new_steer, last_steer - MAX_DELTA, last_steer + MAX_DELTA) + + class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP @@ -116,8 +122,9 @@ class CarController: self.speed = 0.0 self.gas = 0.0 self.brake = 0.0 + self.last_steer = 0.0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl hud_v_cruise = hud_control.setSpeed * CV.MS_TO_KPH if hud_control.speedVisible else 255 @@ -130,6 +137,10 @@ class CarController: accel = 0.0 gas, brake = 0.0, 0.0 + # *** rate limit steer *** + limited_steer = rate_limit_steer(actuators.steer, self.last_steer) + self.last_steer = limited_steer + # *** apply brake hysteresis *** pre_limit_brake, self.braking, self.brake_steady = actuator_hysteresis(brake, self.braking, self.brake_steady, CS.out.vEgo, self.CP.carFingerprint) @@ -143,7 +154,7 @@ class CarController: # **** process the car messages **** # steer torque is converted back to CAN reference (positive when steering right) - apply_steer = int(interp(-actuators.steer * self.params.STEER_MAX, + apply_steer = int(interp(-limited_steer * self.params.STEER_MAX, self.params.STEER_LOOKUP_BP, self.params.STEER_LOOKUP_V)) # Send CAN commands @@ -178,14 +189,14 @@ class CarController: clip(CS.out.vEgo + 0.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(1.0 * 0xc6) + pcm_accel = int(1.0 * self.params.NIDEC_GAS_MAX) else: pcm_speed_V = [0.0, clip(CS.out.vEgo - 2.0, 0.0, 100.0), clip(CS.out.vEgo + 2.0, 0.0, 100.0), clip(CS.out.vEgo + 5.0, 0.0, 100.0)] pcm_speed = interp(gas - brake, pcm_speed_BP, pcm_speed_V) - pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * 0xc6) + pcm_accel = int(clip((accel / 1.44) / max_accel, 0.0, 1.0) * self.params.NIDEC_GAS_MAX) if not self.CP.openpilotLongitudinalControl: if self.frame % 2 == 0 and self.CP.carFingerprint not in HONDA_BOSCH_RADARLESS: # radarless cars don't have supplemental message @@ -243,13 +254,15 @@ class CarController: self.speed = pcm_speed if not self.CP.enableGasInterceptor: - self.gas = pcm_accel / 0xc6 + self.gas = pcm_accel / self.params.NIDEC_GAS_MAX new_actuators = actuators.copy() new_actuators.speed = self.speed new_actuators.accel = self.accel new_actuators.gas = self.gas new_actuators.brake = self.brake + new_actuators.steer = self.last_steer + new_actuators.steerOutputCan = apply_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index a37667fd3a..16880d1b1f 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -274,7 +274,7 @@ class CarState(CarStateBase): ret.cruiseState.available = bool(cp.vl[self.main_on_sig_msg]["MAIN_ON"]) # Gets rid of Pedal Grinding noise when brake is pressed at slow speeds for some models - if self.CP.carFingerprint in (CAR.PILOT, CAR.PASSPORT, CAR.RIDGELINE): + if self.CP.carFingerprint in (CAR.PILOT, CAR.RIDGELINE): if ret.brake > 0.1: ret.brakePressed = True diff --git a/selfdrive/car/honda/hondacan.py b/selfdrive/car/honda/hondacan.py index 87f8e6c5de..17681444af 100644 --- a/selfdrive/car/honda/hondacan.py +++ b/selfdrive/car/honda/hondacan.py @@ -13,7 +13,8 @@ def get_pt_bus(car_fingerprint): def get_lkas_cmd_bus(car_fingerprint, radar_disabled=False): - if radar_disabled: + no_radar = car_fingerprint in HONDA_BOSCH_RADARLESS + if radar_disabled or no_radar: # when radar is disabled, steering commands are sent directly to powertrain bus return get_pt_bus(car_fingerprint) # normally steering commands are sent to radar, which forwards them to powertrain bus @@ -104,13 +105,13 @@ def create_bosch_supplemental_1(packer, car_fingerprint): def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, lkas_hud): commands = [] bus_pt = get_pt_bus(CP.carFingerprint) - radar_disabled = CP.carFingerprint in HONDA_BOSCH and CP.openpilotLongitudinalControl + radar_disabled = CP.carFingerprint in (HONDA_BOSCH - HONDA_BOSCH_RADARLESS) and CP.openpilotLongitudinalControl bus_lkas = get_lkas_cmd_bus(CP.carFingerprint, radar_disabled) if CP.openpilotLongitudinalControl: acc_hud_values = { 'CRUISE_SPEED': hud.v_cruise, - 'ENABLE_MINI_CAR': 1, + 'ENABLE_MINI_CAR': 1 if enabled else 0, 'HUD_DISTANCE': 0, # max distance setting on display 'IMPERIAL_UNIT': int(not is_metric), 'HUD_LEAD': 2 if enabled and hud.lead_visible else 1 if enabled else 0, @@ -153,7 +154,7 @@ def create_ui_commands(packer, CP, enabled, pcm_speed, hud, is_metric, acc_hud, else: commands.append(packer.make_can_msg('LKAS_HUD', bus_lkas, lkas_hud_values)) - if radar_disabled and CP.carFingerprint in HONDA_BOSCH: + if radar_disabled: radar_hud_values = { 'CMBS_OFF': 0x01, 'SET_TO_1': 0x01, diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index c884f586a0..d3cf9fa891 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -4,7 +4,7 @@ from panda import Panda from common.conversions import Conversions as CV from common.numpy_fast import interp from selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS -from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, CivicParams, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu @@ -21,6 +21,8 @@ class CarInterface(CarInterfaceBase): def get_pid_accel_limits(CP, current_speed, cruise_speed): if CP.carFingerprint in HONDA_BOSCH: return CarControllerParams.BOSCH_ACCEL_MIN, CarControllerParams.BOSCH_ACCEL_MAX + elif CP.enableGasInterceptor: + return CarControllerParams.NIDEC_ACCEL_MIN, CarControllerParams.NIDEC_ACCEL_MAX else: # NIDECs don't allow acceleration near cruise_speed, # so limit limits of pid to prevent windup @@ -29,13 +31,12 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.NIDEC_ACCEL_MIN, interp(current_speed, ACCEL_MAX_BP, ACCEL_MAX_VALS) @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "honda" if candidate in HONDA_BOSCH: ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hondaBosch)] - ret.radarOffCan = True + ret.radarUnavailable = True if candidate not in HONDA_BOSCH_RADARLESS: # Disable the radar and let openpilot control longitudinal @@ -220,29 +221,23 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2], [0.06]] tire_stiffness_factor = 0.677 - elif candidate == CAR.ODYSSEY: - ret.mass = 4471. * CV.LB_TO_KG + STD_CARGO_KG + elif candidate in (CAR.ODYSSEY, CAR.ODYSSEY_CHN): + ret.mass = 1900. + STD_CARGO_KG ret.wheelbase = 3.00 ret.centerToFront = ret.wheelbase * 0.41 ret.steerRatio = 14.35 # as spec - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.82 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] + if candidate == CAR.ODYSSEY_CHN: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end + else: + ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end - elif candidate == CAR.ODYSSEY_CHN: - ret.mass = 1849.2 + STD_CARGO_KG # mean of 4 models in kg - ret.wheelbase = 2.90 - ret.centerToFront = ret.wheelbase * 0.41 # from CAR.ODYSSEY - ret.steerRatio = 14.35 - ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 32767], [0, 32767]] # TODO: determine if there is a dead zone at the top end - tire_stiffness_factor = 0.82 - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.28], [0.08]] - - elif candidate in (CAR.PILOT, CAR.PASSPORT): - ret.mass = 4204. * CV.LB_TO_KG + STD_CARGO_KG # average weight - ret.wheelbase = 2.82 + elif candidate == CAR.PILOT: + ret.mass = 4278. * CV.LB_TO_KG + STD_CARGO_KG # average weight + ret.wheelbase = 2.86 ret.centerToFront = ret.wheelbase * 0.428 - ret.steerRatio = 17.25 # as spec + ret.steerRatio = 16.0 # as spec ret.lateralParams.torqueBP, ret.lateralParams.torqueV = [[0, 4096], [0, 4096]] # TODO: determine if there is a dead zone at the top end tire_stiffness_factor = 0.444 ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.38], [0.11]] @@ -297,10 +292,6 @@ class CarInterface(CarInterfaceBase): stop_and_go = candidate in (HONDA_BOSCH | {CAR.CIVIC}) or ret.enableGasInterceptor ret.minEnableSpeed = -1. if stop_and_go else 25.5 * CV.MPH_TO_MS - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -359,5 +350,5 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/honda/radar_interface.py b/selfdrive/car/honda/radar_interface.py index 629ab01d4c..660be4c449 100755 --- a/selfdrive/car/honda/radar_interface.py +++ b/selfdrive/car/honda/radar_interface.py @@ -21,7 +21,7 @@ class RadarInterface(RadarInterfaceBase): self.track_id = 0 self.radar_fault = False self.radar_wrong_config = False - self.radar_off_can = CP.radarOffCan + self.radar_off_can = CP.radarUnavailable self.radar_ts = CP.radarTimeStep self.delay = int(round(0.1 / CP.radarTimeStep)) # 0.1s delay of radar diff --git a/selfdrive/controls/lib/cluster/__init__.py b/selfdrive/car/honda/tests/__init__.py similarity index 100% rename from selfdrive/controls/lib/cluster/__init__.py rename to selfdrive/car/honda/tests/__init__.py diff --git a/selfdrive/car/honda/tests/test_honda.py b/selfdrive/car/honda/tests/test_honda.py new file mode 100755 index 0000000000..7a8c86fb0a --- /dev/null +++ b/selfdrive/car/honda/tests/test_honda.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +import re +import unittest + +from selfdrive.car.honda.values import FW_VERSIONS + +HONDA_FW_VERSION_RE = br"\d{5}-[A-Z0-9]{3}(-|,)[A-Z0-9]{4}(\x00){2}$" + + +class TestHondaFingerprint(unittest.TestCase): + def test_fw_version_format(self): + # Asserts all FW versions follow an expected format + for fw_by_ecu in FW_VERSIONS.values(): + for fws in fw_by_ecu.values(): + for fw in fws: + self.assertTrue(re.match(HONDA_FW_VERSION_RE, fw) is not None, fw) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 638008934a..009a549afd 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -4,9 +4,10 @@ from typing import Dict, List, Optional, Union from cereal import car from common.conversions import Conversions as CV +from panda.python import uds from selfdrive.car import dbc_dict from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness -from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries +from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 Ecu = car.CarParams.Ecu VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -27,6 +28,7 @@ class CarControllerParams: NIDEC_MAX_ACCEL_V = [0.5, 2.4, 1.4, 0.6] NIDEC_MAX_ACCEL_BP = [0.0, 4.0, 10., 20.] + NIDEC_GAS_MAX = 198 # 0xc6 NIDEC_BRAKE_MAX = 1024 // 4 BOSCH_ACCEL_MIN = -3.5 # m/s^2 @@ -90,7 +92,6 @@ class CAR: ACURA_RDX = "ACURA RDX 2018" ACURA_RDX_3G = "ACURA RDX 2020" PILOT = "HONDA PILOT 2017" - PASSPORT = "HONDA PASSPORT 2021" RIDGELINE = "HONDA RIDGELINE 2017" INSIGHT = "HONDA INSIGHT 2019" HONDA_E = "HONDA E 2020" @@ -106,48 +107,91 @@ class Footnote(Enum): class HondaCarInfo(CarInfo): package: str = "Honda Sensing" + def init_make(self, CP: car.CarParams): + if CP.carFingerprint in HONDA_BOSCH: + self.harness = Harness.bosch_b if CP.carFingerprint in HONDA_BOSCH_RADARLESS else Harness.bosch_a + else: + self.harness = Harness.nidec + CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { CAR.ACCORD: [ - HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Accord 2018-22", "All", "https://www.youtube.com/watch?v=mrUwlj3Mi58", min_steer_speed=3. * CV.MPH_TO_MS), + HondaCarInfo("Honda Inspire 2018", "All", min_steer_speed=3. * CV.MPH_TO_MS), ], - CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec, video_link="https://youtu.be/-IkImTe1NYE"), + CAR.ACCORDH: HondaCarInfo("Honda Accord Hybrid 2018-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.CIVIC: HondaCarInfo("Honda Civic 2016-18", min_steer_speed=12. * CV.MPH_TO_MS, video_link="https://youtu.be/-IkImTe1NYE"), CAR.CIVIC_BOSCH: [ - HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS, harness=Harness.bosch_a), - HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + HondaCarInfo("Honda Civic 2019-21", "All", "https://www.youtube.com/watch?v=4Iz1Mz5LGF8", [Footnote.CIVIC_DIESEL], 2. * CV.MPH_TO_MS), + HondaCarInfo("Honda Civic Hatchback 2017-21", min_steer_speed=12. * CV.MPH_TO_MS), ], CAR.CIVIC_BOSCH_DIESEL: None, # same platform CAR.CIVIC_2022: [ - HondaCarInfo("Honda Civic 2022", "All", harness=Harness.bosch_b), - HondaCarInfo("Honda Civic Hatchback 2022", "All", harness=Harness.bosch_b), + HondaCarInfo("Honda Civic 2022", "All"), + HondaCarInfo("Honda Civic Hatchback 2022", "All"), ], - CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_ILX: HondaCarInfo("Acura ILX 2016-19", "AcuraWatch Plus", min_steer_speed=25. * CV.MPH_TO_MS), + CAR.CRV: HondaCarInfo("Honda CR-V 2015-16", "Touring Trim", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.CRV_5G: HondaCarInfo("Honda CR-V 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), CAR.CRV_EU: None, # HondaCarInfo("Honda CR-V EU", "Touring"), # Euro version of CRV Touring - CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20", harness=Harness.nidec), + CAR.CRV_HYBRID: HondaCarInfo("Honda CR-V Hybrid 2017-19", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FIT: HondaCarInfo("Honda Fit 2018-20", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.FREED: HondaCarInfo("Honda Freed 2020", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.HRV: HondaCarInfo("Honda HR-V 2019-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ODYSSEY: HondaCarInfo("Honda Odyssey 2018-20"), CAR.ODYSSEY_CHN: None, # Chinese version of Odyssey - CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.PILOT: HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.PASSPORT: HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS, harness=Harness.nidec), - CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), - CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS, harness=Harness.bosch_a), + CAR.ACURA_RDX: HondaCarInfo("Acura RDX 2016-18", "AcuraWatch Plus", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.ACURA_RDX_3G: HondaCarInfo("Acura RDX 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.PILOT: [ + HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), + HondaCarInfo("Honda Passport 2019-21", "All", min_steer_speed=12. * CV.MPH_TO_MS), + ], + CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-22", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.INSIGHT: HondaCarInfo("Honda Insight 2019-22", "All", min_steer_speed=3. * CV.MPH_TO_MS), + CAR.HONDA_E: HondaCarInfo("Honda e 2020", "All", min_steer_speed=3. * CV.MPH_TO_MS), } +HONDA_VERSION_REQUEST = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xF112) +HONDA_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) + \ + p16(0xF112) + FW_QUERY_CONFIG = FwQueryConfig( requests=[ + # Currently used to fingerprint Request( [StdQueries.UDS_VERSION_REQUEST], [StdQueries.UDS_VERSION_RESPONSE], + bus=1, ), + + # Data collection requests: + # Log extra identifiers for current ECUs + Request( + [HONDA_VERSION_REQUEST], + [HONDA_VERSION_RESPONSE], + bus=1, + logging=True, + ), + # Nidec PT bus + Request( + [StdQueries.UDS_VERSION_REQUEST], + [StdQueries.UDS_VERSION_RESPONSE], + bus=0, + logging=True, + ), + # Bosch PT bus + Request( + [StdQueries.UDS_VERSION_REQUEST], + [StdQueries.UDS_VERSION_RESPONSE], + bus=1, + logging=True, + obd_multiplexing=False, + ), + ], + extra_ecus=[ + # The only other ECU on PT bus accessible by camera on radarless Civic + (Ecu.unknown, 0x18DAB3F1, None), ], ) @@ -236,7 +280,7 @@ FW_VERSIONS = { b'39990-TVA-A340\x00\x00', b'39990-TVA-X030\x00\x00', b'39990-TVA-X040\x00\x00', - b'39990-TVA,A150\x00\x00', + b'39990-TVA,A150\x00\x00', # modified firmware b'39990-TVE-H130\x00\x00', ], (Ecu.unknown, 0x18da3af1, None): [ @@ -324,10 +368,12 @@ FW_VERSIONS = { b'57114-TWA-A050\x00\x00', b'57114-TWA-A530\x00\x00', b'57114-TWA-B520\x00\x00', + b'57114-TWB-H030\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TWA-A440\x00\x00', b'77959-TWA-L420\x00\x00', + b'77959-TWB-H220\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ b'78109-TWA-A010\x00\x00', @@ -341,6 +387,7 @@ FW_VERSIONS = { b'78109-TWA-A230\x00\x00', b'78109-TWA-L010\x00\x00', b'78109-TWA-L210\x00\x00', + b'78109-TWA-H210\x00\x00', ], (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TWA-A910\x00\x00', @@ -352,16 +399,19 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x18dab5f1, None): [ b'36161-TWA-A070\x00\x00', b'36161-TWA-A330\x00\x00', + b'36161-TWB-H040\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36802-TWA-A070\x00\x00', b'36802-TWA-A080\x00\x00', b'36802-TWA-A330\x00\x00', + b'36802-TWB-H060\x00\x00', ], (Ecu.eps, 0x18da30f1, None): [ b'39990-TVA-A160\x00\x00', b'39990-TVA-A150\x00\x00', b'39990-TVA-A340\x00\x00', + b'39990-TWB-H120\x00\x00', ], }, CAR.CIVIC: { @@ -446,7 +496,7 @@ FW_VERSIONS = { b'78109-TED-Q510\x00\x00', b'78109-TEG-A310\x00\x00', ], - (Ecu.fwdCamera, 0x18dab0f1, None): [ + (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-TBA-A020\x00\x00', b'36161-TBA-A030\x00\x00', b'36161-TBA-A040\x00\x00', @@ -964,7 +1014,7 @@ FW_VERSIONS = { b'77959-THR-A110\x00\x00', b'77959-THR-X010\x00\x00', ], - (Ecu.fwdCamera, 0x18dab0f1, None): [ + (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-THR-A020\x00\x00', b'36161-THR-A030\x00\x00', b'36161-THR-A110\x00\x00', @@ -1031,6 +1081,23 @@ FW_VERSIONS = { b'54008-THR-A020\x00\x00', ], }, + CAR.ODYSSEY_CHN: { + (Ecu.eps, 0x18da30f1, None): [ + b'39990-T6D-H220\x00\x00', + ], + (Ecu.gateway, 0x18daeff1, None): [ + b'38897-T6A-J010\x00\x00', + ], + (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78109-T6A-F310\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T6A-P040\x00\x00', + ], + (Ecu.srs, 0x18da53f1, None): [ + b'77959-T6A-P110\x00\x00', + ], + }, CAR.PILOT: { (Ecu.shiftByWire, 0x18da0bf1, None): [ b'54008-TG7-A520\x00\x00', @@ -1043,6 +1110,8 @@ FW_VERSIONS = { b'28101-5EZ-A060\x00\x00', b'28101-5EZ-A100\x00\x00', b'28101-5EZ-A210\x00\x00', + b'28101-5EZ-A600\x00\x00', + b'28101-5EZ-A430\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-RLV-4060\x00\x00', @@ -1054,6 +1123,9 @@ FW_VERSIONS = { b'37805-RLV-C520\x00\x00', b'37805-RLV-C530\x00\x00', b'37805-RLV-C910\x00\x00', + b'37805-RLV-B220\x00\x00', + b'37805-RLV-B210\x00\x00', + b'37805-RLV-L160\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ b'38897-TG7-A030\x00\x00', @@ -1068,7 +1140,7 @@ FW_VERSIONS = { b'39990-TG7-A070\x00\x00', b'39990-TGS-A230\x00\x00', ], - (Ecu.fwdCamera, 0x18dab0f1, None): [ + (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-TG7-A310\x00\x00', b'36161-TG7-A520\x00\x00', b'36161-TG7-A630\x00\x00', @@ -1086,6 +1158,7 @@ FW_VERSIONS = { b'36161-TGS-A130\x00\x00', b'36161-TGT-A030\x00\x00', b'36161-TGT-A130\x00\x00', + b'36161-TGS-A030\x00\x00', ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TG7-A020\x00\x00', @@ -1123,6 +1196,9 @@ FW_VERSIONS = { b'78109-TGS-AP20\x00\x00', b'78109-TGT-AJ20\x00\x00', b'78109-TGT-AK30\x00\x00', + b'78109-TGS-AT20\x00\x00', + b'78109-TGS-AX20\x00\x00', + b'78109-TGS-AJ20\x00\x00', ], (Ecu.vsa, 0x18da28f1, None): [ b'57114-TG7-A130\x00\x00', @@ -1139,44 +1215,12 @@ FW_VERSIONS = { b'57114-TGT-A530\x00\x00', ], }, - CAR.PASSPORT: { - (Ecu.programmedFuelInjection, 0x18da10f1, None): [ - b'37805-RLV-B220\x00\x00', - b'37805-RLV-B210\x00\x00', - ], - (Ecu.eps, 0x18da30f1, None): [ - b'39990-TGS-A230\x00\x00', - ], - (Ecu.fwdRadar, 0x18dab0f1, None): [ - b'36161-TGS-A030\x00\x00', - b'36161-TGS-A130\x00\x00', - ], - (Ecu.gateway, 0x18daeff1, None): [ - b'38897-TG7-A040\x00\x00', - ], - (Ecu.srs, 0x18da53f1, None): [ - b'77959-TGS-A010\x00\x00', - ], - (Ecu.shiftByWire, 0x18da0bf1, None): [ - b'54008-TG7-A530\x00\x00', - ], - (Ecu.transmission, 0x18da1ef1, None): [ - b'28101-5EZ-A600\x00\x00', - ], - (Ecu.combinationMeter, 0x18da60f1, None): [ - b'78109-TGS-AT20\x00\x00', - b'78109-TGS-AX20\x00\x00', - ], - (Ecu.vsa, 0x18da28f1, None): [ - b'57114-TGS-A530\x00\x00', - ], - }, CAR.ACURA_RDX: { (Ecu.vsa, 0x18da28f1, None): [ b'57114-TX5-A220\x00\x00', b'57114-TX4-A220\x00\x00', ], - (Ecu.fwdCamera, 0x18dab0f1, None): [ + (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-TX5-A030\x00\x00', b'36161-TX4-A030\x00\x00', ], @@ -1273,7 +1317,7 @@ FW_VERSIONS = { b'39990-T6Z-A030\x00\x00', b'39990-T6Z-A050\x00\x00', ], - (Ecu.fwdCamera, 0x18dab0f1, None): [ + (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T6Z-A020\x00\x00', b'36161-T6Z-A310\x00\x00', b'36161-T6Z-A420\x00\x00', @@ -1412,6 +1456,7 @@ FW_VERSIONS = { (Ecu.eps, 0x18DA30F1, None): [ b'39990-T39-A130\x00\x00', b'39990-T43-J020\x00\x00', + b'39990-T24-T120\x00\x00', ], (Ecu.gateway, 0x18DAEFF1, None): [ b'38897-T20-A020\x00\x00', @@ -1419,11 +1464,13 @@ FW_VERSIONS = { b'38897-T21-A010\x00\x00', b'38897-T20-A210\x00\x00', b'38897-T20-A310\x00\x00', + b'38897-T24-Z120\x00\x00', ], (Ecu.srs, 0x18DA53F1, None): [ b'77959-T20-A970\x00\x00', b'77959-T47-A940\x00\x00', b'77959-T47-A950\x00\x00', + b'77959-T20-M820\x00\x00', ], (Ecu.combinationMeter, 0x18DA60F1, None): [ b'78108-T21-A220\x00\x00', @@ -1431,16 +1478,26 @@ FW_VERSIONS = { b'78108-T23-A110\x00\x00', b'78108-T21-A230\x00\x00', b'78108-T22-A020\x00\x00', + b'78108-T21-MB10\x00\x00', + ], + (Ecu.fwdRadar, 0x18dab0f1, None): [ + b'36161-T20-A070\x00\x00', + b'36161-T20-A080\x00\x00', + b'36161-T20-A060\x00\x00', + b'36161-T47-A070\x00\x00', + b'36161-T24-T070\x00\x00', ], (Ecu.vsa, 0x18DA28F1, None): [ b'57114-T20-AB40\x00\x00', b'57114-T43-JB30\x00\x00', + b'57114-T24-TB30\x00\x00', ], (Ecu.transmission, 0x18da1ef1, None): [ b'28101-65D-A020\x00\x00', b'28101-65D-A120\x00\x00', b'28101-65H-A020\x00\x00', b'28101-65H-A120\x00\x00', + b'28101-65J-N010\x00\x00', ], (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-64L-A540\x00\x00', @@ -1448,6 +1505,7 @@ FW_VERSIONS = { b'37805-64S-A720\x00\x00', b'37805-64A-A540\x00\x00', b'37805-64A-A620\x00\x00', + b'37805-64D-P510\x00\x00', ], }, } @@ -1471,7 +1529,6 @@ DBC = { CAR.ODYSSEY: dbc_dict('honda_odyssey_exl_2018_generated', 'acura_ilx_2016_nidec'), CAR.ODYSSEY_CHN: dbc_dict('honda_odyssey_extreme_edition_2018_china_can_generated', 'acura_ilx_2016_nidec'), CAR.PILOT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), - CAR.PASSPORT: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.RIDGELINE: dbc_dict('acura_ilx_2016_can_generated', 'acura_ilx_2016_nidec'), CAR.INSIGHT: dbc_dict('honda_insight_ex_2019_can_generated', None), CAR.HONDA_E: dbc_dict('acura_rdx_2020_can_generated', None), @@ -1486,7 +1543,7 @@ STEER_THRESHOLD = { 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.PASSPORT, CAR.RIDGELINE} + CAR.PILOT, 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, CAR.CIVIC_2022} 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 b567013774..2b721d28a4 100644 --- a/selfdrive/car/hyundai/carcontroller.py +++ b/selfdrive/car/hyundai/carcontroller.py @@ -3,7 +3,7 @@ from common.conversions import Conversions as CV from common.numpy_fast import clip from common.realtime import DT_CTRL from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_torque_limits +from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.hyundai import hyundaicanfd, hyundaican from selfdrive.car.hyundai.values import HyundaiFlags, Buttons, CarControllerParams, CANFD_CAR, CAR, CAMERA_SCC_CAR @@ -49,21 +49,18 @@ class CarController: self.angle_limit_counter = 0 self.frame = 0 + self.accel_last = 0 self.apply_steer_last = 0 self.car_fingerprint = CP.carFingerprint self.last_button_frame = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl # steering torque - steer = actuators.steer - if self.CP.carFingerprint in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): - # these cars have significantly more torque than most HKG; limit to 70% of max - steer = clip(steer, -0.7, 0.7) - new_steer = int(round(steer * self.params.STEER_MAX)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) + new_steer = int(round(actuators.steer * self.params.STEER_MAX)) + apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.params) if not CC.latActive: apply_steer = 0 @@ -84,12 +81,18 @@ class CarController: # *** common hyundai stuff *** # tester present - w/ no response (keeps relevant ECU disabled) - if self.frame % 100 == 0 and self.CP.openpilotLongitudinalControl and self.CP.carFingerprint not in CAMERA_SCC_CAR: + if self.frame % 100 == 0 and not (self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and \ + self.CP.carFingerprint not in CAMERA_SCC_CAR and self.CP.openpilotLongitudinalControl: + # for longitudinal control, either radar or ADAS driving ECU addr, bus = 0x7d0, 0 if self.CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 can_sends.append([addr, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", bus]) + # for blinkers + if self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: + can_sends.append([0x7b1, 0, b"\x02\x3E\x80\x00\x00\x00\x00\x00", 5]) + # >90 degree steering fault prevention # Count up to MAX_ANGLE_FRAMES, at which point we need to cut torque to avoid a steering fault if CC.latActive and abs(CS.out.steeringAngleDeg) >= MAX_ANGLE: @@ -120,11 +123,17 @@ class CarController: if self.frame % 5 == 0 and (not hda2 or hda2_long): can_sends.append(hyundaicanfd.create_lfahda_cluster(self.packer, self.CP, CC.enabled)) + # blinkers + if hda2 and self.CP.flags & HyundaiFlags.ENABLE_BLINKERS: + can_sends.extend(hyundaicanfd.create_spas_messages(self.packer, self.frame, CC.leftBlinker, CC.rightBlinker)) + if self.CP.openpilotLongitudinalControl: - can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) + if hda2: + can_sends.extend(hyundaicanfd.create_adrv_messages(self.packer, self.frame)) if self.frame % 2 == 0: - can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, accel, stopping, CC.cruiseControl.override, + can_sends.append(hyundaicanfd.create_acc_control(self.packer, self.CP, CC.enabled, self.accel_last, accel, stopping, CC.cruiseControl.override, set_speed_in_units)) + self.accel_last = accel else: # button presses if (self.frame - self.last_button_frame) * DT_CTRL > 0.25: @@ -135,14 +144,17 @@ class CarController: self.last_button_frame = self.frame else: for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.CANCEL)) + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.CANCEL)) self.last_button_frame = self.frame # cruise standstill resume elif CC.cruiseControl.resume: - if not (self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS: + # TODO: resume for alt button cars + pass + else: for _ in range(20): - can_sends.append(hyundaicanfd.create_buttons(self.packer, CS.buttons_counter+1, Buttons.RES_ACCEL)) + can_sends.append(hyundaicanfd.create_buttons(self.packer, self.CP, CS.buttons_counter+1, Buttons.RES_ACCEL)) self.last_button_frame = self.frame else: can_sends.append(hyundaican.create_lkas11(self.packer, self.frame, self.car_fingerprint, apply_steer, lat_active, @@ -158,7 +170,8 @@ class CarController: if (self.frame - self.last_button_frame) * DT_CTRL > 0.1: # send 25 messages at a time to increases the likelihood of resume being accepted can_sends.extend([hyundaican.create_clu11(self.packer, self.frame, CS.clu11, Buttons.RES_ACCEL, self.CP.carFingerprint)] * 25) - self.last_button_frame = self.frame + if (self.frame - self.last_button_frame) * DT_CTRL >= 0.15: + self.last_button_frame = self.frame if self.frame % 2 == 0 and self.CP.openpilotLongitudinalControl: # TODO: unclear if this is needed @@ -167,10 +180,7 @@ class CarController: hud_control.leadVisible, set_speed_in_units, stopping, CC.cruiseControl.override)) # 20 Hz LFA MFA message - if self.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.KONA_EV_2022, - 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.SANTA_FE_PHEV_2022): + if self.frame % 5 == 0 and self.CP.flags & HyundaiFlags.SEND_LFA.value: can_sends.append(hyundaican.create_lfahda_mfc(self.packer, CC.enabled)) # 5 Hz ACC options @@ -183,6 +193,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = apply_steer / self.params.STEER_MAX + new_actuators.steerOutputCan = apply_steer new_actuators.accel = accel self.frame += 1 diff --git a/selfdrive/car/hyundai/carstate.py b/selfdrive/car/hyundai/carstate.py index 22f4c89111..99c08bb5c0 100644 --- a/selfdrive/car/hyundai/carstate.py +++ b/selfdrive/car/hyundai/carstate.py @@ -6,7 +6,8 @@ from cereal import car from common.conversions import Conversions as CV from opendbc.can.parser import CANParser from opendbc.can.can_define import CANDefine -from selfdrive.car.hyundai.values import HyundaiFlags, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.hyundaicanfd import get_e_can_bus +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, FEATURES, CAMERA_SCC_CAR, CANFD_CAR, EV_CAR, HYBRID_CAR, Buttons, CarControllerParams from selfdrive.car.interfaces import CarStateBase PREV_BUTTON_SAMPLES = 8 @@ -21,8 +22,11 @@ class CarState(CarStateBase): self.cruise_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) self.main_buttons = deque([Buttons.NONE] * PREV_BUTTON_SAMPLES, maxlen=PREV_BUTTON_SAMPLES) + self.gear_msg_canfd = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" if CP.carFingerprint in CANFD_CAR: - self.shifter_values = can_define.dv["GEAR_SHIFTER"]["GEAR"] + self.shifter_values = can_define.dv[self.gear_msg_canfd]["GEAR"] elif self.CP.carFingerprint in FEATURES["use_cluster_gears"]: self.shifter_values = can_define.dv["CLU15"]["CF_Clu_Gear"] elif self.CP.carFingerprint in FEATURES["use_tcu_gears"]: @@ -31,7 +35,6 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["LVR12"]["CF_Lvr_Gear"] self.is_metric = False - self.brake_error = False self.buttons_counter = 0 self.cruise_info = {} @@ -71,8 +74,10 @@ class CarState(CarStateBase): self.cluster_speed = cp.vl["CLU15"]["CF_Clu_VehicleSpeed"] self.cluster_speed_counter = 0 - # mimic how dash converts to imperial - if not self.is_metric: + # Mimic how dash converts to imperial. + # Sorento is the only platform where CF_Clu_VehicleSpeed is already imperial when not is_metric + # TODO: CGW_USM1->CF_Gway_DrLockSoundRValue may describe this + if not self.is_metric and self.CP.carFingerprint not in (CAR.KIA_SORENTO,): self.cluster_speed = math.floor(self.cluster_speed * CV.KPH_TO_MPH + CV.KPH_TO_MPH) ret.vEgoCluster = self.cluster_speed * speed_conv @@ -84,7 +89,7 @@ class CarState(CarStateBase): 50, cp.vl["CGW1"]["CF_Gway_TurnSigLh"], cp.vl["CGW1"]["CF_Gway_TurnSigRh"]) ret.steeringTorque = cp.vl["MDPS12"]["CR_Mdps_StrColTq"] ret.steeringTorqueEps = cp.vl["MDPS12"]["CR_Mdps_OutTq"] - ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD + ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steerFaultTemporary = cp.vl["MDPS12"]["CF_Mdps_ToiUnavail"] != 0 or cp.vl["MDPS12"]["CF_Mdps_ToiFlt"] != 0 # cruise state @@ -102,8 +107,9 @@ class CarState(CarStateBase): # TODO: Find brake pressure ret.brake = 0 ret.brakePressed = cp.vl["TCS13"]["DriverBraking"] != 0 - ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY + ret.brakeHoldActive = cp.vl["TCS15"]["AVH_LAMP"] == 2 # 0 OFF, 1 ERROR, 2 ACTIVE, 3 READY ret.parkingBrake = cp.vl["TCS13"]["PBRAKE_ACT"] == 1 + ret.accFaulted = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.carFingerprint in (HYBRID_CAR | EV_CAR): if self.CP.carFingerprint in HYBRID_CAR: @@ -129,8 +135,8 @@ class CarState(CarStateBase): ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) if not self.CP.openpilotLongitudinalControl: - aeb_src = "FCA11" if self.CP.carFingerprint in FEATURES["use_fca"] else "SCC12" - aeb_sig = "FCA_CmdAct" if self.CP.carFingerprint in FEATURES["use_fca"] else "AEB_CmdAct" + aeb_src = "FCA11" if self.CP.flags & HyundaiFlags.USE_FCA.value else "SCC12" + aeb_sig = "FCA_CmdAct" if self.CP.flags & HyundaiFlags.USE_FCA.value else "AEB_CmdAct" aeb_warning = cp_cruise.vl[aeb_src]["CF_VSM_Warn"] != 0 aeb_braking = cp_cruise.vl[aeb_src]["CF_VSM_DecCmdAct"] != 0 or cp_cruise.vl[aeb_src][aeb_sig] != 0 ret.stockFcw = aeb_warning and not aeb_braking @@ -146,7 +152,6 @@ class CarState(CarStateBase): self.fca11 = copy.copy(cp_cruise.vl["FCA11"]) self.fca12 = copy.copy(cp_cruise.vl["FCA12"]) self.steer_state = cp.vl["MDPS12"]["CF_Mdps_ToiActive"] # 0 NOT ACTIVE, 1 ACTIVE - self.brake_error = cp.vl["TCS13"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwState"]) self.main_buttons.extend(cp.vl_all["CLU11"]["CF_Clu_CruiseSwMain"]) @@ -156,17 +161,24 @@ class CarState(CarStateBase): def update_canfd(self, cp, cp_cam): ret = car.CarState.new_message() - if self.CP.carFingerprint in EV_CAR: - ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. - elif self.CP.carFingerprint in HYBRID_CAR: - ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. - ret.gasPressed = ret.gas > 1e-5 - ret.brakePressed = cp.vl["BRAKE"]["BRAKE_PRESSED"] == 1 + self.is_metric = cp.vl["CRUISE_BUTTONS_ALT"]["DISTANCE_UNIT"] != 1 + speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS + + if self.CP.carFingerprint in (EV_CAR | HYBRID_CAR): + if self.CP.carFingerprint in EV_CAR: + ret.gas = cp.vl["ACCELERATOR"]["ACCELERATOR_PEDAL"] / 255. + else: + ret.gas = cp.vl["ACCELERATOR_ALT"]["ACCELERATOR_PEDAL"] / 1023. + ret.gasPressed = ret.gas > 1e-5 + else: + ret.gasPressed = bool(cp.vl["ACCELERATOR_BRAKE_ALT"]["ACCELERATOR_PEDAL_PRESSED"]) + + ret.brakePressed = cp.vl["TCS"]["DriverBraking"] == 1 ret.doorOpen = cp.vl["DOORS_SEATBELTS"]["DRIVER_DOOR_OPEN"] == 1 ret.seatbeltUnlatched = cp.vl["DOORS_SEATBELTS"]["DRIVER_SEATBELT_LATCHED"] == 0 - gear = cp.vl["GEAR_SHIFTER"]["GEAR"] + gear = cp.vl[self.gear_msg_canfd]["GEAR"] ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(gear)) # TODO: figure out positions @@ -184,27 +196,35 @@ class CarState(CarStateBase): ret.steeringAngleDeg = cp.vl["STEERING_SENSORS"]["STEERING_ANGLE"] * -1 ret.steeringTorque = cp.vl["MDPS"]["STEERING_COL_TORQUE"] ret.steeringTorqueEps = cp.vl["MDPS"]["STEERING_OUT_TORQUE"] - ret.steeringPressed = abs(ret.steeringTorque) > self.params.STEER_THRESHOLD + ret.steeringPressed = self.update_steering_pressed(abs(ret.steeringTorque) > self.params.STEER_THRESHOLD, 5) ret.steerFaultTemporary = cp.vl["MDPS"]["LKA_FAULT"] != 0 ret.leftBlinker, ret.rightBlinker = self.update_blinker_from_lamp(50, cp.vl["BLINKERS"]["LEFT_LAMP"], cp.vl["BLINKERS"]["RIGHT_LAMP"]) + if self.CP.enableBsm: + ret.leftBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FL_INDICATOR"] != 0 + ret.rightBlindspot = cp.vl["BLINDSPOTS_REAR_CORNERS"]["FR_INDICATOR"] != 0 - ret.cruiseState.available = True - ret.cruiseState.enabled = cp.vl["SCC1"]["CRUISE_ACTIVE"] == 1 - self.is_metric = cp.vl["CLUSTER_INFO"]["DISTANCE_UNIT"] != 1 - if not self.CP.openpilotLongitudinalControl: - speed_factor = CV.KPH_TO_MS if self.is_metric else CV.MPH_TO_MS - cp_cruise_info = cp if self.CP.flags & HyundaiFlags.CANFD_HDA2 else cp_cam - ret.cruiseState.speed = cp_cruise_info.vl["CRUISE_INFO"]["SET_SPEED"] * speed_factor - ret.cruiseState.standstill = cp_cruise_info.vl["CRUISE_INFO"]["CRUISE_STANDSTILL"] == 1 - self.cruise_info = copy.copy(cp_cruise_info.vl["CRUISE_INFO"]) + # cruise state + # CAN FD cars enable on main button press, set available if no TCS faults preventing engagement + ret.cruiseState.available = cp.vl["TCS"]["ACCEnable"] == 0 + if self.CP.openpilotLongitudinalControl: + # These are not used for engage/disengage since openpilot keeps track of state using the buttons + ret.cruiseState.enabled = cp.vl["TCS"]["ACC_REQ"] == 1 + ret.cruiseState.standstill = False + else: + cp_cruise_info = cp_cam if self.CP.flags & HyundaiFlags.CANFD_CAMERA_SCC else cp + ret.cruiseState.enabled = cp_cruise_info.vl["SCC_CONTROL"]["ACCMode"] in (1, 2) + ret.cruiseState.standstill = cp_cruise_info.vl["SCC_CONTROL"]["CRUISE_STANDSTILL"] == 1 + ret.cruiseState.speed = cp_cruise_info.vl["SCC_CONTROL"]["VSetDis"] * speed_factor + self.cruise_info = copy.copy(cp_cruise_info.vl["SCC_CONTROL"]) cruise_btn_msg = "CRUISE_BUTTONS_ALT" if self.CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" self.prev_cruise_buttons = self.cruise_buttons[-1] self.cruise_buttons.extend(cp.vl_all[cruise_btn_msg]["CRUISE_BUTTONS"]) self.main_buttons.extend(cp.vl_all[cruise_btn_msg]["ADAPTIVE_CRUISE_MAIN_BTN"]) self.buttons_counter = cp.vl[cruise_btn_msg]["COUNTER"] + ret.accFaulted = cp.vl["TCS"]["ACCEnable"] != 0 # 0 ACC CONTROL ENABLED, 1-3 ACC CONTROL DISABLED if self.CP.flags & HyundaiFlags.CANFD_HDA2: self.cam_0x2a4 = copy.copy(cp_cam.vl["CAM_0x2a4"]) @@ -299,7 +319,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("FCA_CmdAct", "FCA11"), ("CF_VSM_Warn", "FCA11"), @@ -390,7 +410,7 @@ class CarState(CarStateBase): ("SCC12", 50), ] - if CP.carFingerprint in FEATURES["use_fca"]: + if CP.flags & HyundaiFlags.USE_FCA.value: signals += [ ("CF_VSM_Prefill", "FCA11"), ("CF_VSM_HBACmd", "FCA11"), @@ -429,14 +449,16 @@ class CarState(CarStateBase): def get_can_parser_canfd(CP): cruise_btn_msg = "CRUISE_BUTTONS_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS else "CRUISE_BUTTONS" + gear_msg = "GEAR_ALT_2" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS_2 else \ + "GEAR_ALT" if CP.flags & HyundaiFlags.CANFD_ALT_GEARS else \ + "GEAR_SHIFTER" signals = [ ("WHEEL_SPEED_1", "WHEEL_SPEEDS"), ("WHEEL_SPEED_2", "WHEEL_SPEEDS"), ("WHEEL_SPEED_3", "WHEEL_SPEEDS"), ("WHEEL_SPEED_4", "WHEEL_SPEEDS"), - ("GEAR", "GEAR_SHIFTER"), - ("BRAKE_PRESSED", "BRAKE"), + ("GEAR", gear_msg), ("STEERING_RATE", "STEERING_SENSORS"), ("STEERING_ANGLE", "STEERING_SENSORS"), @@ -444,12 +466,14 @@ class CarState(CarStateBase): ("STEERING_OUT_TORQUE", "MDPS"), ("LKA_FAULT", "MDPS"), - ("CRUISE_ACTIVE", "SCC1"), + ("DriverBraking", "TCS"), + ("ACCEnable", "TCS"), + ("ACC_REQ", "TCS"), + ("COUNTER", cruise_btn_msg), ("CRUISE_BUTTONS", cruise_btn_msg), ("ADAPTIVE_CRUISE_MAIN_BTN", cruise_btn_msg), - - ("DISTANCE_UNIT", "CLUSTER_INFO"), + ("DISTANCE_UNIT", "CRUISE_BUTTONS_ALT"), ("LEFT_LAMP", "BLINKERS"), ("RIGHT_LAMP", "BLINKERS"), @@ -460,24 +484,35 @@ class CarState(CarStateBase): checks = [ ("WHEEL_SPEEDS", 100), - ("GEAR_SHIFTER", 100), - ("BRAKE", 100), + (gear_msg, 100), ("STEERING_SENSORS", 100), ("MDPS", 100), - ("SCC1", 50), - (cruise_btn_msg, 50), - ("CLUSTER_INFO", 4), + ("TCS", 50), + ("CRUISE_BUTTONS_ALT", 50), ("BLINKERS", 4), ("DOORS_SEATBELTS", 4), ] - if CP.flags & HyundaiFlags.CANFD_HDA2 and not CP.openpilotLongitudinalControl: + if not (CP.flags & HyundaiFlags.CANFD_ALT_BUTTONS): + checks.append(("CRUISE_BUTTONS", 50)) + + if CP.enableBsm: signals += [ - ("SET_SPEED", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), + ("FL_INDICATOR", "BLINDSPOTS_REAR_CORNERS"), + ("FR_INDICATOR", "BLINDSPOTS_REAR_CORNERS"), ] checks += [ - ("CRUISE_INFO", 50), + ("BLINDSPOTS_REAR_CORNERS", 20), + ] + + if not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and not CP.openpilotLongitudinalControl: + signals += [ + ("ACCMode", "SCC_CONTROL"), + ("VSetDis", "SCC_CONTROL"), + ("CRUISE_STANDSTILL", "SCC_CONTROL"), + ] + checks += [ + ("SCC_CONTROL", 50), ] if CP.carFingerprint in EV_CAR: @@ -494,32 +529,39 @@ class CarState(CarStateBase): checks += [ ("ACCELERATOR_ALT", 100), ] + else: + signals += [ + ("ACCELERATOR_PEDAL_PRESSED", "ACCELERATOR_BRAKE_ALT"), + ] + checks += [ + ("ACCELERATOR_BRAKE_ALT", 100), + ] - bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 - return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, bus) + return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, get_e_can_bus(CP)) @staticmethod def get_cam_can_parser_canfd(CP): + signals = [] + checks = [] if CP.flags & HyundaiFlags.CANFD_HDA2: - signals = [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] - checks = [("CAM_0x2a4", 20)] - else: - signals = [ - ("COUNTER", "CRUISE_INFO"), - ("NEW_SIGNAL_1", "CRUISE_INFO"), - ("CRUISE_MAIN", "CRUISE_INFO"), - ("CRUISE_STATUS", "CRUISE_INFO"), - ("CRUISE_INACTIVE", "CRUISE_INFO"), - ("ZEROS_9", "CRUISE_INFO"), - ("CRUISE_STANDSTILL", "CRUISE_INFO"), - ("ZEROS_5", "CRUISE_INFO"), - ("DISTANCE_SETTING", "CRUISE_INFO"), - ("SET_SPEED", "CRUISE_INFO"), - ("NEW_SIGNAL_4", "CRUISE_INFO"), + signals += [(f"BYTE{i}", "CAM_0x2a4") for i in range(3, 24)] + checks += [("CAM_0x2a4", 20)] + elif CP.flags & HyundaiFlags.CANFD_CAMERA_SCC: + signals += [ + ("COUNTER", "SCC_CONTROL"), + ("NEW_SIGNAL_1", "SCC_CONTROL"), + ("MainMode_ACC", "SCC_CONTROL"), + ("ACCMode", "SCC_CONTROL"), + ("CRUISE_INACTIVE", "SCC_CONTROL"), + ("ZEROS_9", "SCC_CONTROL"), + ("CRUISE_STANDSTILL", "SCC_CONTROL"), + ("ZEROS_5", "SCC_CONTROL"), + ("DISTANCE_SETTING", "SCC_CONTROL"), + ("VSetDis", "SCC_CONTROL"), ] - checks = [ - ("CRUISE_INFO", 50), + checks += [ + ("SCC_CONTROL", 50), ] return CANParser(DBC[CP.carFingerprint]["pt"], signals, checks, 6) diff --git a/selfdrive/car/hyundai/hyundaican.py b/selfdrive/car/hyundai/hyundaican.py index 494899cda2..611f51d0ce 100644 --- a/selfdrive/car/hyundai/hyundaican.py +++ b/selfdrive/car/hyundai/hyundaican.py @@ -20,7 +20,8 @@ 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.KONA_EV_2022, - CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, CAR.SANTA_FE_PHEV_2022): + CAR.SANTA_FE_2022, CAR.KIA_K5_2021, CAR.IONIQ_HEV_2022, CAR.SANTA_FE_HEV_2022, + CAR.SANTA_FE_PHEV_2022, CAR.KIA_STINGER_2022, CAR.KIA_K5_HEV_2020): values["CF_Lkas_LdwsActivemode"] = int(left_lane) + (int(right_lane) << 1) values["CF_Lkas_LdwsOpt_USM"] = 2 diff --git a/selfdrive/car/hyundai/hyundaicanfd.py b/selfdrive/car/hyundai/hyundaicanfd.py index f2cbafdcf0..af7239571c 100644 --- a/selfdrive/car/hyundai/hyundaicanfd.py +++ b/selfdrive/car/hyundai/hyundaicanfd.py @@ -1,9 +1,9 @@ +from common.numpy_fast import clip from selfdrive.car.hyundai.values import HyundaiFlags - def get_e_can_bus(CP): # On the CAN-FD platforms, the LKAS camera is on both A-CAN and E-CAN. HDA2 cars - # have a a different harness than the HDA1 and non-HDA variants in order to split + # have a different harness than the HDA1 and non-HDA variants in order to split # a different bus, since the steering is done by different ECUs. return 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 4 @@ -39,21 +39,22 @@ def create_cam_0x2a4(packer, camera_values): }) return packer.make_can_msg("CAM_0x2a4", 4, camera_values) -def create_buttons(packer, cnt, btn): +def create_buttons(packer, CP, cnt, btn): values = { "COUNTER": cnt, "SET_ME_1": 1, "CRUISE_BUTTONS": btn, } - return packer.make_can_msg("CRUISE_BUTTONS", 5, values) + + bus = 5 if CP.flags & HyundaiFlags.CANFD_HDA2 else 6 + return packer.make_can_msg("CRUISE_BUTTONS", bus, values) def create_acc_cancel(packer, CP, cruise_info_copy): values = cruise_info_copy values.update({ - "CRUISE_STATUS": 0, - "CRUISE_INACTIVE": 1, + "ACCMode": 4, }) - return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) + return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values) def create_lfahda_cluster(packer, CP, enabled): values = { @@ -63,35 +64,56 @@ def create_lfahda_cluster(packer, CP, enabled): return packer.make_can_msg("LFAHDA_CLUSTER", get_e_can_bus(CP), values) -def create_acc_control(packer, CP, enabled, accel, stopping, gas_override, set_speed): - cruise_status = 0 if not enabled else (4 if gas_override else 2) +def create_acc_control(packer, CP, enabled, accel_last, accel, stopping, gas_override, set_speed): + jerk = 5 + jn = jerk / 50 if not enabled or gas_override: - accel = 0 + a_val, a_raw = 0, 0 + else: + a_raw = accel + a_val = clip(accel, accel_last - jn, accel_last + jn) + values = { - "CRUISE_STATUS": cruise_status, - "CRUISE_INACTIVE": 0 if enabled else 1, - "CRUISE_MAIN": 1, - "CRUISE_STANDSTILL": 0, - "STOP_REQ": 1 if stopping else 0, - "ACCEL_REQ": accel, - "ACCEL_REQ2": accel, - "SET_SPEED": set_speed, - "DISTANCE_SETTING": 4, + "ACCMode": 0 if not enabled else (2 if gas_override else 1), + "MainMode_ACC": 1, + "StopReq": 1 if stopping else 0, + "aReqValue": a_val, + "aReqRaw": a_raw, + "VSetDis": set_speed, + "JerkLowerLimit": jerk if enabled else 1, + "JerkUpperLimit": 3.0, "ACC_ObjDist": 1, - "ObjValid": 1, + "ObjValid": 0, "OBJ_STATUS": 2, - "SET_ME_2": 0x2, + "SET_ME_2": 0x4, "SET_ME_3": 0x3, "SET_ME_TMP_64": 0x64, - - "NEW_SIGNAL_9": 2, - "NEW_SIGNAL_10": 4, + "DISTANCE_SETTING": 4, } - return packer.make_can_msg("CRUISE_INFO", get_e_can_bus(CP), values) + return packer.make_can_msg("SCC_CONTROL", get_e_can_bus(CP), values) +def create_spas_messages(packer, frame, left_blink, right_blink): + ret = [] + + values = { + } + ret.append(packer.make_can_msg("SPAS1", 5, values)) + + blink = 0 + if left_blink: + blink = 3 + elif right_blink: + blink = 4 + values = { + "BLINKER_CONTROL": blink, + } + ret.append(packer.make_can_msg("SPAS2", 5, values)) + + return ret + def create_adrv_messages(packer, frame): # messages needed to car happy after disabling diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 392ddf24bb..6aa776eb13 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -2,12 +2,14 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons, CarControllerParams +from selfdrive.car.hyundai.hyundaicanfd import get_e_can_bus +from selfdrive.car.hyundai.values import HyundaiFlags, CAR, DBC, CANFD_CAR, CAMERA_SCC_CAR, CANFD_RADAR_SCC_CAR, EV_CAR, HYBRID_CAR, LEGACY_SAFETY_MODE_CAR, Buttons from selfdrive.car.hyundai.radar_interface import RADAR_START_ADDR -from selfdrive.car import STD_CARGO_KG, create_button_event, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, create_button_event, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.disable_ecu import disable_ecu +Ecu = car.CarParams.Ecu ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL) @@ -17,168 +19,128 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu class CarInterface(CarInterfaceBase): @staticmethod - def get_pid_accel_limits(CP, current_speed, cruise_speed): - return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX - - @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "hyundai" - ret.radarOffCan = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None + ret.radarUnavailable = RADAR_START_ADDR not in fingerprint[1] or DBC[ret.carFingerprint]["radar"] is None # These cars have been put into dashcam only due to both a lack of users and test coverage. # These cars likely still work fine. Once a user confirms each car works and a test route is # added to selfdrive/car/tests/routes.py, we can remove it from this list. - ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, CAR.ELANTRA_GT_I30} + ret.dashcamOnly = candidate in {CAR.KIA_OPTIMA_H, } if candidate in CANFD_CAR: - # detect HDA2 with LKAS message - if 0x50 in fingerprint[6]: + # detect HDA2 with ADAS Driving ECU + if Ecu.adas in [fw.ecu for fw in car_fw]: ret.flags |= HyundaiFlags.CANFD_HDA2.value else: # non-HDA2 if 0x1cf not in fingerprint[4]: ret.flags |= HyundaiFlags.CANFD_ALT_BUTTONS.value + # ICE cars do not have 0x130; GEARS message on 0x40 or 0x70 instead + if 0x130 not in fingerprint[4]: + if 0x40 not in fingerprint[4]: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS_2.value + else: + ret.flags |= HyundaiFlags.CANFD_ALT_GEARS.value + if candidate not in CANFD_RADAR_SCC_CAR: + ret.flags |= HyundaiFlags.CANFD_CAMERA_SCC.value + else: + # Send LFA message on cars with HDA + if 0x485 in fingerprint[2]: + ret.flags |= HyundaiFlags.SEND_LFA.value + + # These cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 + if 0x38d in fingerprint[0] or 0x38d in fingerprint[2]: + ret.flags |= HyundaiFlags.USE_FCA.value ret.steerActuatorDelay = 0.1 # Default delay ret.steerLimitTimer = 0.4 tire_stiffness_factor = 1. + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) 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 # Values from optimizer ret.steerRatio = 16.55 # 13.8 is spec end-to-end tire_stiffness_factor = 0.82 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[9., 22.], [9., 22.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.35], [0.05, 0.09]] elif candidate in (CAR.SONATA, CAR.SONATA_HYBRID): ret.mass = 1513. + STD_CARGO_KG ret.wheelbase = 2.84 ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable tire_stiffness_factor = 0.65 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SONATA_LF: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 4497. * CV.LB_TO_KG ret.wheelbase = 2.804 ret.steerRatio = 13.27 * 1.15 # 15% higher at the center seems reasonable - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] elif candidate == CAR.PALISADE: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 1999. + STD_CARGO_KG ret.wheelbase = 2.90 ret.steerRatio = 15.6 * 1.15 tire_stiffness_factor = 0.63 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate in (CAR.ELANTRA, CAR.ELANTRA_GT_I30): - ret.lateralTuning.pid.kf = 0.00006 + elif candidate == CAR.ELANTRA: ret.mass = 1275. + STD_CARGO_KG ret.wheelbase = 2.7 ret.steerRatio = 15.4 # 14 is Stock | Settled Params Learner values are steerRatio: 15.401566348670535 tire_stiffness_factor = 0.385 # stiffnessFactor settled on 1.0081302973865127 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] ret.minSteerSpeed = 32 * CV.MPH_TO_MS elif candidate == CAR.ELANTRA_2021: ret.mass = (2800. * CV.LB_TO_KG) + STD_CARGO_KG ret.wheelbase = 2.72 ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.ELANTRA_HEV_2021: ret.mass = (3017. * CV.LB_TO_KG) + STD_CARGO_KG ret.wheelbase = 2.72 ret.steerRatio = 12.9 tire_stiffness_factor = 0.65 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.HYUNDAI_GENESIS: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 2060. + STD_CARGO_KG ret.wheelbase = 3.01 ret.steerRatio = 16.5 - ret.lateralTuning.init('indi') - ret.lateralTuning.indi.innerLoopGainBP = [0.] - ret.lateralTuning.indi.innerLoopGainV = [3.5] - ret.lateralTuning.indi.outerLoopGainBP = [0.] - ret.lateralTuning.indi.outerLoopGainV = [2.0] - ret.lateralTuning.indi.timeConstantBP = [0.] - ret.lateralTuning.indi.timeConstantV = [1.4] - ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] - ret.lateralTuning.indi.actuatorEffectivenessV = [2.3] ret.minSteerSpeed = 60 * CV.KPH_TO_MS elif candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): ret.mass = {CAR.KONA_EV: 1685., CAR.KONA_HEV: 1425., CAR.KONA_EV_2022: 1743.}.get(candidate, 1275.) + STD_CARGO_KG ret.wheelbase = 2.6 ret.steerRatio = 13.42 # Spec tire_stiffness_factor = 0.385 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): - ret.lateralTuning.pid.kf = 0.00006 + elif candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019, CAR.IONIQ_HEV_2022, CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV): ret.mass = 1490. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/ioniq-electric/specifications.aspx ret.wheelbase = 2.7 ret.steerRatio = 13.73 # Spec tire_stiffness_factor = 0.385 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - if candidate not in (CAR.IONIQ_EV_2020, CAR.IONIQ_PHEV, CAR.IONIQ_HEV_2022): + if candidate in (CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV_2019): ret.minSteerSpeed = 32 * CV.MPH_TO_MS - elif candidate == CAR.IONIQ_PHEV_2019: - ret.mass = 1550. + STD_CARGO_KG # weight per hyundai site https://www.hyundaiusa.com/us/en/vehicles/2019-ioniq-plug-in-hybrid/compare-specs - ret.wheelbase = 2.7 - ret.steerRatio = 13.73 - ret.lateralTuning.init('indi') - ret.lateralTuning.indi.innerLoopGainBP = [0.] - ret.lateralTuning.indi.innerLoopGainV = [2.5] - ret.lateralTuning.indi.outerLoopGainBP = [0.] - ret.lateralTuning.indi.outerLoopGainV = [3.5] - ret.lateralTuning.indi.timeConstantBP = [0.] - ret.lateralTuning.indi.timeConstantV = [1.4] - ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] - ret.lateralTuning.indi.actuatorEffectivenessV = [1.8] - ret.minSteerSpeed = 32 * CV.MPH_TO_MS elif candidate == CAR.VELOSTER: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 ret.steerRatio = 13.75 * 1.15 tire_stiffness_factor = 0.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] elif candidate == CAR.TUCSON: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3520. * CV.LB_TO_KG ret.wheelbase = 2.67 ret.steerRatio = 14.00 * 1.15 tire_stiffness_factor = 0.385 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - elif candidate == CAR.TUCSON_HYBRID_4TH_GEN: - ret.mass = 1680. + STD_CARGO_KG # average of all 3 trims + elif candidate in (CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN): + ret.mass = 1630. + STD_CARGO_KG # average ret.wheelbase = 2.756 ret.steerRatio = 16. tire_stiffness_factor = 0.385 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.SANTA_CRUZ_1ST_GEN: + ret.mass = 1870. + STD_CARGO_KG # weight from Limited trim - the only supported trim + ret.wheelbase = 3.000 + ret.steerRatio = 14.2 # steering ratio according to Hyundai News https://www.hyundainews.com/assets/documents/original/48035-2022SantaCruzProductGuideSpecsv2081521.pdf # Kia elif candidate == CAR.KIA_SORENTO: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 1985. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.1 # 10% higher at the center seems reasonable - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021): - ret.lateralTuning.pid.kf = 0.00006 - ret.mass = 1737. + STD_CARGO_KG + elif candidate in (CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, CAR.KIA_NIRO_HEV_2021, CAR.KIA_NIRO_HEV_2ND_GEN): + ret.mass = 3452. * CV.LB_TO_KG + STD_CARGO_KG # average of all the cars ret.wheelbase = 2.7 ret.steerRatio = 13.9 if CAR.KIA_NIRO_HEV_2021 else 13.73 # Spec tire_stiffness_factor = 0.385 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] if candidate == CAR.KIA_NIRO_PHEV: ret.minSteerSpeed = 32 * CV.MPH_TO_MS elif candidate == CAR.KIA_SELTOS: @@ -186,15 +148,10 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.63 ret.steerRatio = 14.56 tire_stiffness_factor = 1 - ret.lateralTuning.init('indi') - ret.lateralTuning.indi.innerLoopGainBP = [0.] - ret.lateralTuning.indi.innerLoopGainV = [4.] - ret.lateralTuning.indi.outerLoopGainBP = [0.] - ret.lateralTuning.indi.outerLoopGainV = [3.] - ret.lateralTuning.indi.timeConstantBP = [0.] - ret.lateralTuning.indi.timeConstantV = [1.4] - ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] - ret.lateralTuning.indi.actuatorEffectivenessV = [1.8] + elif candidate == CAR.KIA_SPORTAGE_5TH_GEN: + ret.mass = 1700. + STD_CARGO_KG # weight from SX and above trims, average of FWD and AWD versions + ret.wheelbase = 2.756 + ret.steerRatio = 13.6 # steering ratio according to Kia News https://www.kiamedia.com/us/en/models/sportage/2023/specifications elif candidate in (CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.KIA_OPTIMA_H): ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 @@ -202,100 +159,82 @@ class CarInterface(CarInterfaceBase): tire_stiffness_factor = 0.5 if candidate == CAR.KIA_OPTIMA_G4: ret.minSteerSpeed = 32 * CV.MPH_TO_MS - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - elif candidate == CAR.KIA_STINGER: - ret.lateralTuning.pid.kf = 0.00005 + elif candidate in (CAR.KIA_STINGER, CAR.KIA_STINGER_2022): ret.mass = 1825. + STD_CARGO_KG ret.wheelbase = 2.78 ret.steerRatio = 14.4 * 1.15 # 15% higher at the center seems reasonable - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] elif candidate == CAR.KIA_FORTE: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 3558. * CV.LB_TO_KG ret.wheelbase = 2.80 ret.steerRatio = 13.75 tire_stiffness_factor = 0.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] elif candidate == CAR.KIA_CEED: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 1450. + STD_CARGO_KG ret.wheelbase = 2.65 ret.steerRatio = 13.75 tire_stiffness_factor = 0.5 - ret.lateralTuning.pid.kf = 0.00005 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] - elif candidate == CAR.KIA_K5_2021: - ret.lateralTuning.pid.kf = 0.00005 + elif candidate in (CAR.KIA_K5_2021, CAR.KIA_K5_HEV_2020): ret.mass = 3228. * CV.LB_TO_KG ret.wheelbase = 2.85 ret.steerRatio = 13.27 # 2021 Kia K5 Steering Ratio (all trims) tire_stiffness_factor = 0.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.25], [0.05]] elif candidate == CAR.KIA_EV6: ret.mass = 2055 + STD_CARGO_KG ret.wheelbase = 2.9 ret.steerRatio = 16. tire_stiffness_factor = 0.65 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.IONIQ_5: ret.mass = 2012 + STD_CARGO_KG ret.wheelbase = 3.0 ret.steerRatio = 16. tire_stiffness_factor = 0.65 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + elif candidate == CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: + ret.mass = 1767. + STD_CARGO_KG # SX Prestige trim support only + ret.wheelbase = 2.756 + ret.steerRatio = 13.6 + elif candidate in (CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN): + ret.wheelbase = 2.81 + ret.steerRatio = 13.5 # average of the platforms + if candidate == CAR.KIA_SORENTO_4TH_GEN: + ret.mass = 3957 * CV.LB_TO_KG + STD_CARGO_KG + else: + ret.mass = 4537 * CV.LB_TO_KG + STD_CARGO_KG # Genesis + elif candidate == CAR.GENESIS_GV60_EV_1ST_GEN: + ret.mass = 2205 + STD_CARGO_KG + ret.wheelbase = 2.9 + ret.steerRatio = 12.6 # https://www.motor1.com/reviews/586376/2023-genesis-gv60-first-drive/#:~:text=Relative%20to%20the%20related%20Ioniq,5%2FEV6%27s%2014.3%3A1. elif candidate == CAR.GENESIS_G70: - ret.lateralTuning.init('indi') - ret.lateralTuning.indi.innerLoopGainBP = [0.] - ret.lateralTuning.indi.innerLoopGainV = [2.5] - ret.lateralTuning.indi.outerLoopGainBP = [0.] - ret.lateralTuning.indi.outerLoopGainV = [3.5] - ret.lateralTuning.indi.timeConstantBP = [0.] - ret.lateralTuning.indi.timeConstantV = [1.4] - ret.lateralTuning.indi.actuatorEffectivenessBP = [0.] - ret.lateralTuning.indi.actuatorEffectivenessV = [1.8] ret.steerActuatorDelay = 0.1 ret.mass = 1640.0 + STD_CARGO_KG ret.wheelbase = 2.84 ret.steerRatio = 13.56 elif candidate == CAR.GENESIS_G70_2020: - ret.lateralTuning.pid.kf = 0. - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.112], [0.004]] ret.mass = 3673.0 * CV.LB_TO_KG + STD_CARGO_KG ret.wheelbase = 2.83 ret.steerRatio = 12.9 + elif candidate == CAR.GENESIS_GV70_1ST_GEN: + ret.mass = 1950. + STD_CARGO_KG + ret.wheelbase = 2.87 + ret.steerRatio = 14.6 elif candidate == CAR.GENESIS_G80: - ret.lateralTuning.pid.kf = 0.00005 ret.mass = 2060. + STD_CARGO_KG ret.wheelbase = 3.01 ret.steerRatio = 16.5 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]] elif candidate == CAR.GENESIS_G90: ret.mass = 2200 ret.wheelbase = 3.15 ret.steerRatio = 12.069 - ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0.], [0.]] - ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.16], [0.01]] # *** longitudinal control *** if candidate in CANFD_CAR: ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] - ret.longitudinalActuatorDelayLowerBound = 0.15 - ret.longitudinalActuatorDelayUpperBound = 0.5 - ret.experimentalLongitudinalAvailable = bool(ret.flags & HyundaiFlags.CANFD_HDA2) + ret.experimentalLongitudinalAvailable = candidate in (HYBRID_CAR | EV_CAR) and candidate not in CANFD_RADAR_SCC_CAR else: ret.longitudinalTuning.kpV = [0.5] ret.longitudinalTuning.kiV = [0.0] - ret.longitudinalActuatorDelayLowerBound = 0.5 - ret.longitudinalActuatorDelayUpperBound = 0.5 ret.experimentalLongitudinalAvailable = candidate not in LEGACY_SAFETY_MODE_CAR ret.openpilotLongitudinalControl = experimental_long and ret.experimentalLongitudinalAvailable ret.pcmCruise = not ret.openpilotLongitudinalControl @@ -303,7 +242,15 @@ class CarInterface(CarInterfaceBase): ret.stoppingControl = True ret.startingState = True ret.vEgoStarting = 0.1 - ret.startAccel = 2.0 + ret.startAccel = 1.0 + ret.longitudinalActuatorDelayLowerBound = 0.5 + ret.longitudinalActuatorDelayUpperBound = 0.5 + + # *** feature detection *** + if candidate in CANFD_CAR: + ret.enableBsm = 0x1e5 in fingerprint[get_e_can_bus(ret)] + else: + ret.enableBsm = 0x58b in fingerprint[0] # *** panda safety config *** if candidate in CANFD_CAR: @@ -314,9 +261,9 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 if ret.flags & HyundaiFlags.CANFD_ALT_BUTTONS: ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_ALT_BUTTONS + if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC: + ret.safetyConfigs[1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC else: - ret.enableBsm = 0x58b in fingerprint[0] - if candidate in LEGACY_SAFETY_MODE_CAR: # these cars require a special panda safety mode due to missing counters and checksums in the messages ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundaiLegacy)] @@ -333,11 +280,11 @@ class CarInterface(CarInterfaceBase): elif candidate in EV_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_EV_GAS - ret.centerToFront = ret.wheelbase * 0.4 + if candidate in (CAR.KONA, CAR.KONA_EV, CAR.KONA_HEV, CAR.KONA_EV_2022): + ret.flags |= HyundaiFlags.ALT_LIMITS.value + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_ALT_LIMITS - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + ret.centerToFront = ret.wheelbase * 0.4 # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors @@ -347,12 +294,17 @@ class CarInterface(CarInterfaceBase): @staticmethod def init(CP, logcan, sendcan): - if CP.openpilotLongitudinalControl and CP.carFingerprint not in CAMERA_SCC_CAR: + if CP.openpilotLongitudinalControl and not (CP.flags & HyundaiFlags.CANFD_CAMERA_SCC.value) and \ + CP.carFingerprint not in CAMERA_SCC_CAR: addr, bus = 0x7d0, 0 if CP.flags & HyundaiFlags.CANFD_HDA2.value: addr, bus = 0x730, 5 disable_ecu(logcan, sendcan, bus=bus, addr=addr, com_cont_req=b'\x28\x83\x01') + # for blinkers + if CP.flags & HyundaiFlags.ENABLE_BLINKERS: + disable_ecu(logcan, sendcan, bus=5, addr=0x7B1, com_cont_req=b'\x28\x83\x01') + def _update(self, c): ret = self.CS.update(self.cp, self.cp_cam) @@ -370,9 +322,6 @@ class CarInterface(CarInterfaceBase): allow_enable = any(btn in ENABLE_BUTTONS for btn in self.CS.cruise_buttons) or any(self.CS.main_buttons) events = self.create_common_events(ret, pcm_enable=self.CS.CP.pcmCruise, allow_enable=allow_enable) - if self.CS.brake_error: - events.add(EventName.brakeUnavailable) - # low speed steer alert hysteresis logic (only for cars with steer cut off above 10 m/s) if ret.vEgo < (self.CP.minSteerSpeed + 2.) and self.CP.minSteerSpeed > 10.: self.low_speed_alert = True @@ -385,5 +334,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/hyundai/radar_interface.py b/selfdrive/car/hyundai/radar_interface.py index 0d22611fb5..4ecca542b5 100644 --- a/selfdrive/car/hyundai/radar_interface.py +++ b/selfdrive/car/hyundai/radar_interface.py @@ -37,7 +37,7 @@ class RadarInterface(RadarInterfaceBase): self.trigger_msg = RADAR_START_ADDR + RADAR_MSG_COUNT - 1 self.track_id = 0 - self.radar_off_can = CP.radarOffCan + self.radar_off_can = CP.radarUnavailable self.rcp = get_radar_can_parser(CP) def update(self, can_strings): diff --git a/selfdrive/loggerd/__init__.py b/selfdrive/car/hyundai/tests/__init__.py similarity index 100% rename from selfdrive/loggerd/__init__.py rename to selfdrive/car/hyundai/tests/__init__.py diff --git a/selfdrive/car/hyundai/tests/test_hyundai.py b/selfdrive/car/hyundai/tests/test_hyundai.py index a52027f448..6ecfa03796 100755 --- a/selfdrive/car/hyundai/tests/test_hyundai.py +++ b/selfdrive/car/hyundai/tests/test_hyundai.py @@ -2,24 +2,19 @@ import unittest from cereal import car -from selfdrive.car.car_helpers import get_interface_attr -from selfdrive.car.fw_versions import FW_QUERY_CONFIGS -from selfdrive.car.hyundai.values import CANFD_CAR +from selfdrive.car.hyundai.values import CANFD_CAR, FW_QUERY_CONFIG, FW_VERSIONS Ecu = car.CarParams.Ecu - ECU_NAME = {v: k for k, v in Ecu.schema.enumerants.items()} -VERSIONS = get_interface_attr("FW_VERSIONS", ignore_none=True) class TestHyundaiFingerprint(unittest.TestCase): def test_auxiliary_request_ecu_whitelist(self): # Asserts only auxiliary Ecus can exist in database for CAN-FD cars - config = FW_QUERY_CONFIGS['hyundai'] - whitelisted_ecus = {ecu for r in config.requests for ecu in r.whitelist_ecus if r.bus > 3} + whitelisted_ecus = {ecu for r in FW_QUERY_CONFIG.requests for ecu in r.whitelist_ecus if r.bus > 3} for car_model in CANFD_CAR: - ecus = {fw[0] for fw in VERSIONS['hyundai'][car_model].keys()} + ecus = {fw[0] for fw in FW_VERSIONS[car_model].keys()} ecus_not_in_whitelist = ecus - whitelisted_ecus ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_not_in_whitelist]) self.assertEqual(len(ecus_not_in_whitelist), 0, f'{car_model}: Car model has ECUs not in auxiliary request whitelists: {ecu_strings}') diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index f69273ba55..bb26d68329 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,12 +1,12 @@ -from enum import IntFlag from dataclasses import dataclass +from enum import Enum, IntFlag from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds from common.conversions import Conversions as CV from selfdrive.car import dbc_dict -from selfdrive.car.docs_definitions import CarInfo, Harness +from selfdrive.car.docs_definitions import CarFootnote, CarInfo, Column, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 Ecu = car.CarParams.Ecu @@ -23,6 +23,7 @@ class CarControllerParams: self.STEER_DRIVER_MULTIPLIER = 2 self.STEER_DRIVER_FACTOR = 1 self.STEER_THRESHOLD = 150 + self.STEER_STEP = 1 # 100 Hz if CP.carFingerprint in CANFD_CAR: self.STEER_MAX = 270 @@ -34,11 +35,17 @@ class CarControllerParams: # 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. - elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.HYUNDAI_GENESIS, CAR.ELANTRA_GT_I30, CAR.IONIQ, + elif CP.carFingerprint in (CAR.GENESIS_G80, CAR.GENESIS_G90, CAR.ELANTRA, CAR.IONIQ, CAR.IONIQ_EV_LTD, CAR.SANTA_FE_PHEV_2022, CAR.SONATA_LF, CAR.KIA_FORTE, CAR.KIA_NIRO_PHEV, - CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO, CAR.KIA_STINGER): + CAR.KIA_OPTIMA_H, CAR.KIA_SORENTO): self.STEER_MAX = 255 + # these cars have significantly more torque than most HKG; limit to 70% of max + elif CP.flags & HyundaiFlags.ALT_LIMITS: + self.STEER_MAX = 270 + self.STEER_DELTA_UP = 2 + self.STEER_DELTA_DOWN = 3 + # Default for most HKG else: self.STEER_MAX = 384 @@ -47,6 +54,14 @@ class CarControllerParams: class HyundaiFlags(IntFlag): CANFD_HDA2 = 1 CANFD_ALT_BUTTONS = 2 + CANFD_ALT_GEARS = 4 + CANFD_CAMERA_SCC = 8 + + ALT_LIMITS = 16 + ENABLE_BLINKERS = 32 + CANFD_ALT_GEARS_2 = 64 + SEND_LFA = 128 + USE_FCA = 256 class CAR: @@ -54,7 +69,6 @@ class CAR: ELANTRA = "HYUNDAI ELANTRA 2017" ELANTRA_2021 = "HYUNDAI ELANTRA 2021" ELANTRA_HEV_2021 = "HYUNDAI ELANTRA HYBRID 2021" - ELANTRA_GT_I30 = "HYUNDAI I30 N LINE 2019 & GT 2018 DCT" HYUNDAI_GENESIS = "HYUNDAI GENESIS 2015-2016" IONIQ = "HYUNDAI IONIQ HYBRID 2017-2019" IONIQ_HEV_2022 = "HYUNDAI IONIQ HYBRID 2020-2022" @@ -77,44 +91,70 @@ class CAR: VELOSTER = "HYUNDAI VELOSTER 2019" SONATA_HYBRID = "HYUNDAI SONATA HYBRID 2021" IONIQ_5 = "HYUNDAI IONIQ 5 2022" + TUCSON_4TH_GEN = "HYUNDAI TUCSON 4TH GEN" TUCSON_HYBRID_4TH_GEN = "HYUNDAI TUCSON HYBRID 4TH GEN" + SANTA_CRUZ_1ST_GEN = "HYUNDAI SANTA CRUZ 1ST GEN" # Kia KIA_FORTE = "KIA FORTE E 2018 & GT 2021" KIA_K5_2021 = "KIA K5 2021" + KIA_K5_HEV_2020 = "KIA K5 HYBRID 2020" KIA_NIRO_EV = "KIA NIRO EV 2020" KIA_NIRO_PHEV = "KIA NIRO HYBRID 2019" KIA_NIRO_HEV_2021 = "KIA NIRO HYBRID 2021" + KIA_NIRO_HEV_2ND_GEN = "KIA NIRO HYBRID 2ND GEN" KIA_OPTIMA_G4 = "KIA OPTIMA 4TH GEN" KIA_OPTIMA_G4_FL = "KIA OPTIMA 4TH GEN FACELIFT" KIA_OPTIMA_H = "KIA OPTIMA HYBRID 2017 & SPORTS 2019" KIA_SELTOS = "KIA SELTOS 2021" + KIA_SPORTAGE_5TH_GEN = "KIA SPORTAGE 5TH GEN" KIA_SORENTO = "KIA SORENTO GT LINE 2018" + KIA_SORENTO_4TH_GEN = "KIA SORENTO 4TH GEN" + KIA_SORENTO_PHEV_4TH_GEN = "KIA SORENTO PLUG-IN HYBRID 4TH GEN" + KIA_SPORTAGE_HYBRID_5TH_GEN = "KIA SPORTAGE HYBRID 5TH GEN" KIA_STINGER = "KIA STINGER GT2 2018" + KIA_STINGER_2022 = "KIA STINGER 2022" KIA_CEED = "KIA CEED INTRO ED 2019" KIA_EV6 = "KIA EV6 2022" # Genesis + GENESIS_GV60_EV_1ST_GEN = "GENESIS GV60 ELECTRIC 1ST GEN" GENESIS_G70 = "GENESIS G70 2018" GENESIS_G70_2020 = "GENESIS G70 2020" + GENESIS_GV70_1ST_GEN = "GENESIS GV70 1ST GEN" GENESIS_G80 = "GENESIS G80 2017" GENESIS_G90 = "GENESIS G90 2017" +class Footnote(Enum): + # footnotes which mention "red panda" will be replaced with the CAN FD panda kit on the shop page + CANFD = CarFootnote( + "Requires a red panda for this CAN FD car. " + + "All the hardware needed is sold in the CAN FD kit.", + Column.MODEL, shop_footnote=True) + + @dataclass class HyundaiCarInfo(CarInfo): package: str = "Smart Cruise Control (SCC)" + def init_make(self, CP: car.CarParams): + if CP.carFingerprint in CANFD_CAR: + self.footnotes.insert(0, Footnote.CANFD) + CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { - CAR.ELANTRA: HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), - CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-22", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), - CAR.ELANTRA_GT_I30: [ + CAR.ELANTRA: [ + HyundaiCarInfo("Hyundai Elantra 2017-19", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_b), HyundaiCarInfo("Hyundai Elantra GT 2017-19", harness=Harness.hyundai_e), - HyundaiCarInfo("Hyundai i30 2019", harness=Harness.hyundai_e), + HyundaiCarInfo("Hyundai i30 2017-19", harness=Harness.hyundai_e), + ], + CAR.ELANTRA_2021: HyundaiCarInfo("Hyundai Elantra 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), + CAR.ELANTRA_HEV_2021: HyundaiCarInfo("Hyundai Elantra Hybrid 2021-23", video_link="https://youtu.be/_EdYQtV52-c", harness=Harness.hyundai_k), + CAR.HYUNDAI_GENESIS: [ + HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages + HyundaiCarInfo("Genesis G80 2017", "All", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), ], - CAR.HYUNDAI_GENESIS: HyundaiCarInfo("Hyundai Genesis 2015-16", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_j), # TODO: check 2015 packages CAR.IONIQ: HyundaiCarInfo("Hyundai Ioniq Hybrid 2017-19", harness=Harness.hyundai_c), CAR.IONIQ_HEV_2022: HyundaiCarInfo("Hyundai Ioniq Hybrid 2020-22", harness=Harness.hyundai_h), # TODO: confirm 2020-21 harness CAR.IONIQ_EV_LTD: HyundaiCarInfo("Hyundai Ioniq Electric 2019", harness=Harness.hyundai_c), @@ -129,7 +169,7 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { CAR.SANTA_FE_2022: HyundaiCarInfo("Hyundai Santa Fe 2021-22", "All", "https://youtu.be/VnHzSTygTS4", harness=Harness.hyundai_l), CAR.SANTA_FE_HEV_2022: HyundaiCarInfo("Hyundai Santa Fe Hybrid 2022", "All", harness=Harness.hyundai_l), CAR.SANTA_FE_PHEV_2022: HyundaiCarInfo("Hyundai Santa Fe Plug-in Hybrid 2022", "All", harness=Harness.hyundai_l), - CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-22", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), + CAR.SONATA: HyundaiCarInfo("Hyundai Sonata 2020-23", "All", "https://www.youtube.com/watch?v=ix63r9kE3Fw", harness=Harness.hyundai_a), CAR.SONATA_LF: HyundaiCarInfo("Hyundai Sonata 2018-19", harness=Harness.hyundai_e), CAR.TUCSON: [ HyundaiCarInfo("Hyundai Tucson 2021", min_enable_speed=19 * CV.MPH_TO_MS, harness=Harness.hyundai_l), @@ -137,30 +177,41 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { ], CAR.PALISADE: [ HyundaiCarInfo("Hyundai Palisade 2020-22", "All", "https://youtu.be/TAnDqjF4fDY?t=456", harness=Harness.hyundai_h), - HyundaiCarInfo("Kia Telluride 2020", "All", harness=Harness.hyundai_h), + HyundaiCarInfo("Kia Telluride 2020-22", "All", harness=Harness.hyundai_h), ], CAR.VELOSTER: HyundaiCarInfo("Hyundai Veloster 2019-20", min_enable_speed=5. * CV.MPH_TO_MS, harness=Harness.hyundai_e), CAR.SONATA_HYBRID: HyundaiCarInfo("Hyundai Sonata Hybrid 2020-22", "All", harness=Harness.hyundai_a), CAR.IONIQ_5: [ - HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022" , "Highway Driving Assist", harness=Harness.hyundai_k), - HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_q), + HyundaiCarInfo("Hyundai Ioniq 5 (Southeast Asia only) 2022-23", "All", harness=Harness.hyundai_q), + HyundaiCarInfo("Hyundai Ioniq 5 (without HDA II) 2022-23", "Highway Driving Assist", harness=Harness.hyundai_k), + HyundaiCarInfo("Hyundai Ioniq 5 (with HDA II) 2022-23", "Highway Driving Assist II", harness=Harness.hyundai_q), + ], + CAR.TUCSON_4TH_GEN: [ + HyundaiCarInfo("Hyundai Tucson 2022", harness=Harness.hyundai_n), + HyundaiCarInfo("Hyundai Tucson 2023", "All", harness=Harness.hyundai_n), ], CAR.TUCSON_HYBRID_4TH_GEN: HyundaiCarInfo("Hyundai Tucson Hybrid 2022", "All", harness=Harness.hyundai_n), + CAR.SANTA_CRUZ_1ST_GEN: HyundaiCarInfo("Hyundai Santa Cruz 2022-23", harness=Harness.hyundai_n), # Kia CAR.KIA_FORTE: HyundaiCarInfo("Kia Forte 2019-21", harness=Harness.hyundai_g), CAR.KIA_K5_2021: HyundaiCarInfo("Kia K5 2021-22", harness=Harness.hyundai_a), + CAR.KIA_K5_HEV_2020: HyundaiCarInfo("Kia K5 Hybrid 2020", harness=Harness.hyundai_a), CAR.KIA_NIRO_EV: [ HyundaiCarInfo("Kia Niro EV 2019", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), HyundaiCarInfo("Kia Niro EV 2020", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_f), HyundaiCarInfo("Kia Niro EV 2021", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Niro EV 2022", "All", "https://www.youtube.com/watch?v=lT7zcG6ZpGo", harness=Harness.hyundai_h), ], - CAR.KIA_NIRO_PHEV: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), + CAR.KIA_NIRO_PHEV: [ + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, harness=Harness.hyundai_c), + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2020", "All", harness=Harness.hyundai_d), + ], CAR.KIA_NIRO_HEV_2021: [ HyundaiCarInfo("Kia Niro Hybrid 2021", harness=Harness.hyundai_f), # TODO: could be hyundai_d, verify HyundaiCarInfo("Kia Niro Hybrid 2022", harness=Harness.hyundai_h), ], + CAR.KIA_NIRO_HEV_2ND_GEN: HyundaiCarInfo("Kia Niro Hybrid 2023", harness=Harness.hyundai_a), CAR.KIA_OPTIMA_G4: HyundaiCarInfo("Kia Optima 2017", "Advanced Smart Cruise Control", harness=Harness.hyundai_b), # TODO: may support 2016, 2018 CAR.KIA_OPTIMA_G4_FL: HyundaiCarInfo("Kia Optima 2019-20", harness=Harness.hyundai_g), CAR.KIA_OPTIMA_H: [ @@ -168,21 +219,32 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Optima Hybrid 2019"), ], CAR.KIA_SELTOS: HyundaiCarInfo("Kia Seltos 2021", harness=Harness.hyundai_a), + CAR.KIA_SPORTAGE_5TH_GEN: HyundaiCarInfo("Kia Sportage 2023", harness=Harness.hyundai_n), CAR.KIA_SORENTO: [ HyundaiCarInfo("Kia Sorento 2018", "Advanced Smart Cruise Control", "https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_c), HyundaiCarInfo("Kia Sorento 2019", video_link="https://www.youtube.com/watch?v=Fkh3s6WHJz8", harness=Harness.hyundai_e), ], + CAR.KIA_SORENTO_4TH_GEN: HyundaiCarInfo("Kia Sorento 2021-23", harness=Harness.hyundai_k), + CAR.KIA_SORENTO_PHEV_4TH_GEN: HyundaiCarInfo("Kia Sorento Plug-in Hybrid 2022-23", harness=Harness.hyundai_a), + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: HyundaiCarInfo("Kia Sportage Hybrid 2023", harness=Harness.hyundai_n), CAR.KIA_STINGER: HyundaiCarInfo("Kia Stinger 2018-20", video_link="https://www.youtube.com/watch?v=MJ94qoofYw0", harness=Harness.hyundai_c), + CAR.KIA_STINGER_2022: HyundaiCarInfo("Kia Stinger 2022", "All", harness=Harness.hyundai_k), CAR.KIA_CEED: HyundaiCarInfo("Kia Ceed 2019", harness=Harness.hyundai_e), CAR.KIA_EV6: [ + HyundaiCarInfo("Kia EV6 (Southeast Asia only) 2022-23", "All", harness=Harness.hyundai_p), HyundaiCarInfo("Kia EV6 (without HDA II) 2022", "Highway Driving Assist", harness=Harness.hyundai_l), HyundaiCarInfo("Kia EV6 (with HDA II) 2022", "Highway Driving Assist II", harness=Harness.hyundai_p) ], # Genesis + CAR.GENESIS_GV60_EV_1ST_GEN: [ + HyundaiCarInfo("Genesis GV60 (Advanced Trim) 2023", "All", harness=Harness.hyundai_a), + HyundaiCarInfo("Genesis GV60 (Performance Trim) 2023", "All", harness=Harness.hyundai_k), + ], CAR.GENESIS_G70: HyundaiCarInfo("Genesis G70 2018-19", "All", harness=Harness.hyundai_f), CAR.GENESIS_G70_2020: HyundaiCarInfo("Genesis G70 2020", "All", harness=Harness.hyundai_f), - CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2017-19", "All", harness=Harness.hyundai_h), + CAR.GENESIS_GV70_1ST_GEN: HyundaiCarInfo("Genesis GV70 2022-23", "All", harness=Harness.hyundai_l), + CAR.GENESIS_G80: HyundaiCarInfo("Genesis G80 2018-19", "All", harness=Harness.hyundai_h), CAR.GENESIS_G90: HyundaiCarInfo("Genesis G90 2017-18", "All", harness=Harness.hyundai_c), } @@ -197,15 +259,6 @@ FINGERPRINTS = { CAR.ELANTRA: [{ 66: 8, 67: 8, 68: 8, 127: 8, 273: 8, 274: 8, 275: 8, 339: 8, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 897: 8, 832: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1314: 8, 1322: 8, 1345: 8, 1349: 8, 1351: 8, 1353: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1415: 8, 1419: 8, 1425: 2, 1427: 6, 1440: 8, 1456: 4, 1472: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1532: 5, 2001: 8, 2003: 8, 2004: 8, 2009: 8, 2012: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 }], - CAR.ELANTRA_GT_I30: [{ - 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 884: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1193: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1365: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8, 1952: 8, 1960: 8, 1988: 8, 2000: 8, 2001: 8, 2005: 8, 2008: 8, 2009: 8, 2013: 8, 2017: 8, 2025: 8 - }, - { - 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1415: 8, 1419: 8, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1530: 8 - }, - { - 66: 8, 67: 8, 68: 8, 127: 8, 128: 8, 129: 8, 273: 8, 274: 8, 275: 8, 339: 8, 354: 3, 356: 4, 399: 8, 512: 6, 544: 8, 593: 8, 608: 8, 688: 5, 790: 8, 809: 8, 832: 8, 897: 8, 899: 8, 902: 8, 903: 8, 905: 8, 909: 8, 916: 8, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1151: 6, 1168: 7, 1170: 8, 1265: 4, 1280: 1, 1282: 4, 1287: 4, 1290: 8, 1292: 8, 1294: 8, 1312: 8, 1322: 8, 1342: 6, 1345: 8, 1348: 8, 1349: 8, 1351: 8, 1353: 8, 1356: 8, 1363: 8, 1366: 8, 1367: 8, 1369: 8, 1407: 8, 1414: 3, 1419: 8, 1427: 6, 1440: 8, 1456: 4, 1470: 8, 1486: 8, 1487: 8, 1491: 8, 1960: 8, 1990: 8, 1998: 8, 2000: 8, 2001: 8, 2004: 8, 2005: 8, 2008: 8, 2009: 8, 2012: 8, 2013: 8, 2015: 8, 2016: 8, 2017: 8, 2024: 8, 2025: 8 - }], CAR.HYUNDAI_GENESIS: [{ 67: 8, 68: 8, 304: 8, 320: 8, 339: 8, 356: 4, 544: 7, 593: 8, 608: 8, 688: 5, 809: 8, 832: 8, 854: 7, 870: 7, 871: 8, 872: 5, 897: 8, 902: 8, 903: 6, 916: 8, 1024: 2, 1040: 8, 1056: 8, 1057: 8, 1078: 4, 1107: 5, 1136: 8, 1151: 6, 1168: 7, 1170: 8, 1173: 8, 1184: 8, 1265: 4, 1280: 1, 1287: 4, 1292: 8, 1312: 8, 1322: 8, 1331: 8, 1332: 8, 1333: 8, 1334: 8, 1335: 8, 1342: 6, 1345: 8, 1363: 8, 1369: 8, 1370: 8, 1371: 8, 1378: 4, 1384: 5, 1407: 8, 1419: 8, 1427: 6, 1434: 2, 1456: 4 }, @@ -282,14 +335,21 @@ FINGERPRINTS = { HYUNDAI_VERSION_REQUEST_LONG = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(0xf100) # Long description + +HYUNDAI_VERSION_REQUEST_ALT = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(0xf110) # Alt long description + HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ p16(uds.DATA_IDENTIFIER_TYPE.VEHICLE_MANUFACTURER_SPARE_PART_NUMBER) + \ p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \ p16(0xf100) + HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) FW_QUERY_CONFIG = FwQueryConfig( requests=[ + # TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD + # CAN queries (OBD-II port) Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], @@ -300,26 +360,58 @@ FW_QUERY_CONFIG = FwQueryConfig( [HYUNDAI_VERSION_RESPONSE], whitelist_ecus=[Ecu.engine, Ecu.transmission, Ecu.eps, Ecu.abs, Ecu.fwdRadar], ), - # CAN-FD queries + + # CAN-FD queries (from camera) + # TODO: combine shared whitelists with CAN requests Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdRadar], - bus=4, + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.cornerRadar, Ecu.hvac], + bus=0, + auxiliary=True, ), Request( [HYUNDAI_VERSION_REQUEST_LONG], [HYUNDAI_VERSION_RESPONSE], - whitelist_ecus=[Ecu.fwdCamera, Ecu.adas], - bus=5, + whitelist_ecus=[Ecu.fwdCamera, Ecu.adas, Ecu.cornerRadar, Ecu.hvac], + bus=1, + auxiliary=True, + obd_multiplexing=False, + ), + + # CAN-FD debugging queries + Request( + [HYUNDAI_VERSION_REQUEST_ALT], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=[Ecu.parking], + bus=0, + auxiliary=True, + ), + Request( + [HYUNDAI_VERSION_REQUEST_ALT], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=[Ecu.parking], + bus=1, + auxiliary=True, + obd_multiplexing=False, ), ], extra_ecus=[ - (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms + (Ecu.adas, 0x730, None), # ADAS Driving ECU on HDA2 platforms + (Ecu.parking, 0x7b1, None), # ADAS Parking ECU (may exist on all platforms) + (Ecu.hvac, 0x7b3, None), # HVAC Control Assembly + (Ecu.cornerRadar, 0x7b7, None), ], ) FW_VERSIONS = { + CAR.HYUNDAI_GENESIS: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS 1.1 -150210', + b'\xf1\x00DH LKAS 1.4 -140110', + b'\xf1\x00DH LKAS 1.5 -140425', + ], + }, CAR.IONIQ: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC H-CUP 1.01 1.01 96400-G2000 ', @@ -357,29 +449,32 @@ FW_VERSIONS = { }, CAR.IONIQ_PHEV: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\000AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.02 99110-G2100 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2200 ', b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC F-CUP 1.00 1.02 99110-G2100 ', ], (Ecu.eps, 0x7d4, None): [ - b'\xf1\000AE MDPS C 1.00 1.01 56310/G2510 4APHC101', + b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2510 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310/G2560 4APHC101', b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', ], (Ecu.fwdCamera, 0x7c4, None): [ - b'\xf1\000AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', + b'\xf1\x00AEP MFC AT USA LHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT EUR RHD 1.00 1.01 95740-G2600 190819', b'\xf1\x00AEP MFC AT USA LHD 1.00 1.00 95740-G2700 201027', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\000\000\xf1\0006U3H1_C2\000\0006U3J9051\000\000PAE0G16NL0\x82zT\xd2', b'\xf1\x816U3J8051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J8051\x00\x00PAETG16UL0\x00\x00\x00\x00', - b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL0\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PAE0G16NL2\xad\xeb\xabt', ], }, CAR.IONIQ_EV_2020: { @@ -407,16 +502,19 @@ FW_VERSIONS = { b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7501 4AEEC104', b'\xf1\x00AE MDPS C 1.00 1.03 56310/G7300 4AEEC103', b'\xf1\x00AE MDPS C 1.00 1.03 56310G7300\x00 4AEEC103', + b'\xf1\x00AE MDPS C 1.00 1.04 56310/G7301 4AEEC104', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G7200 160418', b'\xf1\x00AEE MFC AT USA LHD 1.00 1.00 95740-G2400 180222', b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2300 170703', + b'\xf1\x00AEE MFC AT EUR LHD 1.00 1.00 95740-G2400 180222', ], }, CAR.IONIQ_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00AEhe SCC F-CUP 1.00 1.00 99110-G2600 ', + b'\xf1\x00AEhe SCC FHCUP 1.00 1.00 99110-G2600 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00AE MDPS C 1.00 1.01 56310G2510\x00 4APHC101', @@ -429,6 +527,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x00\x00\x00\x00', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00HAE0G16NL2\x96\xda\xd4\xee', ], }, CAR.SONATA: { @@ -442,7 +541,6 @@ FW_VERSIONS = { b'\xf1\x00DN8_ SCC FHCUP 1.00 1.01 99110-L1000 ', b'\xf1\x00DN89110-L0000 \xaa\xaa\xaa\xaa\xaa\xaa\xaa ', b'\xf1\x8799110L0000\xf1\x00DN8_ SCC F-CUP 1.00 1.00 99110-L0000 ', - b'\xf1\x8799110L0000\xf1\x00DN8_ SCC FHCUP 1.00 1.00 99110-L0000 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00DN ESC \x07 106 \x07\x01 58910-L0100', @@ -455,7 +553,6 @@ FW_VERSIONS = { b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 104\x19\x08\x01 58910-L0100', b'\xf1\x8758910-L0100\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', b'\xf1\x8758910-L0100\xf1\x00DN ESC \x07 104\x19\x08\x01 58910-L0100', - b'\xf1\x8758910-L0300\xf1\x00DN ESC \x03 100 \x08\x01 58910-L0300', b'\xf1\x00DN ESC \x06 106 \x07\x01 58910-L0100', ], (Ecu.engine, 0x7e0, None): [ @@ -465,6 +562,7 @@ FW_VERSIONS = { b'\xf1\x82DNBWN5TMDCXXXG2E', b'\xf1\x82DNCVN5GMCCXXXF0A', b'\xf1\x82DNCVN5GMCCXXXG2B', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82DNDWN5TMDCXXXJ1A', b'\xf1\x87391162M003', b'\xf1\x87391162M013', @@ -473,6 +571,8 @@ FW_VERSIONS = { b'HM6M1_0a0_G20', b'HM6M2_0a0_BD0', b'\xf1\x8739110-2S278\xf1\x82DNDVD5GMCCXXXL5B', + b'\xf1\x8739110-2S041\xf1\x81HM6M1_0a0_M00', + b'\xf1\x81HM6M1_0a0_G20', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00DN8 MDPS C 1,00 1,01 56310L0010\x00 4DNAC101', # modified firmware @@ -489,6 +589,8 @@ FW_VERSIONS = { b'\xf1\x8756310L0010\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0010\x00 4DNAC101', b'\xf1\x8756310L0210\x00\xf1\x00DN8 MDPS C 1.00 1.01 56310L0210\x00 4DNAC101', b'\xf1\x8757700-L0000\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP100', + b'\xf1\x00DN8 MDPS R 1.00 1.00 57700-L0000 4DNAP101', + b'\xf1\x00DN8 MDPS C 1.00 1.01 56310-L0210 4DNAC102', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00DN8 MFC AT KOR LHD 1.00 1.02 99211-L1000 190422', @@ -498,6 +600,7 @@ FW_VERSIONS = { b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.03 99211-L0000 210603', b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.05 99211-L1000 201109', b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.06 99211-L1000 210325', + b'\xf1\x00DN8 MFC AT USA LHD 1.00 1.07 99211-L1000 211223', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', @@ -508,7 +611,10 @@ FW_VERSIONS = { b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x00HT6WA250BLHT6WA910A1SDN8G25NB1\x00\x00\x00\x00\x00\x00\x96\xa1\xf1\x92', b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB2\x00\x00\x00\x00\x00\x00\x08\xc9O:', + b'\xf1\x00HT6WA280BLHT6WAD10A1SDN8G25NB4\x00\x00\x00\x00\x00\x00g!l[', b'\xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', + b'\xf1\x00T02601BL T02832A1 VDN8T25XXX832NS8G\x0e\xfeE', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSCF\xe4!Y', b'\xf1\x87954A02N060\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 VDN8T25XXX730NS5\xf7_\x92\xf5', b'\xf1\x87SAKFBA2926554GJ2VefVww\x87xwwwww\x88\x87xww\x87wTo\xfb\xffvUo\xff\x8d\x16\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', b'\xf1\x87SAKFBA3030524GJ2UVugww\x97yx\x88\x87\x88vw\x87gww\x87wto\xf9\xfffUo\xff\xa2\x0c\xf1\x81U903\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00SDN8T16NB0z{\xd4v', @@ -558,6 +664,7 @@ FW_VERSIONS = { b'\xf1\x87SALFBA7460044GJ2gx\x87\x88Vf\x86hx\x88\x87\x88wwwwgw\x86wd?\xfa\xff\x86U_\xff\xaf\x1f\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SAMFBA8105254GJ2wx\x87\x88Vf\x86hx\x88\x87\x88wwwwwwww\x86O\xfa\xff\x99\x88\x7f\xffZG\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', b'\xf1\x87SANFB45889451GC7wx\x87\x88gw\x87x\x88\x88x\x88\x87wxw\x87wxw\x87\x8f\xfc\xffeU\x8f\xff+Q\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00SDN8T16NB2\n\xdd^\xbc', + b'\xf1\x00T02601BL T02900A1 VDN8T25XXX900NSA\xb9\x13\xf9p', ], }, CAR.SONATA_LF: { @@ -607,14 +714,16 @@ FW_VERSIONS = { }, CAR.SANTA_FE: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1210 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.01 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.02 99110-S2000 ', b'\xf1\x00TM__ SCC F-CUP 1.00 1.03 99110-S2000 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00TM ESC \r 100\x18\x031 58910-S2650', + b'\xf1\x00TM ESC \r 105\x19\x05# 58910-S1500', b'\xf1\x00TM ESC \r 103\x18\x11\x08 58910-S2650', - b'\xf1\x00TM ESC \r 104\x19\a\b 58910-S2650', + b'\xf1\x00TM ESC \r 104\x19\x07\x08 58910-S2650', b'\xf1\x00TM ESC \x02 100\x18\x030 58910-S2600', b'\xf1\x00TM ESC \x02 102\x18\x07\x01 58910-S2600', b'\xf1\x00TM ESC \x02 103\x18\x11\x07 58910-S2600', @@ -631,11 +740,15 @@ FW_VERSIONS = { b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8409', b'\xf1\x00TM MDPS C 1.00 1.00 56340-S2000 8A12', b'\xf1\x00TM MDPS C 1.00 1.01 56340-S2000 9129', + b'\xf1\x00TM MDPS R 1.00 1.02 57700-S1100 4TMDP102' ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TM MFC AT EUR LHD 1.00 1.01 99211-S1010 181207', b'\xf1\x00TM MFC AT USA LHD 1.00 1.00 99211-S2000 180409', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 U833\x00\x00\x00\x00\x00\x00TTM4V22US3_<]\xf1', + b'\xf1\x006W351_C2\x00\x006W3E1051\x00\x00TTM4T20NS5\x00\x00\x00\x00', b'\xf1\x87LBJSGA7082574HG0\x87www\x98\x88\x88\x88\x99\xaa\xb9\x9afw\x86gx\x99\xa7\x89co\xf8\xffvU_\xffR\xaf\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\xa6\xe0\x91', b'\xf1\x87LBKSGA0458404HG0vfvg\x87www\x89\x99\xa8\x99y\xaa\xa7\x9ax\x88\xa7\x88t_\xf9\xff\x86w\x8f\xff\x15x\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM2T20NS1\x00\x00\x00\x00', b'\xf1\x87LDJUEA6010814HG1\x87w\x87x\x86gvw\x88\x88\x98\x88gw\x86wx\x88\x97\x88\x85o\xf8\xff\x86f_\xff\xd37\xf1\x816W3C2051\x00\x00\xf1\x006W351_C2\x00\x006W3C2051\x00\x00TTM4T20NS0\xf8\x19\x92g', @@ -666,7 +779,6 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ', b'\xf1\x8799110S1500\xf1\x00TM__ SCC F-CUP 1.00 1.00 99110-S1500 ', - b'\xf1\x8799110S1500\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ', b'\xf1\x00TM__ SCC FHCUP 1.00 1.00 99110-S1500 ', ], (Ecu.abs, 0x7d1, None): [ @@ -677,12 +789,14 @@ FW_VERSIONS = { b'\xf1\x8758910-S1DA0\xf1\x00TM ESC \x1e 102 \x08\x08 58910-S1DA0', b'\xf1\x8758910-S2GA0\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', b'\xf1\x00TM ESC \x04 102!\x04\x05 58910-S2GA0', + b'\xf1\x00TM ESC \x04 101 \x08\x04 58910-S2GA0', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x82TACVN5GMI3XXXH0A', b'\xf1\x82TMBZN5TMD3XXXG2E', b'\xf1\x82TACVN5GSI3XXXH0A', b'\xf1\x82TMCFD5MMCXXXXG0A', + b'\xf1\x81HM6M1_0a0_G20', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x82TMDWN5TMD3TXXJ1A', b'\xf1\x81HM6M2_0a0_G00', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M1_0a0_J10', @@ -698,6 +812,7 @@ FW_VERSIONS = { b'\xf1\x00TMA MFC AT USA LHD 1.00 1.01 99211-S2500 210205', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NSA\xf3\xf4Uj', b'\xf1\x87SDMXCA9087684GN1VfvgUUeVwwgwwwwwffffU?\xfb\xff\x97\x88\x7f\xff+\xa4\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', b'\xf1\x00T02601BL T02730A1 VTMPT25XXX730NS2\xa6\x06\x88\xf7', b'\xf1\x87SDMXCA8653204GN1EVugEUuWwwwwww\x87wwwwwv/\xfb\xff\xa8\x88\x9f\xff\xa5\x9c\xf1\x89HT6WAD00A1\xf1\x82STM4G25NH1\x00\x00\x00\x00\x00\x00', @@ -707,23 +822,28 @@ FW_VERSIONS = { b'\xf1\x00HT6TA290BLHT6TAF00A1STM0M25GS1\x00\x00\x00\x00\x00\x006\xd8\x97\x15', b'\xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', b'\xf1\x87954A02N250\x00\x00\x00\x00\x00\xf1\x81T02900A1 \xf1\x00T02601BL T02900A1 VTMPT25XXX900NS8\xb7\xaa\xfe\xfc', + b'\xf1\x00T02601BL T02800A1 VTMPT25XXX800NS4\xed\xaf\xed\xf5', ], }, CAR.SANTA_FE_HEV_2022: { (Ecu.fwdRadar, 0x7d0, None): [ - b'\xf1\x8799110CL500\xf1\x00TMhe SCC FHCUP 1.00 1.00 99110-CL500 ', + b'\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', + b'\xf1\x00TM MDPS R 1.00 1.05 57700-CL000 4TSHP105', ], (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00TMH MFC AT EUR LHD 1.00 1.06 99211-S1500 220727', b'\xf1\x00TMH MFC AT USA LHD 1.00 1.03 99211-S1500 210224', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00PSBG2333 E16\x00\x00\x00\x00\x00\x00\x00TTM2H16UA3I\x94\xac\x8f', b'\xf1\x87959102T250\x00\x00\x00\x00\x00\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00PSBG2333 E14\x00\x00\x00\x00\x00\x00\x00TTM2H16SA2\x80\xd7l\xb2', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x87391312MTC1', + b'\xf1\x87391312MTE0', ], }, CAR.SANTA_FE_PHEV_2022: { @@ -750,6 +870,7 @@ FW_VERSIONS = { b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.03 96400-J5100 ', b'\xf1\x00CK__ SCC F_CUP 1.00 1.01 96400-J5000 ', + b'\xf1\x00CK__ SCC F_CUP 1.00 1.02 96400-J5100 ', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81606DE051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -757,6 +878,7 @@ FW_VERSIONS = { b'\xf1\x82CKJN3TMSDE0B\x00\x00\x00\x00', b'\xf1\x82CKKN3TMD_H0A\x00\x00\x00\x00', b'\xe0\x19\xff\xe7\xe7g\x01\xa2\x00\x0f\x00\x9e\x00\x06\x00\xff\xff\xff\xff\xff\xff\x00\x00\xff\xff\xff\xff\xff\xff\x00\x00\x0f\x0e\x0f\x0f\x0e\r\x00\x00\x7f\x02.\xff\x00\x00~p\x00\x00\x00\x00u\xff\xf9\xff\x00\x00\x00\x00V\t\xd5\x01\xc0\x00\x00\x00\x007\xfb\xfc\x0b\x8d\x00', + b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5200 4C2CL104', @@ -764,6 +886,7 @@ FW_VERSIONS = { b'\xf1\x00CK MDPS R 1.00 1.04 57700-J5420 4C4VL104', b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5420 4C4VL106', b'\xf1\x00CK MDPS R 1.00 1.07 57700-J5220 4C2VL107', + b'\xf1\x00CK MDPS R 1.00 1.06 57700-J5220 4C2VL106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CK MFC AT USA LHD 1.00 1.03 95740-J5000 170822', @@ -771,6 +894,7 @@ FW_VERSIONS = { b'\xf1\x00CK MFC AT EUR LHD 1.00 1.03 95740-J5000 170822', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E25\x00\x00\x00\x00\x00\x00\x00SCK0T33NB2\xb3\xee\xba\xdc', b'\xf1\x87VCJLE17622572DK0vd6D\x99\x98y\x97vwVffUfvfC%CuT&Dx\x87o\xff{\x1c\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x89E21\x00\x00\x00\x00\x00\x00\x00\xf1\x82SCK0T33NB0', @@ -781,10 +905,29 @@ FW_VERSIONS = { b'\xf1\x87VDHLG17274082DK2wfFf\x89x\x98wUT5T\x88v\x97xgeGefTGTVvO\xff\x1c\x14\xf1\x81E19\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E19\x00\x00\x00\x00\x00\x00\x00SCK0T33UB2\xee[\x97S', b'\xf1\x87VDHLG17000192DK2xdFffT\xa5VUD$DwT\x86wveVeeD&T\x99\xba\x8f\xff\xcc\x99\xf1\x81E21\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\t\xb7\x17\xf5', + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SCK0T33NB0\x88\xa2\xe6\xf0', + ], + }, + CAR.KIA_STINGER_2022: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00CK__ SCC F-CUP 1.00 1.00 99110-J5500 ', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640R0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00CK MDPS R 1.00 5.03 57700-J5380 4C2VR503', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00CK MFC AT AUS RHD 1.00 1.00 99211-J5500 210622', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x87VCNLF11383972DK1vffV\x99\x99\x89\x98\x86eUU\x88wg\x89vfff\x97fff\x99\x87o\xff"\xc1\xf1\x81E30\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 E30\x00\x00\x00\x00\x00\x00\x00SCK0T33GH0\xbe`\xfb\xc6', ], }, CAR.PALISADE: { (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00LX2_ SCC F-CUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC F-CUP 1.00 1.05 99110-S8100 ', b'\xf1\x00LX2 SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCU- 1.00 1.05 99110-S8100 ', @@ -792,18 +935,21 @@ FW_VERSIONS = { b'\xf1\x00LX2_ SCC FHCUP 1.00 1.04 99110-S8100 ', b'\xf1\x00LX2_ SCC FHCUP 1.00 1.05 99110-S8100 ', b'\xf1\x00ON__ FCA FHCUP 1.00 1.02 99110-S9100 ', + b'\xf1\x00ON__ FCA FHCUP 1.00 1.01 99110-S9110 ', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00LX ESC \x01 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x01 1031\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 101\x19\x03\x17 58910-S8330', b'\xf1\x00LX ESC \x0b 102\x19\x05\x07 58910-S8330', + b'\xf1\x00LX ESC \x0b 103\x19\t\t 58910-S8350', b'\xf1\x00LX ESC \x0b 103\x19\t\x07 58910-S8330', b'\xf1\x00LX ESC \x0b 103\x19\t\x10 58910-S8360', b'\xf1\x00LX ESC \x0b 104 \x10\x16 58910-S8360', b'\xf1\x00ON ESC \x0b 100\x18\x12\x18 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x08 58910-S9360', b'\xf1\x00ON ESC \x0b 101\x19\t\x05 58910-S9320', + b'\xf1\x00ON ESC \x01 101\x19\t\x08 58910-S9360', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x81640J0051\x00\x00\x00\x00\x00\x00\x00\x00', @@ -812,6 +958,7 @@ FW_VERSIONS = { ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00LX2 MDPS C 1,00 1,03 56310-S8020 4LXDC103', + b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8000 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.03 56310-S8020 4LXDC103', b'\xf1\x00LX2 MDPS C 1.00 1.04 56310-S8020 4LXDC104', b'\xf1\x00ON MDPS C 1.00 1.00 56340-S9000 8B13', @@ -825,10 +972,13 @@ FW_VERSIONS = { b'\xf1\x00ON MFC AT USA LHD 1.00 1.01 99211-S9100 181105', b'\xf1\x00ON MFC AT USA LHD 1.00 1.03 99211-S9100 200720', b'\xf1\x00LX2 MFC AT USA LHD 1.00 1.00 99211-S8110 210226', + b'\xf1\x00ON MFC AT USA LHD 1.00 1.04 99211-S9100 211227', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00bcsh8p54 U872\x00\x00\x00\x00\x00\x00TON4G38NB1\x96z28', + b'\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX4G38NB3X\xa8\xc08', b'\xf1\x00bcsh8p54 U903\x00\x00\x00\x00\x00\x00TON4G38NB2[v\\\xb6', + b'\xf1\x00bcsh8p54 U922\x00\x00\x00\x00\x00\x00TON2G38NB5j\x94.\xde', b'\xf1\x87LBLUFN591307KF25vgvw\x97wwwy\x99\xa7\x99\x99\xaa\xa9\x9af\x88\x96h\x95o\xf7\xff\x99f/\xff\xe4c\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB2\xd7\xc1/\xd1', b'\xf1\x87LBLUFN650868KF36\xa9\x98\x89\x88\xa8\x88\x88\x88h\x99\xa6\x89fw\x86gw\x88\x97x\xaa\x7f\xf6\xff\xbb\xbb\x8f\xff+\x82\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', b'\xf1\x87LBLUFN655162KF36\x98\x88\x88\x88\x98\x88\x88\x88x\x99\xa7\x89x\x99\xa7\x89x\x99\x97\x89g\x7f\xf7\xffwU_\xff\xe9!\xf1\x81U891\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U891\x00\x00\x00\x00\x00\x00SLX2G38NB3\xd1\xc3\xf8\xa8', @@ -936,6 +1086,25 @@ FW_VERSIONS = { b'\xf1\x81640H0051\x00\x00\x00\x00\x00\x00\x00\x00', ], }, + CAR.GENESIS_G80: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00DH__ SCC F-CUP 1.00 1.01 96400-B1120 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.03 95895-B1500 180713', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.02 95895-B1500 170810', + b'\xf1\x00DH LKAS AT USA LHD 1.01 1.01 95895-B1500 161014', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x00bcsh8p54 E21\x00\x00\x00\x00\x00\x00\x00SDH0T33NH4\xd7O\x9e\xc9', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00TDH0G38NH3:-\xa9n', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0G38NH2j\x9dA\x1c', + b'\xf1\x00bcsh8p54 E18\x00\x00\x00\x00\x00\x00\x00SDH0T33NH3\x97\xe6\xbc\xb8', + ], + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00', + ], + }, CAR.GENESIS_G90: { (Ecu.transmission, 0x7e1, None): [b'\xf1\x87VDGMD15866192DD3x\x88x\x89wuFvvfUf\x88vWwgwwwvfVgx\x87o\xff\xbc^\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7'], (Ecu.fwdRadar, 0x7d0, None): [b'\xf1\x00HI__ SCC F-CUP 1.00 1.01 96400-D2100 '], @@ -991,11 +1160,13 @@ FW_VERSIONS = { b'\xf1\x8799110L2000\xf1\000DL3_ SCC FHCUP 1.00 1.03 99110-L2000 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', b'\xf1\x8799110L2100\xf1\x00DL3_ SCC FHCUP 1.00 1.03 99110-L2100 ', + b'\xf1\x00DL3_ SCC F-CUP 1.00 1.03 99110-L2100 ', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x8756310-L3110\xf1\000DL3 MDPS C 1.00 1.01 56310-L3110 4DLAC101', b'\xf1\x8756310-L3220\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', b'\xf1\x8757700-L3000\xf1\x00DL3 MDPS R 1.00 1.02 57700-L3000 4DLAP102', + b'\xf1\x00DL3 MDPS C 1.00 1.01 56310-L3220 4DLAC101', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DL3 MFC AT USA LHD 1.00 1.03 99210-L3000 200915', @@ -1006,6 +1177,7 @@ FW_VERSIONS = { b'\xf1\x8758910-L3200\xf1\000DL ESC \006 101 \004\002 58910-L3200', b'\xf1\x8758910-L3800\xf1\x00DL ESC \t 101 \x07\x02 58910-L3800', b'\xf1\x8758910-L3600\xf1\x00DL ESC \x03 100 \x08\x02 58910-L3600', + b'\xf1\x00DL ESC \t 100 \x06\x02 58910-L3800', ], (Ecu.engine, 0x7E0, None): [ b'\xf1\x87391212MKT0', @@ -1018,6 +1190,24 @@ FW_VERSIONS = { b'\xf1\x87SALFEA6046104GK2wvwgeTeFg\x88\x96xwwwwffvfe?\xfd\xff\x86fo\xff\x97A\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL2T16NB1ia\x0b\xb8', b'\xf1\x87SCMSAA8572454GK1\x87x\x87\x88Vf\x86hgwvwvwwgvwwgT?\xfb\xff\x97fo\xffH\xb8\xf1\x81U913\x00\x00\x00\x00\x00\x00\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', b'\xf1\x87954A02N300\x00\x00\x00\x00\x00\xf1\x81T02730A1 \xf1\x00T02601BL T02730A1 WDL3T25XXX730NS2b\x1f\xb8%', + b'\xf1\x00bcsh8p54 U913\x00\x00\x00\x00\x00\x00TDL4T16NB05\x94t\x18', + ], + }, + CAR.KIA_K5_HEV_2020: { + (Ecu.fwdRadar, 0x7D0, None): [ + b'\xf1\x00DLhe SCC FHCUP 1.00 1.02 99110-L7000 ', + ], + (Ecu.eps, 0x7D4, None): [ + b'\xf1\x00DL3 MDPS C 1.00 1.02 56310-L7000 4DLHC102', + ], + (Ecu.fwdCamera, 0x7C4, None): [ + b'\xf1\x00DL3HMFC AT KOR LHD 1.00 1.02 99210-L2000 200309', + ], + (Ecu.engine, 0x7E0, None): [ + b'\xf1\x87391162JLA0', + ], + (Ecu.transmission, 0x7E1, None): [ + b'\xf1\x00PSBG2323 E08\x00\x00\x00\x00\x00\x00\x00TDL2H20KA2\xe3\xc6cz', ], }, CAR.KONA_EV: { @@ -1052,6 +1242,7 @@ FW_VERSIONS = { b'\xf1\x8758520-K4010\xf1\x00OS IEB \x02 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x04 101 \x11\x13 58520-K4010', b'\xf1\x8758520-K4010\xf1\x00OS IEB \x03 101 \x11\x13 58520-K4010', + b'\xf1\x00OS IEB \r 102"\x05\x16 58520-K4010', # TODO: these return from the MULTI request, above return from LONG b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xd3\x00\x00\x00\x00\xff\xb7\xff\xee\xff\xe0\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xffP\xf5\xff\xfd\x00\x00\x00\x00\xfc\x00\x01', b'\x01\x04\x7f\xff\xff\xf8\xff\xff\x00\x00\x01\xdb\x00\x00\x00\x00\xff\xb1\xff\xd9\xff\xd2\x00\xc0\xc0\xfc\xd5\xfc\x00\x00U\x10\xff\xd6\xf5\x00\x06\x00\x00\x00\x14\xfd\x00\x04', @@ -1061,10 +1252,12 @@ FW_VERSIONS = { b'\xf1\x00OSP LKA AT CND LHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT EUR RHD 1.00 1.02 99211-J9110 802', b'\xf1\x00OSP LKA AT AUS RHD 1.00 1.04 99211-J9200 904', + b'\xf1\x00OSP LKA AT EUR LHD 1.00 1.04 99211-J9200 904', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00OSP MDPS C 1.00 1.02 56310K4260\x00 4OEPC102', b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4970 4OEPC102', + b'\xf1\x00OSP MDPS C 1.00 1.02 56310/K4271 4OEPC102', ], (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00YB__ FCA ----- 1.00 1.01 99110-K4500 \x00\x00\x00', @@ -1103,22 +1296,27 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x816H6F4051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6D1051\x00\x00\x00\x00\x00\x00\x00\x00', + b'\xf1\x816H6F6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b"\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\xf4'\\\x91", b'\xf1\x816U3J2051\x00\x00\xf1\x006U3H0_C2\x00\x006U3J2051\x00\x00PDE0G16NS2\x00\x00\x00\x00', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x00\x00\x00\x00', b'\xf1\x816U3H3051\x00\x00\xf1\x006U3H0_C2\x00\x006U3H3051\x00\x00PDE0G16NS1\x13\xcd\x88\x92', + b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL2&[\xc3\x01', ], (Ecu.eps, 0x7D4, None): [ b'\xf1\x00DE MDPS C 1.00 1.09 56310G5301\x00 4DEHC109', + b'\xf1\x00DE MDPS C 1.00 1.01 56310G5520\x00 4DEPC101', ], (Ecu.fwdCamera, 0x7C4, None): [ b'\xf1\x00DEP MFC AT USA LHD 1.00 1.01 95740-G5010 170424', b'\xf1\x00DEP MFC AT USA LHD 1.00 1.00 95740-G5010 170117', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.05 99211-G5000 190826', ], (Ecu.fwdRadar, 0x7D0, None): [ b'\xf1\x00DEhe SCC H-CUP 1.01 1.02 96400-G5100 ', + b'\xf1\x00DEhe SCC F-CUP 1.00 1.02 99110-G5100 ', ], }, CAR.KIA_NIRO_HEV_2021: { @@ -1204,29 +1402,54 @@ FW_VERSIONS = { b'\xf1\x87\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xf1\x816T6B8051\x00\x00\xf1\x006T6H0_C2\x00\x006T6B8051\x00\x00TJFSG24NH27\xa7\xc2\xb4', ], }, + CAR.ELANTRA: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', + b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', + b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', + ], + (Ecu.eps, 0x7d4, None): [ + b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', + b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', + ], + (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00PD ESC \x0b 104\x18\t\x03 58920-G3350', + b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00PD__ SCC F-CUP 1.00 1.00 96400-G3300 ', + b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', + ], + }, CAR.ELANTRA_2021: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', b'\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', + b'\xf1\x00CN7_ SCC FNCUP 1.00 1.01 99110-AA000 ', b'\xf1\x8799110AA000\xf1\x00CN7_ SCC FHCUP 1.00 1.01 99110-AA000 ', b'\xf1\x8799110AA000\xf1\x00CN7_ SCC F-CUP 1.00 1.01 99110-AA000 ', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x87\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x00CN7 MDPS C 1.00 1.06 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 4CNDC106', b'\xf1\x8756310/AA070\xf1\x00CN7 MDPS C 1.00 1.06 56310/AA070 4CNDC106', - b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', + b'\xf1\x8756310AA050\x00\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106\xf1\xa01.06', + b'\xf1\x00CN7 MDPS C 1.00 1.06 56310AA050\x00 4CNDC106', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.00 99210-AB000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AA000 200819', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.01 99210-AB000 210205', b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.06 99210-AA000 220111', + b'\xf1\x00CN7 MFC AT USA LHD 1.00 1.03 99210-AB000 220426', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 104 \x08\x03 58910-AA800', - b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800', b'\xf1\x8758910-AA800\xf1\x00CN ESC \t 105 \x10\x03 58910-AA800', + b'\xf1\x8758910-AB800\xf1\x00CN ESC \t 101 \x10\x03 58910-AB800\xf1\xa01.01', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x00HT6WA280BLHT6VA640A1CCN0N20NS5\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -1242,6 +1465,7 @@ FW_VERSIONS = { b'\xf1\x81HM6M2_0a0_FF0', b'\xf1\x82CNCVD0AMFCXCSFFB', b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_G80', + b'\xf1\x870\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf1\x81HM6M2_0a0_HC0', ], }, CAR.ELANTRA_HEV_2021: { @@ -1338,62 +1562,157 @@ FW_VERSIONS = { b'\xf1\x81640F0051\x00\x00\x00\x00\x00\x00\x00\x00' ], }, + CAR.KIA_SORENTO_PHEV_4TH_GEN: { + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQhe SCC FHCUP 1.00 1.06 99110-P4000 ', + ], + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4HMFC AT USA LHD 1.00 1.11 99210-P2000 211217', + ] + }, CAR.KIA_EV6: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', - b'\xf1\x8799110CV000\xf1\x00CV1_ RDR ----- 1.00 1.01 99110-CV000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.05 99210-CV000 211027', b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.06 99210-CV000 220328', b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.05 99210-CV000 211027', + b'\xf1\x00CV1 MFC AT EUR LHD 1.00 1.06 99210-CV000 220328', + b'\xf1\x00CV1 MFC AT EUR RHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT USA LHD 1.00 1.00 99210-CV100 220630', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.04 99210-CV000 210823', + b'\xf1\x00CV1 MFC AT KOR LHD 1.00 1.06 99210-CV000 220328', ], }, CAR.IONIQ_5: { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', - b'\xf1\x8799110GI000\xf1\x00NE1_ RDR ----- 1.00 1.00 99110-GI000 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.02 99211-GI010 211206', b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', + b'\xf1\x00NE1 MFC AT EUR RHD 1.00 1.02 99211-GI010 211206', + b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.03 99211-GI010 220401', + ], + }, + CAR.TUCSON_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9240 14T', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, CAR.TUCSON_HYBRID_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9240 14Q', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.01 99211-N9100 14A', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', ], }, + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1060 665', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.01 1.03 99110-CH000 ', + ], + }, + CAR.SANTA_CRUZ_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW000 14M', + b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-CW010 14X', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NX4__ 1.00 1.00 99110-K5000 ', + b'\xf1\x00NX4__ 1.01 1.00 99110-K5000 ', + ], + }, + CAR.KIA_SPORTAGE_5TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1030 662', + b'\xf1\x00NQ5 FR_CMR AT USA LHD 1.00 1.00 99211-P1040 663', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00NQ5__ 1.00 1.02 99110-P1000 ', + b'\xf1\x00NQ5__ 1.00 1.03 99110-P1000 ', + ], + }, + CAR.GENESIS_GV70_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.04 99211-AR000 210204', + b'\xf1\x00JK1 MFC AT USA LHD 1.00 1.01 99211-AR200 220125', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.02 99110-AR000 ', + b'\xf1\x00JK1_ SCC FHCUP 1.00 1.00 99110-AR200 ', + ], + }, + CAR.GENESIS_GV60_EV_1ST_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU100 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.02 99211-CU000 211215', + b'\xf1\x00JW1 MFC AT USA LHD 1.00 1.03 99211-CU000 221118', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00JW1_ RDR ----- 1.00 1.00 99110-CU000 ', + ], + }, + CAR.KIA_SORENTO_4TH_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.05 99210-R5000 210623', + b'\xf1\x00MQ4 MFC AT USA LHD 1.00 1.03 99210-R5000 200903', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', + ], + }, + CAR.KIA_NIRO_HEV_2ND_GEN: { + (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.08 99211-AT000 220531', + b'\xf1\x00SG2HMFC AT USA LHD 1.01 1.09 99211-AT000 220801', + ], + (Ecu.fwdRadar, 0x7d0, None): [ + b'\xf1\x00SG2_ RDR ----- 1.00 1.01 99110-AT000 ', + ], + }, } 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, CAR.SANTA_FE_PHEV_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, CAR.KIA_K5_HEV_2020], "6B": [CAR.KIA_SORENTO, CAR.HYUNDAI_GENESIS], } FEATURES = { # which message has the gear - "use_cluster_gears": {CAR.ELANTRA, CAR.ELANTRA_GT_I30, CAR.KONA}, + "use_cluster_gears": {CAR.ELANTRA, CAR.KONA}, "use_tcu_gears": {CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.SONATA_LF, CAR.VELOSTER, CAR.TUCSON}, - "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, 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, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022}, - - # these cars use the FCA11 message for the AEB and FCW signals, all others use SCC12 - "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, CAR.TUCSON, CAR.KONA_EV_2022}, + "use_elect_gears": {CAR.KIA_NIRO_EV, CAR.KIA_NIRO_PHEV, 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, CAR.IONIQ_PHEV_2019, CAR.KONA_EV_2022, CAR.KIA_K5_HEV_2020}, } -CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_HYBRID_4TH_GEN} +CANFD_CAR = {CAR.KIA_EV6, CAR.IONIQ_5, CAR.TUCSON_4TH_GEN, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.SANTA_CRUZ_1ST_GEN, CAR.KIA_SPORTAGE_5TH_GEN, CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.GENESIS_GV60_EV_1ST_GEN, CAR.KIA_SORENTO_4TH_GEN, CAR.KIA_NIRO_HEV_2ND_GEN} + +# The radar does SCC on these cars when HDA I, rather than the camera +CANFD_RADAR_SCC_CAR = {CAR.GENESIS_GV70_1ST_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_SORENTO_4TH_GEN} # The camera does SCC on these cars, rather than the radar CAMERA_SCC_CAR = {CAR.KONA_EV_2022, } -HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, 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, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN} # these cars use a different gas signal -EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5} +HYBRID_CAR = {CAR.IONIQ_PHEV, CAR.ELANTRA_HEV_2021, CAR.KIA_NIRO_PHEV, 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, CAR.IONIQ_PHEV_2019, CAR.TUCSON_HYBRID_4TH_GEN, CAR.KIA_SPORTAGE_HYBRID_5TH_GEN, CAR.KIA_SORENTO_PHEV_4TH_GEN, CAR.KIA_K5_HEV_2020, CAR.KIA_NIRO_HEV_2ND_GEN} # these cars use a different gas signal +EV_CAR = {CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.KONA_EV, CAR.KIA_NIRO_EV, CAR.KONA_EV_2022, CAR.KIA_EV6, CAR.IONIQ_5, CAR.GENESIS_GV60_EV_1ST_GEN} # these cars require a special panda safety mode due to missing counters and checksums in the messages -LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, CAR.KIA_STINGER, CAR.GENESIS_G70, CAR.GENESIS_G80, CAR.KIA_CEED, CAR.ELANTRA, CAR.IONIQ_HEV_2022} +LEGACY_SAFETY_MODE_CAR = {CAR.HYUNDAI_GENESIS, CAR.IONIQ_EV_2020, CAR.IONIQ_EV_LTD, CAR.IONIQ_PHEV, CAR.IONIQ, CAR.KONA_EV, CAR.KIA_SORENTO, CAR.SONATA_LF, CAR.KIA_OPTIMA_G4, CAR.KIA_OPTIMA_G4_FL, CAR.VELOSTER, + 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 @@ -1401,7 +1720,6 @@ DBC = { CAR.ELANTRA: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_2021: dbc_dict('hyundai_kia_generic', None), CAR.ELANTRA_HEV_2021: dbc_dict('hyundai_kia_generic', None), - CAR.ELANTRA_GT_I30: dbc_dict('hyundai_kia_generic', None), CAR.GENESIS_G70: dbc_dict('hyundai_kia_generic', None), CAR.GENESIS_G70_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.GENESIS_G80: dbc_dict('hyundai_kia_generic', None), @@ -1415,6 +1733,7 @@ DBC = { CAR.IONIQ_HEV_2022: dbc_dict('hyundai_kia_generic', None), CAR.KIA_FORTE: dbc_dict('hyundai_kia_generic', None), CAR.KIA_K5_2021: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_K5_HEV_2020: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_EV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_PHEV: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), CAR.KIA_NIRO_HEV_2021: dbc_dict('hyundai_kia_generic', None), @@ -1424,6 +1743,7 @@ DBC = { CAR.KIA_SELTOS: dbc_dict('hyundai_kia_generic', None), CAR.KIA_SORENTO: dbc_dict('hyundai_kia_generic', None), # Has 0x5XX messages, but different format CAR.KIA_STINGER: dbc_dict('hyundai_kia_generic', None), + CAR.KIA_STINGER_2022: dbc_dict('hyundai_kia_generic', None), CAR.KONA: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV: dbc_dict('hyundai_kia_generic', None), CAR.KONA_EV_2022: dbc_dict('hyundai_kia_generic', None), @@ -1440,6 +1760,15 @@ DBC = { CAR.KIA_CEED: dbc_dict('hyundai_kia_generic', None), CAR.KIA_EV6: dbc_dict('hyundai_canfd', None), CAR.SONATA_HYBRID: dbc_dict('hyundai_kia_generic', 'hyundai_kia_mando_front_radar_generated'), + CAR.TUCSON_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.TUCSON_HYBRID_4TH_GEN: dbc_dict('hyundai_canfd', None), CAR.IONIQ_5: dbc_dict('hyundai_canfd', None), + CAR.SANTA_CRUZ_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SPORTAGE_5TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SPORTAGE_HYBRID_5TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.GENESIS_GV70_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_PHEV_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.GENESIS_GV60_EV_1ST_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_SORENTO_4TH_GEN: dbc_dict('hyundai_canfd', None), + CAR.KIA_NIRO_HEV_2ND_GEN: dbc_dict('hyundai_canfd', None), } diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 4647a04244..249818369c 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -8,10 +8,10 @@ from cereal import car from common.basedir import BASEDIR from common.conversions import Conversions as CV from common.kalman.simple_kalman import KF1D -from common.numpy_fast import interp +from common.numpy_fast import clip, interp from common.realtime import DT_CTRL -from selfdrive.car import apply_hysteresis, gen_empty_fingerprint -from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_deadzone +from selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness +from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, apply_center_deadzone from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel @@ -23,7 +23,7 @@ TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqu MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 ACCEL_MIN = -3.5 -FRICTION_THRESHOLD = 0.2 +FRICTION_THRESHOLD = 0.3 TORQUE_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/params.yaml') TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override.yaml') @@ -87,10 +87,35 @@ class CarInterfaceBase(ABC): def get_pid_accel_limits(CP, current_speed, cruise_speed): return ACCEL_MIN, ACCEL_MAX + @classmethod + def get_non_essential_params(cls, candidate: str): + """ + Parameters essential to controlling the car may be incomplete or wrong without FW versions or fingerprints. + """ + return cls.get_params(candidate, gen_empty_fingerprint(), list(), False) + + @classmethod + def get_params(cls, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool): + ret = CarInterfaceBase.get_std_params(candidate) + ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long) + + # Set common params using fields set by the car interface + # TODO: get actual value, for now starting with reasonable value for + # civic and scaling by mass and wheelbase + ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) + + # TODO: some car interfaces set stiffness factor + if ret.tireStiffnessFront == 0 or ret.tireStiffnessRear == 0: + # TODO: start from empirically derived lateral slip stiffness for the civic and scale by + # mass and CG position, so all cars will have approximately similar dyn behaviors + ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) + + return ret + @staticmethod @abstractmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - pass + def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool): + raise NotImplementedError @staticmethod def init(CP, logcan, sendcan): @@ -109,7 +134,7 @@ class CarInterfaceBase(ABC): def torque_from_lateral_accel_linear(lateral_accel_value, torque_params, lateral_accel_error, lateral_accel_deadzone, friction_compensation): # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction_interp = interp( - apply_deadzone(lateral_accel_error, lateral_accel_deadzone), + apply_center_deadzone(lateral_accel_error, lateral_accel_deadzone), [-FRICTION_THRESHOLD, FRICTION_THRESHOLD], [-torque_params.friction, torque_params.friction] ) @@ -121,7 +146,7 @@ class CarInterfaceBase(ABC): # returns a set of default params to avoid repetition in car specific params @staticmethod - def get_std_params(candidate, fingerprint): + def get_std_params(candidate): ret = car.CarParams.new_message() ret.carFingerprint = candidate @@ -208,7 +233,7 @@ class CarInterfaceBase(ABC): return reader @abstractmethod - def apply(self, c: car.CarControl) -> Tuple[car.CarControl.Actuators, List[bytes]]: + def apply(self, c: car.CarControl, now_nanos: int) -> Tuple[car.CarControl.Actuators, List[bytes]]: pass def create_common_events(self, cs_out, extra_gears=None, pcm_enable=True, allow_enable=True, @@ -250,8 +275,8 @@ class CarInterfaceBase(ABC): # Enable OP long on falling edge of enable buttons (defaults to accelCruise and decelCruise, overridable per-port) if not self.CP.pcmCruise and (b.type in enable_buttons and not b.pressed): events.add(EventName.buttonEnable) - # Disable on rising edge of cancel for both stock and OP long - if b.type == ButtonType.cancel and b.pressed: + # Disable on rising and falling edge of cancel for both stock and OP long + if b.type == ButtonType.cancel: events.add(EventName.buttonCancel) # Handle permanent and temporary steering faults @@ -303,6 +328,7 @@ class CarStateBase(ABC): self.cruise_buttons = 0 self.left_blinker_cnt = 0 self.right_blinker_cnt = 0 + self.steering_pressed_cnt = 0 self.left_blinker_prev = False self.right_blinker_prev = False self.cluster_speed_hyst_gap = 0.0 @@ -340,6 +366,12 @@ class CarStateBase(ABC): self.right_blinker_cnt = blinker_time if right_blinker_lamp else max(self.right_blinker_cnt - 1, 0) return self.left_blinker_cnt > 0, self.right_blinker_cnt > 0 + def update_steering_pressed(self, steering_pressed, steering_pressed_min_count): + """Applies filtering on steering pressed for noisy driver torque signals.""" + self.steering_pressed_cnt += 1 if steering_pressed else -1 + self.steering_pressed_cnt = clip(self.steering_pressed_cnt, 0, steering_pressed_min_count * 2) + return self.steering_pressed_cnt > steering_pressed_min_count + def update_blinker_from_stalk(self, blinker_time: int, left_blinker_stalk: bool, right_blinker_stalk: bool): """Update blinkers from stalk position. When stalk is seen the blinker will be on for at least blinker_time, or until the stalk is turned off, whichever is longer. If the opposite stalk direction is seen the blinker @@ -369,15 +401,15 @@ class CarStateBase(ABC): return GearShifter.unknown d: Dict[str, car.CarState.GearShifter] = { - 'P': GearShifter.park, 'PARK': GearShifter.park, - 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, - 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, - 'E': GearShifter.eco, 'ECO': GearShifter.eco, - 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, - 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, - 'S': GearShifter.sport, 'SPORT': GearShifter.sport, - 'L': GearShifter.low, 'LOW': GearShifter.low, - 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, + 'P': GearShifter.park, 'PARK': GearShifter.park, + 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, + 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, + 'E': GearShifter.eco, 'ECO': GearShifter.eco, + 'T': GearShifter.manumatic, 'MANUAL': GearShifter.manumatic, + 'D': GearShifter.drive, 'DRIVE': GearShifter.drive, + 'S': GearShifter.sport, 'SPORT': GearShifter.sport, + 'L': GearShifter.low, 'LOW': GearShifter.low, + 'B': GearShifter.brake, 'BRAKE': GearShifter.brake, } return d.get(gear.upper(), GearShifter.unknown) diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 4b4bdcc0ca..d9c658a14c 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -137,17 +137,17 @@ class IsoTpParallelQuery: else: response_timeouts[tx_addr] = 0 request_done[tx_addr] = True - cloudlog.warning(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") + cloudlog.error(f"iso-tp query bad response: {tx_addr} - 0x{dat.hex()}") cur_time = time.monotonic() if cur_time - max(response_timeouts.values()) > 0: for tx_addr in msgs: if request_counter[tx_addr] > 0 and not request_done[tx_addr]: - cloudlog.warning(f"iso-tp query timeout after receiving response: {tx_addr}") + cloudlog.error(f"iso-tp query timeout after receiving response: {tx_addr}") break if cur_time - start_time > total_timeout: - cloudlog.warning("iso-tp query timeout while receiving data") + cloudlog.error("iso-tp query timeout while receiving data") break return results diff --git a/selfdrive/car/mazda/carcontroller.py b/selfdrive/car/mazda/carcontroller.py index 2add59ccb0..cb401a8abd 100644 --- a/selfdrive/car/mazda/carcontroller.py +++ b/selfdrive/car/mazda/carcontroller.py @@ -1,6 +1,6 @@ from cereal import car from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_torque_limits +from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.mazda import mazdacan from selfdrive.car.mazda.values import CarControllerParams, Buttons @@ -15,7 +15,7 @@ class CarController: self.brake_counter = 0 self.frame = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): can_sends = [] apply_steer = 0 @@ -23,8 +23,8 @@ class CarController: if CC.latActive: # calculate steer and also set limits due to driver torque new_steer = int(round(CC.actuators.steer * CarControllerParams.STEER_MAX)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, - CS.out.steeringTorque, CarControllerParams) + apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, + CS.out.steeringTorque, CarControllerParams) if CC.cruiseControl.cancel: # If brake is pressed, let us wait >70ms before trying to disable crz to avoid @@ -59,6 +59,7 @@ class CarController: new_actuators = CC.actuators.copy() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.steerOutputCan = apply_steer self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/mazda/interface.py b/selfdrive/car/mazda/interface.py index 7c42431e33..2930b002d4 100755 --- a/selfdrive/car/mazda/interface.py +++ b/selfdrive/car/mazda/interface.py @@ -2,7 +2,7 @@ from cereal import car from common.conversions import Conversions as CV from selfdrive.car.mazda.values import CAR, LKAS_LIMITS -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type @@ -11,12 +11,10 @@ EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mazda" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.mazda)] - ret.radarOffCan = True + ret.radarUnavailable = True ret.dashcamOnly = candidate not in (CAR.CX5_2022, CAR.CX9_2021) @@ -48,10 +46,6 @@ class CarInterface(CarInterfaceBase): ret.centerToFront = ret.wheelbase * 0.41 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -75,5 +69,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index 9befad4d0b..598b598a16 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -20,6 +20,10 @@ class CarControllerParams: STEER_DRIVER_MULTIPLIER = 1 # weight driver torque STEER_DRIVER_FACTOR = 1 # from dbc STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor + STEER_STEP = 1 # 100 Hz + + def __init__(self, CP): + pass class CAR: @@ -42,8 +46,8 @@ CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = { CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"), CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"), CAR.MAZDA6: MazdaCarInfo("Mazda 6 2017-20"), - CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-22", video_link="https://youtu.be/dA3duO4a0O4"), - CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022"), + CAR.CX9_2021: MazdaCarInfo("Mazda CX-9 2021-23", video_link="https://youtu.be/dA3duO4a0O4"), + CAR.CX5_2022: MazdaCarInfo("Mazda CX-5 2022-23"), } @@ -89,6 +93,7 @@ FW_VERSIONS = { ], (Ecu.fwdCamera, 0x706, None): [ b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PYB2-21PS1-H\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -98,13 +103,15 @@ FW_VERSIONS = { }, CAR.CX5: { (Ecu.eps, 0x730, None): [ + b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'KCB8-3210X-B-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-G-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-J-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'KJ01-3210X-M-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', - b'K319-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'PA53-188K2-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PAR4-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PYFA-188K2-J\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', 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', @@ -146,6 +153,7 @@ FW_VERSIONS = { ], (Ecu.transmission, 0x7e1, None): [ b'PA66-21PS1-A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'PA66-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX39-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX39-21PS1-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PX68-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -279,6 +287,7 @@ FW_VERSIONS = { b'TC3M-3210X-A-00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'PXGW-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-C\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM4-188K2-D\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'PXM6-188K2-E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', @@ -295,6 +304,7 @@ FW_VERSIONS = { b'GSH7-67XK2-N\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'GSH7-67XK2-S\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', + b'GSH7-67XK2-T\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'PXM4-21PS1-B\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index a3194cd79e..13210c86d5 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -1,39 +1,28 @@ #!/usr/bin/env python3 -import math from cereal import car -from common.conversions import Conversions as CV from system.swaglog import cloudlog import cereal.messaging as messaging -from selfdrive.car import gen_empty_fingerprint, get_safety_config +from selfdrive.car import get_safety_config from selfdrive.car.interfaces import CarInterfaceBase + # mocked car interface to work with chffrplus -TS = 0.01 # 100Hz -YAW_FR = 0.2 # ~0.8s time constant on yaw rate filter -# low pass gain -LPG = 2 * math.pi * YAW_FR * TS / (1 + 2 * math.pi * YAW_FR * TS) - - class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): super().__init__(CP, CarController, CarState) cloudlog.debug("Using Mock Car Interface") - self.sm = messaging.SubMaster(['gyroscope', 'gpsLocation', 'gpsLocationExternal']) + self.sm = messaging.SubMaster(['gpsLocation', 'gpsLocationExternal']) self.speed = 0. self.prev_speed = 0. - self.yaw_rate = 0. - self.yaw_rate_meas = 0. @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "mock" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.noOutput)] ret.mass = 1700. - ret.rotationalInertia = 2500. ret.wheelbase = 2.70 ret.centerToFront = ret.wheelbase * 0.5 ret.steerRatio = 13. # reasonable @@ -45,11 +34,6 @@ class CarInterface(CarInterfaceBase): # returns a car.CarState def _update(self, c): self.sm.update(0) - - # get basic data from phone and gps since CAN isn't connected - if self.sm.updated['gyroscope']: - self.yaw_rate_meas = -self.sm['gyroscope'].gyroUncalibrated.v[0] - gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' if self.sm.updated[gps_sock]: self.prev_speed = self.speed @@ -61,10 +45,9 @@ class CarInterface(CarInterfaceBase): # speeds ret.vEgo = self.speed ret.vEgoRaw = self.speed - a = self.speed - self.prev_speed - ret.aEgo = a - ret.brakePressed = a < -0.5 + ret.aEgo = self.speed - self.prev_speed + ret.brakePressed = ret.aEgo < -0.5 ret.standstill = self.speed < 0.01 ret.wheelSpeeds.fl = self.speed @@ -72,13 +55,9 @@ class CarInterface(CarInterfaceBase): ret.wheelSpeeds.rl = self.speed ret.wheelSpeeds.rr = self.speed - self.yawRate = LPG * self.yaw_rate_meas + (1. - LPG) * self.yaw_rate - curvature = self.yaw_rate / max(self.speed, 1.) - ret.steeringAngleDeg = curvature * self.CP.steerRatio * self.CP.wheelbase * CV.RAD_TO_DEG - return ret - def apply(self, c): + def apply(self, c, now_nanos): # in mock no carcontrols actuators = car.CarControl.Actuators.new_message() return actuators, [] diff --git a/selfdrive/car/nissan/carcontroller.py b/selfdrive/car/nissan/carcontroller.py index dbc2b33c6b..45c3dd720c 100644 --- a/selfdrive/car/nissan/carcontroller.py +++ b/selfdrive/car/nissan/carcontroller.py @@ -1,6 +1,6 @@ from cereal import car -from common.numpy_fast import clip, interp from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_angle_limits from selfdrive.car.nissan import nissancan from selfdrive.car.nissan.values import CAR, CarControllerParams @@ -14,11 +14,11 @@ class CarController: self.frame = 0 self.lkas_max_torque = 0 - self.last_angle = 0 + self.apply_angle_last = 0 self.packer = CANPacker(dbc_name) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -28,18 +28,11 @@ class CarController: ### STEER ### lkas_hud_msg = CS.lkas_hud_msg lkas_hud_info_msg = CS.lkas_hud_info_msg - apply_angle = actuators.steeringAngleDeg - steer_hud_alert = 1 if hud_control.visualAlert in (VisualAlert.steerRequired, VisualAlert.ldw) else 0 if CC.latActive: # windup slower - if self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle): - angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_V) - else: - angle_rate_lim = interp(CS.out.vEgo, CarControllerParams.ANGLE_DELTA_BP, CarControllerParams.ANGLE_DELTA_VU) - - apply_angle = clip(apply_angle, self.last_angle - angle_rate_lim, self.last_angle + angle_rate_lim) + apply_angle = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo, CarControllerParams) # Max torque from driver before EPS will give up and not apply torque if not bool(CS.out.steeringPressed): @@ -57,7 +50,7 @@ class CarController: apply_angle = CS.out.steeringAngleDeg self.lkas_max_torque = 0 - self.last_angle = apply_angle + self.apply_angle_last = apply_angle if self.CP.carFingerprint in (CAR.ROGUE, CAR.XTRAIL, CAR.ALTIMA) and pcm_cancel_cmd: can_sends.append(nissancan.create_acc_cancel_cmd(self.packer, self.car_fingerprint, CS.cruise_throttle_msg)) diff --git a/selfdrive/car/nissan/carstate.py b/selfdrive/car/nissan/carstate.py index a5ca237110..d6b6d17d55 100644 --- a/selfdrive/car/nissan/carstate.py +++ b/selfdrive/car/nissan/carstate.py @@ -44,7 +44,7 @@ class CarState(CarStateBase): ret.vEgoRaw = (ret.wheelSpeeds.fl + ret.wheelSpeeds.fr + ret.wheelSpeeds.rl + ret.wheelSpeeds.rr) / 4. ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = ret.vEgoRaw < 0.01 + ret.standstill = cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RL"] == 0.0 and cp.vl["WHEEL_SPEEDS_REAR"]["WHEEL_SPEED_RR"] == 0.0 if self.CP.carFingerprint == CAR.ALTIMA: ret.cruiseState.enabled = bool(cp.vl["CRUISE_STATE"]["CRUISE_ENABLED"]) diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index e095ceb461..074cd1cc57 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.nissan.values import CAR @@ -8,9 +8,7 @@ from selfdrive.car.nissan.values import CAR class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "nissan" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.nissan)] ret.autoResumeSng = False @@ -20,6 +18,9 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerRatio = 17 + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.radarUnavailable = True + if candidate in (CAR.ROGUE, CAR.XTRAIL): ret.mass = 1610 + STD_CARGO_KG ret.wheelbase = 2.705 @@ -35,17 +36,6 @@ class CarInterface(CarInterfaceBase): ret.wheelbase = 2.824 ret.centerToFront = ret.wheelbase * 0.44 - ret.steerControlType = car.CarParams.SteerControlType.angle - ret.radarOffCan = True - - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState @@ -66,5 +56,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index 09bd7ca838..e9af828e2b 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -4,7 +4,7 @@ from enum import Enum from cereal import car from panda.python import uds -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo, Harness from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -12,12 +12,14 @@ Ecu = car.CarParams.Ecu class CarControllerParams: - ANGLE_DELTA_BP = [0., 5., 15.] - ANGLE_DELTA_V = [5., .8, .15] # windup limit - ANGLE_DELTA_VU = [5., 3.5, 0.4] # unwind limit + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., .8, .15]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., 3.5, 0.4]) LKAS_MAX_TORQUE = 1 # A value of 1 is easy to overpower STEER_THRESHOLD = 1.0 + def __init__(self, CP): + pass + class CAR: XTRAIL = "NISSAN X-TRAIL 2017" @@ -37,7 +39,7 @@ class NissanCarInfo(CarInfo): CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = { CAR.XTRAIL: NissanCarInfo("Nissan X-Trail 2017"), - CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-22"), + CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-22", video_link="https://youtu.be/vaMbtAh_0cY"), CAR.LEAF_IC: None, # same platforms CAR.ROGUE: NissanCarInfo("Nissan Rogue 2018-20"), CAR.ALTIMA: NissanCarInfo("Nissan Altima 2019-20", harness=Harness.nissan_b), diff --git a/selfdrive/car/subaru/carcontroller.py b/selfdrive/car/subaru/carcontroller.py index b5429daef2..a6dbf4a39e 100644 --- a/selfdrive/car/subaru/carcontroller.py +++ b/selfdrive/car/subaru/carcontroller.py @@ -1,5 +1,5 @@ from opendbc.can.packer import CANPacker -from selfdrive.car import apply_std_steer_torque_limits +from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.subaru import subarucan from selfdrive.car.subaru.values import DBC, GLOBAL_GEN2, PREGLOBAL_CARS, CarControllerParams @@ -19,7 +19,7 @@ class CarController: self.p = CarControllerParams(CP) self.packer = CANPacker(DBC[CP.carFingerprint]['pt']) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -34,7 +34,7 @@ class CarController: # 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, self.p) + apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.p) if not CC.latActive: apply_steer = 0 @@ -87,6 +87,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.p.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last self.frame += 1 return new_actuators, can_sends diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index a920c02534..733482ef82 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 from cereal import car from panda import Panda -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS @@ -9,11 +9,9 @@ from selfdrive.car.subaru.values import CAR, GLOBAL_GEN2, PREGLOBAL_CARS class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "subaru" - ret.radarOffCan = True + ret.radarUnavailable = True ret.dashcamOnly = candidate in PREGLOBAL_CARS ret.autoResumeSng = False @@ -103,14 +101,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"unknown car: {candidate}") - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by - # mass and CG position, so all cars will have approximately similar dyn behaviors - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret # returns a car.CarState @@ -122,5 +112,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index da5ff1785a..9c4447631d 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -63,7 +63,7 @@ CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { ], CAR.IMPREZA_2020: [ SubaruCarInfo("Subaru Impreza 2020-22"), - SubaruCarInfo("Subaru Crosstrek 2020-21"), + SubaruCarInfo("Subaru Crosstrek 2020-23"), SubaruCarInfo("Subaru XV 2020-21"), ], CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), @@ -196,6 +196,7 @@ FW_VERSIONS = { b'\xaa!dt\a', b'\xc5!ar\a', b'\xbe!as\a', + b'\xc5!as\x07', b'\xc5!ds\a', b'\xc5!`s\a', b'\xaa!au\a', @@ -234,12 +235,14 @@ FW_VERSIONS = { b'\x9a\xc0\000\000', b'\n\xc0\004\000', b'\x9a\xc0\x04\x00', + b'\n\xc0\x04\x01', ], (Ecu.fwdCamera, 0x787, None): [ b'\000\000eb\037@ \"', b'\000\000e\x8f\037@ )', b'\x00\x00eq\x1f@ "', b'\x00\x00eq\x00\x00\x00\x00', + b'\x00\x00e\x8f\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\xca!ap\a', @@ -249,6 +252,8 @@ FW_VERSIONS = { b'\xcc!fp\a', b'\xca!f@\x07', b'\xca!fp\x07', + b'\xf3"f@\x07', + b'\xe6!fp\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xe6\xf5\004\000\000', @@ -257,6 +262,8 @@ FW_VERSIONS = { b'\xe7\xf5D0\000', b'\xf1\x00\xd7\x10@', b'\xe6\xf5D0\x00', + b'\xe9\xf6F0\x00', + b'\xe9\xf5B0\x00', ], }, CAR.FORESTER: { @@ -266,9 +273,11 @@ FW_VERSIONS = { b'\xa3 \031\024\000', b'\xa3 \x14\x01', b'\xf1\x00\xbb\r\x05', + b'\xa3 \x18&\x00', ], (Ecu.eps, 0x746, None): [ b'\x8d\xc0\x04\x00', + b'\x8d\xc0\x00\x00', ], (Ecu.fwdCamera, 0x787, None): [ b'\x00\x00e!\x1f@ \x11', @@ -285,6 +294,7 @@ FW_VERSIONS = { b'\xcb\"`p\a', b'\xf1\x00\xa2\x10\n', b'\xcf"`p\x07', + b'\xb6\xa2`A\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\032\xf6B0\000', @@ -292,6 +302,7 @@ FW_VERSIONS = { b'\032\xf6b`\000', b'\x1a\xf6B`\x00', b'\x1a\xf6b0\x00', + b'\x1a\xe6B1\x00', ], }, CAR.FORESTER_PREGLOBAL: { @@ -460,6 +471,7 @@ FW_VERSIONS = { b'\xa1 \x08\x02', b'\xa1 \x06\x02', b'\xa1 \x08\x00', + b'\xa1 "\t\x00', ], (Ecu.eps, 0x746, None): [ b'\x9b\xc0\x10\x00', @@ -481,6 +493,7 @@ FW_VERSIONS = { b'\xe2"`p\x07', b'\xf1\x82\xe2,\xa0@\x07', b'\xbc"`q\x07', + b'\xe3,\xa0@\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\xa5\xfe\xf7@\x00', diff --git a/selfdrive/car/tesla/carcontroller.py b/selfdrive/car/tesla/carcontroller.py index cf43b8ef00..1f18c3d0ea 100644 --- a/selfdrive/car/tesla/carcontroller.py +++ b/selfdrive/car/tesla/carcontroller.py @@ -1,5 +1,6 @@ -from common.numpy_fast import clip, interp +from common.numpy_fast import clip from opendbc.can.packer import CANPacker +from selfdrive.car import apply_std_steer_angle_limits from selfdrive.car.tesla.teslacan import TeslaCAN from selfdrive.car.tesla.values import DBC, CANBUS, CarControllerParams @@ -8,12 +9,12 @@ class CarController: def __init__(self, dbc_name, CP, VM): self.CP = CP self.frame = 0 - self.last_angle = 0 + self.apply_angle_last = 0 self.packer = CANPacker(dbc_name) self.pt_packer = CANPacker(DBC[CP.carFingerprint]['pt']) self.tesla_can = TeslaCAN(self.packer, self.pt_packer) - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators pcm_cancel_cmd = CC.cruiseControl.cancel @@ -24,20 +25,15 @@ class CarController: lkas_enabled = CC.latActive and not hands_on_fault if lkas_enabled: - apply_angle = actuators.steeringAngleDeg - # Angular rate limit based on speed - steer_up = self.last_angle * apply_angle > 0. and abs(apply_angle) > abs(self.last_angle) - rate_limit = CarControllerParams.RATE_LIMIT_UP if steer_up else CarControllerParams.RATE_LIMIT_DOWN - max_angle_diff = interp(CS.out.vEgo, rate_limit.speed_points, rate_limit.max_angle_diff_points) - apply_angle = clip(apply_angle, self.last_angle - max_angle_diff, self.last_angle + max_angle_diff) + apply_angle = apply_std_steer_angle_limits(actuators.steeringAngleDeg, self.apply_angle_last, CS.out.vEgo, CarControllerParams) # To not fault the EPS apply_angle = clip(apply_angle, CS.out.steeringAngleDeg - 20, CS.out.steeringAngleDeg + 20) else: apply_angle = CS.out.steeringAngleDeg - self.last_angle = apply_angle + self.apply_angle_last = apply_angle can_sends.append(self.tesla_can.create_steering_control(apply_angle, lkas_enabled, self.frame)) # Longitudinal control (in sync with stock message, about 40Hz) diff --git a/selfdrive/car/tesla/interface.py b/selfdrive/car/tesla/interface.py index 2eb29efb41..70d49896cb 100755 --- a/selfdrive/car/tesla/interface.py +++ b/selfdrive/car/tesla/interface.py @@ -2,14 +2,13 @@ from cereal import car from panda import Panda from selfdrive.car.tesla.values import CANBUS, CAR -from selfdrive.car import STD_CARGO_KG, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase class CarInterface(CarInterfaceBase): @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "tesla" # There is no safe way to do steer blending with user torque, @@ -24,7 +23,6 @@ class CarInterface(CarInterfaceBase): ret.longitudinalTuning.kpV = [0] ret.longitudinalTuning.kiBP = [0] ret.longitudinalTuning.kiV = [0] - ret.stopAccel = 0.0 ret.longitudinalActuatorDelayUpperBound = 0.5 # s ret.radarTimeStep = (1.0 / 8) # 8Hz @@ -51,9 +49,6 @@ class CarInterface(CarInterfaceBase): else: raise ValueError(f"Unsupported car: {candidate}") - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront) - return ret def _update(self, c): @@ -63,5 +58,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index e28666c625..bac025a9c5 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -2,14 +2,13 @@ from collections import namedtuple from typing import Dict, List, Union from cereal import car -from selfdrive.car import dbc_dict +from selfdrive.car import AngleRateLimit, dbc_dict from selfdrive.car.docs_definitions import CarInfo from selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries Ecu = car.CarParams.Ecu Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) -AngleRateLimit = namedtuple('AngleRateLimit', ['speed_points', 'max_angle_diff_points']) class CAR: @@ -99,13 +98,16 @@ BUTTONS = [ Button(car.CarState.ButtonEvent.Type.rightBlinker, "STW_ACTN_RQ", "TurnIndLvr_Stat", [2]), Button(car.CarState.ButtonEvent.Type.accelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [4, 16]), Button(car.CarState.ButtonEvent.Type.decelCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [8, 32]), - Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), - Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.cancel, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [1]), + Button(car.CarState.ButtonEvent.Type.resumeCruise, "STW_ACTN_RQ", "SpdCtrlLvr_Stat", [2]), ] class CarControllerParams: - RATE_LIMIT_UP = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., .8, .15]) - RATE_LIMIT_DOWN = AngleRateLimit(speed_points=[0., 5., 15.], max_angle_diff_points=[5., 3.5, 0.4]) + ANGLE_RATE_LIMIT_UP = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., .8, .15]) + ANGLE_RATE_LIMIT_DOWN = AngleRateLimit(speed_bp=[0., 5., 15.], angle_v=[5., 3.5, 0.4]) JERK_LIMIT_MAX = 8 JERK_LIMIT_MIN = -8 ACCEL_TO_SPEED_MULTIPLIER = 3 + + def __init__(self, CP): + pass diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 85787ae88d..4dd19d24e3 100644 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -18,14 +18,17 @@ from selfdrive.car.body.values import CAR as COMMA non_tested_cars = [ FORD.ESCAPE_MK4, FORD.FOCUS_MK4, + FORD.MAVERICK_MK1, GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, GM.MALIBU, GM.EQUINOX, - GM.BOLT_EV, - HYUNDAI.ELANTRA_GT_I30, HYUNDAI.GENESIS_G90, HYUNDAI.KIA_OPTIMA_H, + HONDA.ODYSSEY_CHN, + VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter + TOYOTA.RAV4_TSS2_2023, + TOYOTA.RAV4H_TSS2_2023, ] CarTestRoute = namedtuple('CarTestRoute', ['route', 'car_model', 'segment'], defaults=(None,)) @@ -43,14 +46,18 @@ routes = [ CarTestRoute("221c253375af4ee9|2022-06-15--18-38-24", CHRYSLER.RAM_1500), CarTestRoute("8fb5eabf914632ae|2022-08-04--17-28-53", CHRYSLER.RAM_HD, segment=6), + CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.BRONCO_SPORT_MK1), CarTestRoute("62241b0c7fea4589|2022-09-01--15-32-49", FORD.EXPLORER_MK6), #TestRoute("f1b4c567731f4a1b|2018-04-30--10-15-35", FORD.FUSION), CarTestRoute("7cc2a8365b4dd8a9|2018-12-02--12-10-44", GM.ACADIA), CarTestRoute("aa20e335f61ba898|2019-02-05--16-59-04", GM.BUICK_REGAL), + CarTestRoute("75a6bcb9b8b40373|2023-03-11--22-47-33", GM.BUICK_LACROSSE), + CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE), CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), CarTestRoute("c950e28c26b5b168|2018-05-30--22-03-41", GM.VOLT), CarTestRoute("f08912a233c1584f|2022-08-11--18-02-41", GM.BOLT_EUV, segment=1), + CarTestRoute("555d4087cf86aa91|2022-12-02--12-15-07", GM.BOLT_EUV, segment=14), # Bolt EV CarTestRoute("38aa7da107d5d252|2022-08-15--16-01-12", GM.SILVERADO), CarTestRoute("0e7a2ba168465df5|2020-10-18--14-14-22", HONDA.ACURA_RDX_3G), @@ -62,7 +69,6 @@ routes = [ CarTestRoute("2c4292a5cd10536c|2021-08-19--21-32-15", HONDA.FREED), CarTestRoute("03be5f2fd5c508d1|2020-04-19--18-44-15", HONDA.HRV), CarTestRoute("917b074700869333|2021-05-24--20-40-20", HONDA.ACURA_ILX), - CarTestRoute("81722949a62ea724|2019-04-06--15-19-25", HONDA.ODYSSEY_CHN), CarTestRoute("08a3deb07573f157|2020-03-06--16-11-19", HONDA.ACCORD), # 1.5T CarTestRoute("1da5847ac2488106|2021-05-24--19-31-50", HONDA.ACCORD), # 2.0T CarTestRoute("085ac1d942c35910|2021-03-25--20-11-15", HONDA.ACCORD), # 2021 with new style HUD msgs @@ -73,7 +79,7 @@ routes = [ CarTestRoute("d83f36766f8012a5|2020-02-05--18-42-21", HONDA.CIVIC_BOSCH_DIESEL), CarTestRoute("f0890d16a07a236b|2021-05-25--17-27-22", HONDA.INSIGHT), CarTestRoute("07d37d27996096b6|2020-03-04--21-57-27", HONDA.PILOT), - CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PASSPORT), + CarTestRoute("684e8f96bd491a0e|2021-11-03--11-08-42", HONDA.PILOT), # Passport CarTestRoute("0a78dfbacc8504ef|2020-03-04--13-29-55", HONDA.CIVIC_BOSCH), CarTestRoute("f34a60d68d83b1e5|2020-10-06--14-35-55", HONDA.ACURA_RDX), CarTestRoute("54fd8451b3974762|2021-04-01--14-50-10", HONDA.RIDGELINE), @@ -81,8 +87,11 @@ routes = [ CarTestRoute("f44aa96ace22f34a|2021-12-22--06-22-31", HONDA.CIVIC_2022), CarTestRoute("6fe86b4e410e4c37|2020-07-22--16-27-13", HYUNDAI.HYUNDAI_GENESIS), + CarTestRoute("b5d6dc830ad63071|2022-12-12--21-28-25", HYUNDAI.GENESIS_GV60_EV_1ST_GEN, segment=12), CarTestRoute("70c5bec28ec8e345|2020-08-08--12-22-23", HYUNDAI.GENESIS_G70), + CarTestRoute("ca4de5b12321bd98|2022-10-18--21-15-59", HYUNDAI.GENESIS_GV70_1ST_GEN), CarTestRoute("6b301bf83f10aa90|2020-11-22--16-45-07", HYUNDAI.GENESIS_G80), + CarTestRoute("f0709d2bc6ca451f|2022-10-15--08-13-54", HYUNDAI.SANTA_CRUZ_1ST_GEN), CarTestRoute("4dbd55df87507948|2022-03-01--09-45-38", HYUNDAI.SANTA_FE), CarTestRoute("bf43d9df2b660eb0|2021-09-23--14-16-37", HYUNDAI.SANTA_FE_2022), CarTestRoute("37398f32561a23ad|2021-11-18--00-11-35", HYUNDAI.SANTA_FE_HEV_2022), @@ -91,13 +100,17 @@ routes = [ CarTestRoute("7653b2bce7bcfdaa|2020-03-04--15-34-32", HYUNDAI.KIA_OPTIMA_G4), CarTestRoute("018654717bc93d7d|2022-09-19--23-11-10", HYUNDAI.KIA_OPTIMA_G4_FL, segment=0), CarTestRoute("c75a59efa0ecd502|2021-03-11--20-52-55", HYUNDAI.KIA_SELTOS), + CarTestRoute("b3537035ffe6a7d6|2022-10-17--15-23-49", HYUNDAI.KIA_SPORTAGE_HYBRID_5TH_GEN), CarTestRoute("5b7c365c50084530|2020-04-15--16-13-24", HYUNDAI.SONATA), CarTestRoute("b2a38c712dcf90bd|2020-05-18--18-12-48", HYUNDAI.SONATA_LF), CarTestRoute("fb3fd42f0baaa2f8|2022-03-30--15-25-05", HYUNDAI.TUCSON), + CarTestRoute("db68bbe12250812c|2022-12-05--00-54-12", HYUNDAI.TUCSON_4TH_GEN), # 2023 CarTestRoute("36e10531feea61a4|2022-07-25--13-37-42", HYUNDAI.TUCSON_HYBRID_4TH_GEN), CarTestRoute("5875672fc1d4bf57|2020-07-23--21-33-28", HYUNDAI.KIA_SORENTO), + CarTestRoute("1d0d000db3370fd0|2023-01-04--22-28-42", HYUNDAI.KIA_SORENTO_4TH_GEN, segment=5), + CarTestRoute("628935d7d3e5f4f7|2022-11-30--01-12-46", HYUNDAI.KIA_SORENTO_PHEV_4TH_GEN), CarTestRoute("9c917ba0d42ffe78|2020-04-17--12-43-19", HYUNDAI.PALISADE), - CarTestRoute("22de8111a8c5463c|2022-07-29--13-34-49", HYUNDAI.IONIQ_5), + CarTestRoute("05a8f0197fdac372|2022-10-19--14-14-09", HYUNDAI.IONIQ_5), # HDA2 CarTestRoute("3f29334d6134fcd4|2022-03-30--22-00-50", HYUNDAI.IONIQ_PHEV_2019), CarTestRoute("fa8db5869167f821|2021-06-10--22-50-10", HYUNDAI.IONIQ_PHEV), CarTestRoute("2c5cf2dd6102e5da|2020-12-17--16-06-44", HYUNDAI.IONIQ_EV_2020), @@ -109,15 +122,20 @@ routes = [ CarTestRoute("ff973b941a69366f|2022-07-28--22-01-19", HYUNDAI.KONA_EV_2022, segment=11), CarTestRoute("49f3c13141b6bc87|2021-07-28--08-05-13", HYUNDAI.KONA_HEV), CarTestRoute("5dddcbca6eb66c62|2020-07-26--13-24-19", HYUNDAI.KIA_STINGER), + CarTestRoute("5b50b883a4259afb|2022-11-09--15-00-42", HYUNDAI.KIA_STINGER_2022), CarTestRoute("d624b3d19adce635|2020-08-01--14-59-12", HYUNDAI.VELOSTER), - CarTestRoute("d824e27e8c60172c|2022-05-19--16-15-28", HYUNDAI.KIA_EV6), # HDA2 + CarTestRoute("d545129f3ca90f28|2022-10-19--09-22-54", HYUNDAI.KIA_EV6), # HDA2 CarTestRoute("68d6a96e703c00c9|2022-09-10--16-09-39", HYUNDAI.KIA_EV6), # HDA1 CarTestRoute("007d5e4ad9f86d13|2021-09-30--15-09-23", HYUNDAI.KIA_K5_2021), + CarTestRoute("c58dfc9fc16590e0|2023-01-14--13-51-48", HYUNDAI.KIA_K5_HEV_2020), CarTestRoute("50c6c9b85fd1ff03|2020-10-26--17-56-06", HYUNDAI.KIA_NIRO_EV), CarTestRoute("173219cf50acdd7b|2021-07-05--10-27-41", HYUNDAI.KIA_NIRO_PHEV), CarTestRoute("34a875f29f69841a|2021-07-29--13-02-09", HYUNDAI.KIA_NIRO_HEV_2021), + CarTestRoute("db04d2c63990e3ba|2023-02-08--16-52-39", HYUNDAI.KIA_NIRO_HEV_2ND_GEN), CarTestRoute("50a2212c41f65c7b|2021-05-24--16-22-06", HYUNDAI.KIA_FORTE), + CarTestRoute("192283cdbb7a58c2|2022-10-15--01-43-18", HYUNDAI.KIA_SPORTAGE_5TH_GEN), CarTestRoute("c5ac319aa9583f83|2021-06-01--18-18-31", HYUNDAI.ELANTRA), + CarTestRoute("734ef96182ddf940|2022-10-02--16-41-44", HYUNDAI.ELANTRA), # 2019 Elantra GT CarTestRoute("82e9cdd3f43bf83e|2021-05-15--02-42-51", HYUNDAI.ELANTRA_2021), CarTestRoute("715ac05b594e9c59|2021-06-20--16-21-07", HYUNDAI.ELANTRA_HEV_2021), CarTestRoute("7120aa90bbc3add7|2021-08-02--07-12-31", HYUNDAI.SONATA_HYBRID), @@ -166,12 +184,15 @@ routes = [ CarTestRoute("0a0de17a1e6a2d15|2020-09-21--21-24-41", TOYOTA.PRIUS_TSS2), CarTestRoute("9b36accae406390e|2021-03-30--10-41-38", TOYOTA.MIRAI), CarTestRoute("cd9cff4b0b26c435|2021-05-13--15-12-39", TOYOTA.CHR), + CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2), CarTestRoute("57858ede0369a261|2021-05-18--20-34-20", TOYOTA.CHRH), + CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHRH_TSS2), CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), CarTestRoute("2c68dda277d887ac|2021-05-11--15-22-20", VOLKSWAGEN.ATLAS_MK1), - CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), + CarTestRoute("cae14e88932eb364|2021-03-26--14-43-28", VOLKSWAGEN.GOLF_MK7), # Stock ACC + CarTestRoute("3cfdec54aa035f3f|2022-10-13--14-58-58", VOLKSWAGEN.GOLF_MK7), # openpilot longitudinal CarTestRoute("58a7d3b707987d65|2021-03-25--17-26-37", VOLKSWAGEN.JETTA_MK7), CarTestRoute("4d134e099430fba2|2021-03-26--00-26-06", VOLKSWAGEN.PASSAT_MK8), CarTestRoute("3cfdec54aa035f3f|2022-07-19--23-45-10", VOLKSWAGEN.PASSAT_NMS), @@ -188,6 +209,7 @@ routes = [ CarTestRoute("0cd0b7f7e31a3853|2021-12-03--03-12-05", VOLKSWAGEN.AUDI_Q3_MK2), CarTestRoute("8f205bdd11bcbb65|2021-03-26--01-00-17", VOLKSWAGEN.SEAT_ATECA_MK1), CarTestRoute("fc6b6c9a3471c846|2021-05-27--13-39-56", VOLKSWAGEN.SEAT_LEON_MK3), + CarTestRoute("0bbe367c98fa1538|2023-03-04--17-46-11", VOLKSWAGEN.SKODA_FABIA_MK4), CarTestRoute("12d6ae3057c04b0d|2021-09-15--00-04-07", VOLKSWAGEN.SKODA_KAMIQ_MK1), CarTestRoute("12d6ae3057c04b0d|2021-09-04--21-21-21", VOLKSWAGEN.SKODA_KAROQ_MK1), CarTestRoute("90434ff5d7c8d603|2021-03-15--12-07-31", VOLKSWAGEN.SKODA_KODIAQ_MK1), diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index aabf652c8c..ac8213e4c1 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -25,38 +25,50 @@ class TestCarInterfaces(unittest.TestCase): car_fw = [] - car_params = CarInterface.get_params(car_name, fingerprints, car_fw) + car_params = CarInterface.get_params(car_name, fingerprints, car_fw, experimental_long=False) car_interface = CarInterface(car_params, CarController, CarState) assert car_params assert car_interface self.assertGreater(car_params.mass, 1) + self.assertGreater(car_params.wheelbase, 0) + # centerToFront is center of gravity to front wheels, assert a reasonable range + self.assertTrue(car_params.wheelbase * 0.3 < car_params.centerToFront < car_params.wheelbase * 0.7) self.assertGreater(car_params.maxLateralAccel, 0) + # Longitudinal sanity checks + self.assertEqual(len(car_params.longitudinalTuning.kpV), len(car_params.longitudinalTuning.kpBP)) + self.assertEqual(len(car_params.longitudinalTuning.kiV), len(car_params.longitudinalTuning.kiBP)) + self.assertEqual(len(car_params.longitudinalTuning.deadzoneV), len(car_params.longitudinalTuning.deadzoneBP)) + + # Lateral sanity checks if car_params.steerControlType != car.CarParams.SteerControlType.angle: - tuning = car_params.lateralTuning.which() - if tuning == 'pid': - self.assertTrue(len(car_params.lateralTuning.pid.kpV)) - elif tuning == 'torque': - kf = car_params.lateralTuning.torque.kf - self.assertTrue(not math.isnan(kf) and kf > 0) - self.assertTrue(not math.isnan(car_params.lateralTuning.torque.friction)) - elif tuning == 'indi': - self.assertTrue(len(car_params.lateralTuning.indi.outerLoopGainV)) + tune = car_params.lateralTuning + if tune.which() == 'pid': + self.assertTrue(not math.isnan(tune.pid.kf) and tune.pid.kf > 0) + self.assertTrue(len(tune.pid.kpV) > 0 and len(tune.pid.kpV) == len(tune.pid.kpBP)) + self.assertTrue(len(tune.pid.kiV) > 0 and len(tune.pid.kiV) == len(tune.pid.kiBP)) + + elif tune.which() == 'torque': + self.assertTrue(not math.isnan(tune.torque.kf) and tune.torque.kf > 0) + self.assertTrue(not math.isnan(tune.torque.friction)) + + elif tune.which() == 'indi': + self.assertTrue(len(tune.indi.outerLoopGainV)) # Run car interface CC = car.CarControl.new_message() for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC) - car_interface.apply(CC) + car_interface.apply(CC, 0) + car_interface.apply(CC, 0) CC = car.CarControl.new_message() CC.enabled = True for _ in range(10): car_interface.update(CC, []) - car_interface.apply(CC) - car_interface.apply(CC) + car_interface.apply(CC, 0) + car_interface.apply(CC, 0) # Test radar interface RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface @@ -65,7 +77,7 @@ class TestCarInterfaces(unittest.TestCase): # Run radar interface once radar_interface.update([]) - if not car_params.radarOffCan and radar_interface.rcp is not None and \ + if not car_params.radarUnavailable and radar_interface.rcp is not None and \ hasattr(radar_interface, '_update') and hasattr(radar_interface, 'trigger_msg'): radar_interface._update([radar_interface.trigger_msg]) diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index b7056df5b3..e56f98f7a8 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -10,8 +10,9 @@ from selfdrive.car.honda.values import CAR as HONDA class TestCarDocs(unittest.TestCase): - def setUp(self): - self.all_cars = get_all_car_info() + @classmethod + def setUpClass(cls): + cls.all_cars = get_all_car_info() def test_generator(self): generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index b9926301f1..e9f23145cd 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 import random import unittest +from collections import defaultdict from parameterized import parameterized from cereal import car @@ -44,6 +45,16 @@ class TestFwFingerprint(unittest.TestCase): duplicates = {fw for fw in ecu_fw if ecu_fw.count(fw) > 1} self.assertFalse(len(duplicates), f"{car_model}: Duplicate FW versions: Ecu.{ECU_NAME[ecu[0]]}, {duplicates}") + def test_all_addrs_map_to_one_ecu(self): + for brand, cars in VERSIONS.items(): + addr_to_ecu = defaultdict(set) + for ecus in cars.values(): + for ecu_type, addr, sub_addr in ecus.keys(): + addr_to_ecu[(addr, sub_addr)].add(ecu_type) + ecus_for_addr = addr_to_ecu[(addr, sub_addr)] + ecu_strings = ", ".join([f'Ecu.{ECU_NAME[ecu]}' for ecu in ecus_for_addr]) + self.assertLessEqual(len(ecus_for_addr), 1, f"{brand} has multiple ECUs that map to one address: {ecu_strings} -> ({hex(addr)}, {sub_addr})") + def test_data_collection_ecus(self): # Asserts no extra ECUs are in the fingerprinting database for brand, config in FW_QUERY_CONFIGS.items(): @@ -56,7 +67,7 @@ class TestFwFingerprint(unittest.TestCase): blacklisted_addrs = (0x7c4, 0x7d0) # includes A/C ecu and an unknown ecu for car_model, ecus in FW_VERSIONS.items(): with self.subTest(car_model=car_model): - CP = interfaces[car_model][0].get_params(car_model) + CP = interfaces[car_model][0].get_non_essential_params(car_model) if CP.carName == 'subaru': for ecu in ecus.keys(): self.assertNotIn(ecu[1], blacklisted_addrs, f'{car_model}: Blacklisted ecu: (Ecu.{ECU_NAME[ecu[0]]}, {hex(ecu[1])})') diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py new file mode 100755 index 0000000000..d3e4de1798 --- /dev/null +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +from collections import defaultdict +import importlib +from parameterized import parameterized_class +import sys +from typing import DefaultDict, Dict +import unittest + +from common.realtime import DT_CTRL +from selfdrive.car.car_helpers import interfaces +from selfdrive.car.fingerprints import all_known_cars +from selfdrive.car.interfaces import get_torque_params + +CAR_MODELS = all_known_cars() + +# ISO 11270 +MAX_LAT_JERK = 2.5 # m/s^3 +MAX_LAT_JERK_TOLERANCE = 0.75 # m/s^3 +MAX_LAT_ACCEL = 3.0 # m/s^2 + +# jerk is measured over half a second +JERK_MEAS_FRAMES = 0.5 / DT_CTRL + +car_model_jerks: DefaultDict[str, Dict[str, float]] = defaultdict(dict) + + +@parameterized_class('car_model', [(c,) for c in CAR_MODELS]) +class TestLateralLimits(unittest.TestCase): + car_model: str + + @classmethod + def setUpClass(cls): + CarInterface, _, _ = interfaces[cls.car_model] + CP = CarInterface.get_non_essential_params(cls.car_model) + + if CP.dashcamOnly: + raise unittest.SkipTest("Platform is behind dashcamOnly") + + # TODO: test all platforms + if CP.lateralTuning.which() != 'torque': + raise unittest.SkipTest + + if CP.notCar: + raise unittest.SkipTest + + CarControllerParams = importlib.import_module(f'selfdrive.car.{CP.carName}.values').CarControllerParams + cls.control_params = CarControllerParams(CP) + cls.torque_params = get_torque_params(cls.car_model) + + @staticmethod + def calculate_0_5s_jerk(control_params, torque_params): + steer_step = control_params.STEER_STEP + steer_up_per_frame = (control_params.STEER_DELTA_UP / control_params.STEER_MAX) / steer_step + steer_down_per_frame = (control_params.STEER_DELTA_DOWN / control_params.STEER_MAX) / steer_step + + steer_up_0_5_sec = min(steer_up_per_frame * JERK_MEAS_FRAMES, 1.0) + steer_down_0_5_sec = min(steer_down_per_frame * JERK_MEAS_FRAMES, 1.0) + + max_lat_accel = torque_params['MAX_LAT_ACCEL_MEASURED'] + return steer_up_0_5_sec * max_lat_accel, steer_down_0_5_sec * max_lat_accel + + def test_jerk_limits(self): + up_jerk, down_jerk = self.calculate_0_5s_jerk(self.control_params, self.torque_params) + car_model_jerks[self.car_model] = {"up_jerk": up_jerk, "down_jerk": down_jerk} + self.assertLessEqual(up_jerk, MAX_LAT_JERK + MAX_LAT_JERK_TOLERANCE) + self.assertLessEqual(down_jerk, MAX_LAT_JERK + MAX_LAT_JERK_TOLERANCE) + + def test_max_lateral_accel(self): + self.assertLessEqual(self.torque_params["MAX_LAT_ACCEL_MEASURED"], MAX_LAT_ACCEL) + + +if __name__ == "__main__": + result = unittest.main(exit=False) + + print(f"\n\n---- Lateral limit report ({len(CAR_MODELS)} cars) ----\n") + + max_car_model_len = max([len(car_model) for car_model in car_model_jerks]) + for car_model, _jerks in sorted(car_model_jerks.items(), key=lambda i: i[1]['up_jerk'], reverse=True): + violation = any([_jerk >= MAX_LAT_JERK + MAX_LAT_JERK_TOLERANCE for _jerk in _jerks.values()]) + violation_str = " - VIOLATION" if violation else "" + + print(f"{car_model:{max_car_model_len}} - up jerk: {round(_jerks['up_jerk'], 2):5} m/s^3, down jerk: {round(_jerks['down_jerk'], 2):5} m/s^3{violation_str}") + + # exit with test result + sys.exit(not result.result.wasSuccessful()) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index e4e141153f..6fbe1436f1 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -9,7 +9,6 @@ from parameterized import parameterized_class from cereal import log, car from common.realtime import DT_CTRL -from selfdrive.boardd.boardd import can_capnp_to_can_list, can_list_to_can_capnp from selfdrive.car.fingerprints import all_known_cars from selfdrive.car.car_helpers import interfaces from selfdrive.car.gm.values import CAR as GM @@ -20,8 +19,7 @@ from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader from tools.lib.route import Route -from panda.tests.safety import libpandasafety_py -from panda.tests.safety.common import package_can_msg +from panda.tests.libpanda import libpanda_py PandaType = log.PandaState.PandaType @@ -109,12 +107,16 @@ class TestCarModelBase(unittest.TestCase): assert cls.CP assert cls.CP.carFingerprint == cls.car_model + @classmethod + def tearDownClass(cls): + del cls.can_msgs + def setUp(self): self.CI = self.CarInterface(self.CP, self.CarController, self.CarState) assert self.CI # TODO: check safetyModel is in release panda build - self.safety = libpandasafety_py.libpandasafety + self.safety = libpanda_py.libpanda cfg = self.CP.safetyConfigs[-1] set_status = self.safety.set_safety_hooks(cfg.safetyModel.raw, cfg.safetyParam) @@ -147,7 +149,7 @@ class TestCarModelBase(unittest.TestCase): for i, msg in enumerate(self.can_msgs): CS = self.CI.update(CC, (msg.as_builder().to_bytes(),)) - self.CI.apply(CC) + self.CI.apply(CC, msg.logMonoTime) if CS.canValid: can_valid = True @@ -188,7 +190,7 @@ class TestCarModelBase(unittest.TestCase): if msg.src >= 64: continue - to_send = package_can_msg([msg.address, 0, msg.dat, msg.src % 4]) + to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) if self.safety.safety_rx_hook(to_send) != 1: failed_addrs[hex(msg.address)] += 1 @@ -210,41 +212,43 @@ class TestCarModelBase(unittest.TestCase): # warm up pass, as initial states may be different for can in self.can_msgs[:300]: - for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): - to_send = package_can_msg(msg) + self.CI.update(CC, (can.as_builder().to_bytes(), )) + for msg in filter(lambda m: m.src in range(64), can.can): + to_send = libpanda_py.make_CANPacket(msg.address, msg.src, msg.dat) self.safety.safety_rx_hook(to_send) - self.CI.update(CC, (can_list_to_can_capnp([msg, ]), )) - - if not self.CP.pcmCruise: - self.safety.set_controls_allowed(0) controls_allowed_prev = False CS_prev = car.CarState.new_message() checks = defaultdict(lambda: 0) - for can in self.can_msgs: + for idx, can in enumerate(self.can_msgs): CS = self.CI.update(CC, (can.as_builder().to_bytes(), )) - for msg in can_capnp_to_can_list(can.can, src_filter=range(64)): - msg = list(msg) - msg[3] %= 4 - to_send = package_can_msg(msg) + for msg in filter(lambda m: m.src in range(64), can.can): + to_send = libpanda_py.make_CANPacket(msg.address, msg.src % 4, msg.dat) ret = self.safety.safety_rx_hook(to_send) self.assertEqual(1, ret, f"safety rx failed ({ret=}): {to_send}") + # Skip first frame so CS_prev is properly initialized + if idx == 0: + CS_prev = CS + # Button may be left pressed in warm up period + if not self.CP.pcmCruise: + self.safety.set_controls_allowed(0) + continue + # TODO: check rest of panda's carstate (steering, ACC main on, etc.) checks['gasPressed'] += CS.gasPressed != self.safety.get_gas_pressed_prev() - checks['cruiseState'] += CS.cruiseState.enabled and not CS.cruiseState.available - if self.CP.carName not in ("hyundai", "volkswagen", "body"): + if self.CP.carName not in ("hyundai", "body"): # TODO: fix standstill mismatches for other makes checks['standstill'] += CS.standstill == self.safety.get_vehicle_moving() # TODO: remove this exception once this mismatch is resolved brake_pressed = CS.brakePressed if CS.brakePressed and not self.safety.get_brake_pressed_prev(): - if self.CP.carFingerprint in (HONDA.PILOT, HONDA.PASSPORT, HONDA.RIDGELINE) and CS.brake > 0.05: + if self.CP.carFingerprint in (HONDA.PILOT, HONDA.RIDGELINE) and CS.brake > 0.05: brake_pressed = False - safety_brake_pressed = self.safety.get_brake_pressed_prev() or self.safety.get_regen_braking_prev() - checks['brakePressed'] += brake_pressed != safety_brake_pressed + checks['brakePressed'] += brake_pressed != self.safety.get_brake_pressed_prev() + checks['regenBraking'] += CS.regenBraking != self.safety.get_regen_braking_prev() if self.CP.pcmCruise: # On most pcmCruise cars, openpilot's state is always tied to the PCM's cruise state. diff --git a/selfdrive/car/torque_data/override.yaml b/selfdrive/car/torque_data/override.yaml index 889eeffb25..75051316fa 100644 --- a/selfdrive/car/torque_data/override.yaml +++ b/selfdrive/car/torque_data/override.yaml @@ -7,31 +7,43 @@ NISSAN LEAF 2018 Instrument Cluster: [.nan, 1.5, .nan] NISSAN LEAF 2018: [.nan, 1.5, .nan] NISSAN ROGUE 2019: [.nan, 1.5, .nan] +# Toyota LTA also has torque +TOYOTA RAV4 2023: [.nan, 3.0, .nan] +TOYOTA RAV4 HYBRID 2023: [.nan, 3.0, .nan] + # Tesla has high torque TESLA AP1 MODEL S: [.nan, 2.5, .nan] TESLA AP2 MODEL S: [.nan, 2.5, .nan] # Guess +FORD BRONCO SPORT 1ST GEN: [.nan, 1.5, .nan] FORD ESCAPE 4TH GEN: [.nan, 1.5, .nan] FORD EXPLORER 6TH GEN: [.nan, 1.5, .nan] FORD FOCUS 4TH GEN: [.nan, 1.5, .nan] +FORD MAVERICK 1ST GEN: [.nan, 1.5, .nan] ### # No steering wheel COMMA BODY: [.nan, 1000, .nan] # Totally new cars -KIA EV6 2022: [3.5, 3.0, 0.0] RAM 1500 5TH GEN: [2.0, 2.0, 0.0] RAM HD 5TH GEN: [1.4, 1.4, 0.0] SUBARU OUTBACK 6TH GEN: [2.3, 2.3, 0.11] -CHEVROLET BOLT EV 2022: [2.0, 2.0, 0.05] +CADILLAC ESCALADE 2017: [1.899999976158142, 1.842270016670227, 0.1120000034570694] CHEVROLET BOLT EUV 2022: [2.0, 2.0, 0.05] CHEVROLET SILVERADO 1500 2020: [1.9, 1.9, 0.112] CHEVROLET EQUINOX 2019: [2.0, 2.0, 0.05] VOLKSWAGEN PASSAT NMS: [2.5, 2.5, 0.1] VOLKSWAGEN SHARAN 2ND GEN: [2.5, 2.5, 0.1] -HYUNDAI TUCSON HYBRID 4TH GEN: [2.5, 2.5, 0.0] +HYUNDAI SANTA CRUZ 1ST GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE 5TH GEN: [2.7, 2.7, 0.1] +KIA SPORTAGE HYBRID 5TH GEN: [2.5, 2.5, 0.1] +GENESIS GV70 1ST GEN: [2.42, 2.42, 0.1] +KIA SORENTO PLUG-IN HYBRID 4TH GEN: [2.5, 2.5, 0.1] +GENESIS GV60 ELECTRIC 1ST GEN: [2.5, 2.5, 0.1] +KIA SORENTO 4TH GEN: [2.5, 2.5, 0.1] +KIA NIRO HYBRID 2ND GEN: [2.42, 2.5, 0.12] # Dashcam or fallback configured as ideal car mock: [10.0, 10, 0.0] diff --git a/selfdrive/car/torque_data/params.yaml b/selfdrive/car/torque_data/params.yaml index 160f605488..cb423e1d87 100644 --- a/selfdrive/car/torque_data/params.yaml +++ b/selfdrive/car/torque_data/params.yaml @@ -4,11 +4,11 @@ ACURA RDX 2020: [1.4314459806646749, 0.33874701282109954, 0.18048847083897598] AUDI A3 3RD GEN: [1.5122414863077502, 1.7443517531719404, 0.15194151892450905] AUDI Q3 2ND GEN: [1.4439223359448605, 1.2254955789112076, 0.1413798895978097] CHEVROLET VOLT PREMIER 2017: [1.5961527626411784, 1.8422651988094612, 0.1572393918005158] -CHRYSLER PACIFICA 2018: [1.593387270257916, 1.3366521181047952, 0.13776367250652022] -CHRYSLER PACIFICA 2020: [1.4323553627965695, 1.509076559398423, 0.14328246159386085] -CHRYSLER PACIFICA HYBRID 2017: [1.3032470208409048, 1.06831764583744, 0.13287170990024627] -CHRYSLER PACIFICA HYBRID 2018: [1.6068280248761635, 1.2943025830995154, 0.1358557824293823] -CHRYSLER PACIFICA HYBRID 2019: [1.4624643614072217, 1.1958788168371808, 0.15748488008472716] +CHRYSLER PACIFICA 2018: [2.07140, 1.3366521181047952, 0.13776367250652022] +CHRYSLER PACIFICA 2020: [1.86206, 1.509076559398423, 0.14328246159386085] +CHRYSLER PACIFICA HYBRID 2017: [1.79422, 1.06831764583744, 0.116237] +CHRYSLER PACIFICA HYBRID 2018: [2.08887, 1.2943025830995154, 0.114818] +CHRYSLER PACIFICA HYBRID 2019: [1.90120, 1.1958788168371808, 0.131520] GENESIS G70 2018: [3.8520195946707947, 2.354697063349854, 0.06830285485626221] GMC ACADIA DENALI 2018: [1.3181430320331884, 1.1853735340610179, 0.3450592280031644] HONDA ACCORD 2018: [1.7135052593468778, 0.3461280068322071, 0.21579936052863807] @@ -22,34 +22,37 @@ HONDA FIT 2018: [1.5719981427109775, 0.5712761407108976, 0.110773383324281] HONDA HRV 2019: [2.0661212805710205, 0.7521343418694775, 0.17760375789242094] HONDA INSIGHT 2019: [1.5201671214069354, 0.5660229120683284, 0.25808042580281876] HONDA ODYSSEY 2018: [1.8774809275211801, 0.8394431662987996, 0.2096978613792822] -HONDA PASSPORT 2021: [1.5305538930036766, 0.7956063674638759, 0.19599407381531284] HONDA PILOT 2017: [1.7262026201812795, 0.9470005614967523, 0.21351430733218763] HONDA RIDGELINE 2017: [1.4146525028237624, 0.7356572861629564, 0.23307177552211328] -HYUNDAI GENESIS 2015-2016: [1.8466226943929824, 1.5552063647830634, 0.0984484465421171] +HYUNDAI ELANTRA 2021: [3.169, 2.1259108157250735, 0.0819] +HYUNDAI GENESIS 2015-2016: [2.7807965280270794, 2.325, 0.0984484465421171] +HYUNDAI IONIQ 5 2022: [3.172929, 2.713050, 0.096019] HYUNDAI IONIQ ELECTRIC LIMITED 2019: [1.7662975472852054, 1.613755614526594, 0.17087579756306276] HYUNDAI IONIQ PHEV 2020: [3.2928700076638537, 2.1193482926455656, 0.12463700961468778] HYUNDAI IONIQ PLUG-IN HYBRID 2019: [2.970807902012267, 1.6312321830002083, 0.1088964990357482] -HYUNDAI KONA ELECTRIC 2019: [4.398306735170212, 3.2961956260770484, 0.08651833437845884] +HYUNDAI KONA ELECTRIC 2019: [3.078814714619148, 2.307336938253934, 0.12359762054065548] HYUNDAI PALISADE 2020: [2.544642494803999, 1.8721703683337008, 0.1301424599248651] HYUNDAI SANTA FE 2019: [3.0787027729757632, 2.6173437483495565, 0.1207019341823945] HYUNDAI SANTA FE HYBRID 2022: [3.501877602644835, 2.729064118456137, 0.10384068104538963] HYUNDAI SANTA FE PlUG-IN HYBRID 2022: [1.6953050513611045, 1.5837614296206861, 0.12672855941458458] HYUNDAI SONATA 2019: [2.2200457811703953, 1.2967330275895228, 0.14039920986586393] -HYUNDAI SONATA 2020: [3.284505627881726, 2.1259108157250735, 0.08452048323586728] +HYUNDAI SONATA 2020: [2.9638737459977467, 2.1259108157250735, 0.07813665616927593] HYUNDAI SONATA HYBRID 2021: [2.8990264092395734, 2.061410192222139, 0.0899805488717382] -JEEP GRAND CHEROKEE 2019: [1.7321233388827006, 1.289689569171081, 0.15046331002097185] -JEEP GRAND CHEROKEE V6 2018: [1.8776598027756923, 1.4057367824262523, 0.11725947414922003] +HYUNDAI TUCSON HYBRID 4TH GEN: [2.035545, 2.035545, 0.110272] +JEEP GRAND CHEROKEE 2019: [2.30972, 1.289689569171081, 0.117048] +JEEP GRAND CHEROKEE V6 2018: [2.27116, 1.4057367824262523, 0.11725947414922003] +KIA EV6 2022: [3.2, 2.093457, 0.05] KIA K5 2021: [2.405339728085138, 1.460032270828705, 0.11650989850813716] KIA NIRO EV 2020: [2.9215954981365337, 2.1500583840260044, 0.09236802474810267] KIA SORENTO GT LINE 2018: [2.464854685101844, 1.5335274218367956, 0.12056170567599558] KIA STINGER GT2 2018: [2.7499043387418967, 1.849652021986449, 0.12048334239559202] -LEXUS ES 2019: [2.0203086922726112, 2.134803912579666, 0.12757526789308554] -LEXUS ES HYBRID 2019: [2.392442298703042, 1.863360677810788, 0.17690002108856212] +LEXUS ES 2019: [1.935835, 2.134803912579666, 0.093439] +LEXUS ES HYBRID 2019: [2.135678, 1.863360677810788, 0.109627] LEXUS NX 2018: [2.302625600642627, 2.1382378491466625, 0.14986840878892838] LEXUS NX 2020: [2.4331999786982936, 2.1045680431705414, 0.14099899317761067] LEXUS NX HYBRID 2018: [2.4025593501080955, 1.8080446063815507, 0.15349361249519017] LEXUS RX 2016: [1.5876816543130423, 1.0427699298523752, 0.21334066732397142] -LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.2093316728710659] +LEXUS RX 2020: [1.5228812994274734, 1.431102486563665, 0.164117] LEXUS RX HYBRID 2017: [1.6984261557042386, 1.3211501880159107, 0.1820354534928893] LEXUS RX HYBRID 2020: [1.5522309889823778, 1.255230465866663, 0.2220954003055114] MAZDA CX-9 2021: [1.7601682915983443, 1.0889677335154337, 0.17713792194297195] @@ -62,31 +65,31 @@ TOYOTA AVALON 2019: [1.7036141952825095, 1.239619084240008, 0.08459830394899492] TOYOTA AVALON 2022: [2.3154403649717357, 2.7777922854327124, 0.11453999639164605] TOYOTA C-HR 2018: [1.5591084333664578, 1.271271459066948, 0.20259087058453193] TOYOTA C-HR 2021: [1.7678810166088303, 1.3742176337919942, 0.2319674583741509] -TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.13519250664782062] -TOYOTA CAMRY 2021: [2.6922769557433055, 2.3476510120007434, 0.1450430192989234] -TOYOTA CAMRY HYBRID 2018: [2.0974120828287774, 1.7996193116697359, 0.13823613467632756] -TOYOTA CAMRY HYBRID 2021: [2.6426668350384457, 2.3901492458927986, 0.16103875108816076] +TOYOTA CAMRY 2018: [2.1172995371905015, 1.7156177222420887, 0.105192506] +TOYOTA CAMRY 2021: [2.446083, 2.3476510120007434, 0.121615] +TOYOTA CAMRY HYBRID 2018: [1.996333, 1.7996193116697359, 0.112565] +TOYOTA CAMRY HYBRID 2021: [2.263582, 2.3901492458927986, 0.115257] TOYOTA COROLLA 2017: [3.117154369115421, 1.8438132575043773, 0.12289685869250652] -TOYOTA COROLLA HYBRID TSS2 2019: [2.3287672277252005, 1.8118712531729109, 0.2215868445753317] -TOYOTA COROLLA TSS2 2019: [2.4204464833010175, 1.9258612322678952, 0.20670411068012526] +TOYOTA COROLLA HYBRID TSS2 2019: [1.9079729107361805, 1.8118712531729109, 0.22251440891543514] +TOYOTA COROLLA TSS2 2019: [2.0742917676766712, 1.9258612322678952, 0.16888685704519352] TOYOTA HIGHLANDER 2017: [1.8696367437248915, 1.626293990451463, 0.17485372210240796] TOYOTA HIGHLANDER 2020: [2.022340166827233, 1.6183134804881791, 0.14592306380054457] -TOYOTA HIGHLANDER HYBRID 2018: [1.9421825202382728, 1.6433903296845025, 0.16928956792275918] -TOYOTA HIGHLANDER HYBRID 2020: [2.103373061114133, 2.104015182965606, 0.14447040132184993] +TOYOTA HIGHLANDER HYBRID 2018: [1.752033, 1.6433903296845025, 0.144600] +TOYOTA HIGHLANDER HYBRID 2020: [1.901174, 2.104015182965606, 0.14447040132184993] TOYOTA MIRAI 2021: [2.506899832157829, 1.7417213930750164, 0.20182618449440565] -TOYOTA PRIUS 2017: [2.0183401513314294, 1.5023147650693636, 0.20856908464957724] -TOYOTA PRIUS TSS2 2021: [2.327639738920072, 1.9104337425537743, 0.2030762265549664] +TOYOTA PRIUS 2017: [1.60, 1.5023147650693636, 0.151515] +TOYOTA PRIUS TSS2 2021: [1.972600, 1.9104337425537743, 0.170968] TOYOTA RAV4 2017: [2.085695074355425, 2.2142832316984733, 0.13339165270103975] -TOYOTA RAV4 2019: [2.5038362866776835, 2.0993589721530252, 0.1552425356342368] +TOYOTA RAV4 2019: [2.331293, 2.0993589721530252, 0.129822] TOYOTA RAV4 2019 8965: [2.5084506298290377, 2.4216520504763475, 0.11992835265067918] TOYOTA RAV4 2019 x02: [2.7209621987605024, 2.2148637653781593, 0.10862567142268198] TOYOTA RAV4 HYBRID 2017: [1.9796257271652042, 1.7503987331707576, 0.14628860048885406] TOYOTA RAV4 HYBRID 2019: [2.2271858492309153, 2.074844961405639, 0.14382216826893632] TOYOTA RAV4 HYBRID 2019 8965: [2.1077397198131336, 1.8162215166877735, 0.13891369391200137] TOYOTA RAV4 HYBRID 2019 x02: [2.803624333289342, 2.272367966572498, 0.11364569214387774] -TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.1565442715453653] +TOYOTA RAV4 HYBRID 2022: [2.241883248393209, 1.9304407208090029, 0.112174] TOYOTA RAV4 HYBRID 2022 x02: [3.044930631831037, 2.3979189796380918, 0.14023209146703736] -TOYOTA SIENNA 2018: [1.8660896232147548, 1.3208264576110418, 0.18799149615227198] +TOYOTA SIENNA 2018: [1.689726, 1.3208264576110418, 0.140456] VOLKSWAGEN ARTEON 1ST GEN: [1.45136518053819, 1.3639364049316804, 0.23806361745695032] VOLKSWAGEN ATLAS 1ST GEN: [1.4677006726964945, 1.6733266634075656, 0.12959584092073367] VOLKSWAGEN GOLF 7TH GEN: [1.3750394140491293, 1.5814743077200641, 0.2018321939386586] diff --git a/selfdrive/car/torque_data/substitute.yaml b/selfdrive/car/torque_data/substitute.yaml index f5e3d1d61d..5feef12206 100644 --- a/selfdrive/car/torque_data/substitute.yaml +++ b/selfdrive/car/torque_data/substitute.yaml @@ -9,6 +9,7 @@ TOYOTA ALPHARD 2020: TOYOTA SIENNA 2018 TOYOTA PRIUS v 2017 : TOYOTA PRIUS 2017 TOYOTA RAV4 2022: TOYOTA RAV4 HYBRID 2022 TOYOTA C-HR HYBRID 2018: TOYOTA C-HR 2018 +TOYOTA C-HR HYBRID 2022: TOYOTA C-HR 2021 LEXUS IS 2018: LEXUS NX 2018 LEXUS CT HYBRID 2018 : LEXUS NX 2018 LEXUS ES HYBRID 2018: TOYOTA CAMRY HYBRID 2018 @@ -26,19 +27,19 @@ KIA SELTOS 2021: HYUNDAI SONATA 2020 KIA NIRO HYBRID 2019: KIA NIRO EV 2020 KIA NIRO HYBRID 2021: KIA NIRO EV 2020 HYUNDAI VELOSTER 2019: HYUNDAI SONATA 2019 -HYUNDAI I30 N LINE 2019 & GT 2018 DCT: HYUNDAI SONATA 2019 HYUNDAI KONA 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA HYBRID 2020: HYUNDAI KONA ELECTRIC 2019 HYUNDAI KONA ELECTRIC 2022: HYUNDAI KONA ELECTRIC 2019 -HYUNDAI IONIQ 5 2022: KIA EV6 2022 HYUNDAI IONIQ HYBRID 2017-2019: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ HYBRID 2020-2022: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI IONIQ ELECTRIC 2020: HYUNDAI IONIQ PLUG-IN HYBRID 2019 HYUNDAI ELANTRA 2017: HYUNDAI SONATA 2019 HYUNDAI ELANTRA HYBRID 2021: HYUNDAI SONATA 2020 -HYUNDAI ELANTRA 2021: HYUNDAI SONATA 2020 HYUNDAI TUCSON 2019: HYUNDAI SANTA FE 2019 +HYUNDAI TUCSON 4TH GEN: HYUNDAI TUCSON HYBRID 4TH GEN HYUNDAI SANTA FE 2022: HYUNDAI SANTA FE HYBRID 2022 +KIA K5 HYBRID 2020: KIA K5 2021 +KIA STINGER 2022: KIA STINGER GT2 2018 GENESIS G90 2017: GENESIS G70 2018 GENESIS G80 2017: GENESIS G70 2018 GENESIS G70 2020: HYUNDAI SONATA 2020 @@ -49,17 +50,20 @@ HONDA CIVIC SEDAN 1.6 DIESEL 2019: HONDA CIVIC (BOSCH) 2019 HONDA E 2020: HONDA CIVIC (BOSCH) 2019 HONDA ODYSSEY CHN 2019: HONDA ODYSSEY 2018 +BUICK LACROSSE 2017: CHEVROLET VOLT PREMIER 2017 BUICK REGAL ESSENCE 2018: CHEVROLET VOLT PREMIER 2017 CADILLAC ESCALADE ESV 2016: CHEVROLET VOLT PREMIER 2017 CADILLAC ATS Premium Performance 2018: CHEVROLET VOLT PREMIER 2017 CHEVROLET MALIBU PREMIER 2017: CHEVROLET VOLT PREMIER 2017 HOLDEN ASTRA RS-V BK 2017: CHEVROLET VOLT PREMIER 2017 +SKODA FABIA 4TH GEN: VOLKSWAGEN GOLF 7TH GEN SKODA OCTAVIA 3RD GEN: SKODA SUPERB 3RD GEN SKODA SCALA 1ST GEN: SKODA SUPERB 3RD GEN SKODA KODIAQ 1ST GEN: SKODA SUPERB 3RD GEN SKODA KAROQ 1ST GEN: SKODA SUPERB 3RD GEN SKODA KAMIQ 1ST GEN: SKODA SUPERB 3RD GEN +VOLKSWAGEN CRAFTER 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN T-ROC 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN T-CROSS 1ST GEN: VOLKSWAGEN TIGUAN 2ND GEN VOLKSWAGEN TOURAN 2ND GEN: VOLKSWAGEN TIGUAN 2ND GEN diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index faea08ed3f..66a7b57f01 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -1,11 +1,12 @@ from cereal import car from common.numpy_fast import clip, interp -from selfdrive.car import apply_toyota_steer_torque_limits, create_gas_interceptor_command, make_can_msg +from selfdrive.car import apply_meas_steer_torque_limits, create_gas_interceptor_command, make_can_msg from selfdrive.car.toyota.toyotacan import create_steer_command, create_ui_command, \ create_accel_command, create_acc_cancel_command, \ create_fcw_command, create_lta_steer_command from selfdrive.car.toyota.values import CAR, STATIC_DSU_MSGS, NO_STOP_TIMER_CAR, TSS2_CAR, \ - MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams + MIN_ACC_SPEED, PEDAL_TRANSITION, CarControllerParams, \ + UNSUPPORTED_DSU_CAR from opendbc.can.packer import CANPacker VisualAlert = car.CarControl.HUDControl.VisualAlert @@ -33,7 +34,7 @@ class CarController: self.gas = 0 self.accel = 0 - def update(self, CC, CS): + def update(self, CC, CS, now_nanos): actuators = CC.actuators hud_control = CC.hudControl pcm_cancel_cmd = CC.cruiseControl.cancel @@ -59,7 +60,7 @@ class CarController: # steer torque new_steer = int(round(actuators.steer * CarControllerParams.STEER_MAX)) - apply_steer = apply_toyota_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.torque_rate_limits) + apply_steer = apply_meas_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.torque_rate_limits) # Count up to MAX_STEER_RATE_FRAMES, at which point we need to cut torque to avoid a steering fault if lat_active and abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE: @@ -75,6 +76,11 @@ class CarController: apply_steer_req = 0 self.steer_rate_counter = 0 + # Never actuate with LKA on cars that only support LTA + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + apply_steer = 0 + apply_steer_req = 0 + # TODO: probably can delete this. CS.pcm_acc_status uses a different signal # than CS.cruiseState.enabled. confirm they're not meaningfully different if not CC.enabled and CS.pcm_acc_status: @@ -112,7 +118,7 @@ class CarController: lead = hud_control.leadVisible 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 self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if pcm_cancel_cmd and self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: can_sends.append(create_acc_cancel_command(self.packer)) elif self.CP.openpilotLongitudinalControl: can_sends.append(create_accel_command(self.packer, pcm_accel_cmd, pcm_cancel_cmd, self.standstill_req, lead, CS.acc_type)) @@ -142,10 +148,10 @@ class CarController: # forcing the pcm to disengage causes a bad fault sound so play a good sound instead send_ui = True - if self.frame % 100 == 0 or send_ui: + if self.frame % 20 == 0 or send_ui: can_sends.append(create_ui_command(self.packer, steer_alert, pcm_cancel_cmd, hud_control.leftLaneVisible, hud_control.rightLaneVisible, hud_control.leftLaneDepart, - hud_control.rightLaneDepart, CC.enabled)) + hud_control.rightLaneDepart, CC.enabled, CS.lkas_hud)) if (self.frame % 100 == 0 or send_ui) and self.CP.enableDsu: can_sends.append(create_fcw_command(self.packer, fcw_alert)) @@ -157,6 +163,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = apply_steer / CarControllerParams.STEER_MAX + new_actuators.steerOutputCan = apply_steer new_actuators.accel = self.accel new_actuators.gas = self.gas diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 0cfba2b09f..050f8747a2 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -1,3 +1,5 @@ +import copy + from cereal import car from common.conversions import Conversions as CV from common.numpy_fast import mean @@ -6,7 +8,7 @@ from common.realtime import DT_CTRL from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from selfdrive.car.interfaces import CarStateBase -from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE +from selfdrive.car.toyota.values import ToyotaFlags, CAR, DBC, STEER_THRESHOLD, NO_STOP_TIMER_CAR, TSS2_CAR, RADAR_ACC_CAR, EPS_SCALE, UNSUPPORTED_DSU_CAR class CarState(CarStateBase): @@ -15,6 +17,8 @@ class CarState(CarStateBase): can_define = CANDefine(DBC[CP.carFingerprint]["pt"]) self.shifter_values = can_define.dv["GEAR_PACKET"]["GEAR"] self.eps_torque_scale = EPS_SCALE[CP.carFingerprint] / 100. + self.cluster_speed_hyst_gap = CV.KPH_TO_MS / 2. + self.cluster_min_speed = CV.KPH_TO_MS / 2. # On cars with cp.vl["STEER_TORQUE_SENSOR"]["STEER_ANGLE"] # the signal is zeroed to where the steering angle is at start. @@ -24,6 +28,7 @@ class CarState(CarStateBase): self.low_speed_lockout = False self.acc_type = 1 + self.lkas_hud = {} def update(self, cp, cp_cam): ret = car.CarState.new_message() @@ -52,6 +57,7 @@ class CarState(CarStateBase): ) ret.vEgoRaw = mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr]) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) + ret.vEgoCluster = ret.vEgo * 1.015 # minimum of all the cars ret.standstill = ret.vEgoRaw == 0 @@ -82,16 +88,20 @@ class CarState(CarStateBase): ret.steeringTorqueEps = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_EPS"] * self.eps_torque_scale # we could use the override bit from dbc, but it's triggered at too high torque values ret.steeringPressed = abs(ret.steeringTorque) > STEER_THRESHOLD - # steer rate fault, goes to 21 or 25 for 1 frame, then 9 for ~2 seconds - ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 21, 25) + # steer rate fault: goes to 21 or 25 for 1 frame, then 9 for 2 seconds + # lka msg drop out: goes to 9 then 11 for a combined total of 2 seconds + ret.steerFaultTemporary = cp.vl["EPS_STATUS"]["LKA_STATE"] in (0, 9, 11, 21, 25) # 17 is a fault from a prolonged high torque delta between cmd and user - ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] == 17 + # 3 is a fault from the lka command message not being received by the EPS + ret.steerFaultPermanent = cp.vl["EPS_STATUS"]["LKA_STATE"] in (3, 17) - if self.CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if self.CP.carFingerprint in UNSUPPORTED_DSU_CAR: + # TODO: find the bit likely in DSU_CRUISE that describes an ACC fault. one may also exist in CLUTCH ret.cruiseState.available = cp.vl["DSU_CRUISE"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["DSU_CRUISE"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_ALT"]["UI_SET_SPEED"] else: + ret.accFaulted = cp.vl["PCM_CRUISE_2"]["ACC_FAULTED"] != 0 ret.cruiseState.available = cp.vl["PCM_CRUISE_2"]["MAIN_ON"] != 0 ret.cruiseState.speed = cp.vl["PCM_CRUISE_2"]["SET_SPEED"] * CV.KPH_TO_MS cluster_set_speed = cp.vl["PCM_CRUISE_SM"]["UI_SET_SPEED"] @@ -112,16 +122,13 @@ class CarState(CarStateBase): # these cars are identified by an ACC_TYPE value of 2. # TODO: it is possible to avoid the lockout and gain stop and go if you # send your own ACC_CONTROL msg on startup with ACC_TYPE set to 1 - if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in (CAR.LEXUS_IS, CAR.LEXUS_RC)) or \ + if (self.CP.carFingerprint not in TSS2_CAR and self.CP.carFingerprint not in UNSUPPORTED_DSU_CAR) or \ (self.CP.carFingerprint in TSS2_CAR and self.acc_type == 1): self.low_speed_lockout = cp.vl["PCM_CRUISE_2"]["LOW_SPEED_LOCKOUT"] == 2 self.pcm_acc_status = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] - if self.CP.carFingerprint in NO_STOP_TIMER_CAR or self.CP.enableGasInterceptor: - # ignore standstill in hybrid vehicles, since pcm allows to restart without - # receiving any special command. Also if interceptor is detected - ret.cruiseState.standstill = False - else: + if self.CP.carFingerprint not in (NO_STOP_TIMER_CAR - TSS2_CAR): + # ignore standstill state in certain vehicles, since pcm allows to restart with just an acceleration request ret.cruiseState.standstill = self.pcm_acc_status == 7 ret.cruiseState.enabled = bool(cp.vl["PCM_CRUISE"]["CRUISE_ACTIVE"]) ret.cruiseState.nonAdaptive = cp.vl["PCM_CRUISE"]["CRUISE_STATE"] in (1, 2, 3, 4, 5, 6) @@ -136,6 +143,9 @@ class CarState(CarStateBase): ret.leftBlindspot = (cp.vl["BSM"]["L_ADJACENT"] == 1) or (cp.vl["BSM"]["L_APPROACHING"] == 1) ret.rightBlindspot = (cp.vl["BSM"]["R_ADJACENT"] == 1) or (cp.vl["BSM"]["R_APPROACHING"] == 1) + if self.CP.carFingerprint != CAR.PRIUS_V: + self.lkas_hud = copy.copy(cp_cam.vl["LKAS_HUD"]) + return ret @staticmethod @@ -196,7 +206,7 @@ class CarState(CarStateBase): signals.append(("GAS_PEDAL", "GAS_PEDAL")) checks.append(("GAS_PEDAL", 33)) - if CP.carFingerprint in (CAR.LEXUS_IS, CAR.LEXUS_RC): + if CP.carFingerprint in UNSUPPORTED_DSU_CAR: signals.append(("MAIN_ON", "DSU_CRUISE")) signals.append(("SET_SPEED", "DSU_CRUISE")) signals.append(("UI_SET_SPEED", "PCM_CRUISE_ALT")) @@ -205,6 +215,7 @@ class CarState(CarStateBase): else: signals.append(("MAIN_ON", "PCM_CRUISE_2")) signals.append(("SET_SPEED", "PCM_CRUISE_2")) + signals.append(("ACC_FAULTED", "PCM_CRUISE_2")) signals.append(("LOW_SPEED_LOCKOUT", "PCM_CRUISE_2")) checks.append(("PCM_CRUISE_2", 33)) @@ -249,6 +260,18 @@ class CarState(CarStateBase): signals = [] checks = [] + if CP.carFingerprint != CAR.PRIUS_V: + signals += [ + ("LANE_SWAY_FLD", "LKAS_HUD"), + ("LANE_SWAY_BUZZER", "LKAS_HUD"), + ("LANE_SWAY_WARNING", "LKAS_HUD"), + ("LANE_SWAY_SENSITIVITY", "LKAS_HUD"), + ("LANE_SWAY_TOGGLE", "LKAS_HUD"), + ] + checks += [ + ("LKAS_HUD", 1), + ] + if CP.carFingerprint in (TSS2_CAR - RADAR_ACC_CAR): signals += [ ("PRECOLLISION_ACTIVE", "PRE_COLLISION"), diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 28912645ac..33a87451e9 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -2,9 +2,9 @@ from cereal import car from common.conversions import Conversions as CV from panda import Panda -from selfdrive.car.toyota.tunes import LatTunes, LongTunes, set_long_tune, set_lat_tune -from selfdrive.car.toyota.values import Ecu, CAR, ToyotaFlags, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, CarControllerParams -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint, get_safety_config +from selfdrive.car.toyota.values import Ecu, CAR, DBC, ToyotaFlags, CarControllerParams, TSS2_CAR, RADAR_ACC_CAR, NO_DSU_CAR, \ + MIN_ACC_SPEED, EPS_SCALE, EV_HYBRID_CAR, UNSUPPORTED_DSU_CAR, NO_STOP_TIMER_CAR, ANGLE_CONTROL_CAR +from selfdrive.car import STD_CARGO_KG, scale_tire_stiffness, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase EventName = car.CarEvent.EventName @@ -16,23 +16,27 @@ class CarInterface(CarInterfaceBase): return CarControllerParams.ACCEL_MIN, CarControllerParams.ACCEL_MAX @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=[], experimental_long=False): # pylint: disable=dangerous-default-value - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) - + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "toyota" ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.toyota)] ret.safetyConfigs[0].safetyParam = EPS_SCALE[candidate] - if candidate in (CAR.RAV4, CAR.PRIUS_V, CAR.COROLLA, CAR.LEXUS_ESH, CAR.LEXUS_CTH): + # BRAKE_MODULE is on a different address for these cars + if DBC[candidate]["pt"] == "toyota_new_mc_pt_generated": ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_ALT_BRAKE + if candidate in ANGLE_CONTROL_CAR: + ret.dashcamOnly = True + ret.steerControlType = car.CarParams.SteerControlType.angle + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_LTA + else: + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + ret.steerActuatorDelay = 0.12 # Default delay, Prius has larger delay ret.steerLimitTimer = 0.4 ret.stoppingControl = False # Toyota starts braking more when it thinks you want to stop stop_and_go = False - steering_angle_deadzone_deg = 0.0 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) if candidate == CAR.PRIUS: stop_and_go = True @@ -43,8 +47,8 @@ class CarInterface(CarInterfaceBase): # Only give steer angle deadzone to for bad angle sensor prius for fw in car_fw: if fw.ecu == "eps" and not fw.fwVersion == b'8965B47060\x00\x00\x00\x00\x00\x00': - steering_angle_deadzone_deg = 1.0 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) + ret.steerActuatorDelay = 0.25 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg=0.2) elif candidate == CAR.PRIUS_V: stop_and_go = True @@ -52,7 +56,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 17.4 tire_stiffness_factor = 0.5533 ret.mass = 3340. * CV.LB_TO_KG + STD_CARGO_KG - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning, steering_angle_deadzone_deg) elif candidate in (CAR.RAV4, CAR.RAV4H): stop_and_go = True if (candidate in CAR.RAV4H) else False @@ -74,15 +77,13 @@ class CarInterface(CarInterfaceBase): 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 in (CAR.CHR, CAR.CHRH): + elif candidate in (CAR.CHR, CAR.CHRH, CAR.CHR_TSS2, CAR.CHRH_TSS2): stop_and_go = True ret.wheelbase = 2.63906 ret.steerRatio = 13.6 tire_stiffness_factor = 0.7933 ret.mass = 3300. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_F) elif candidate in (CAR.CAMRY, CAR.CAMRYH, CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): stop_and_go = True @@ -90,8 +91,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 13.7 tire_stiffness_factor = 0.7933 ret.mass = 3400. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid - if candidate not in (CAR.CAMRY_TSS2, CAR.CAMRYH_TSS2): - set_lat_tune(ret.lateralTuning, LatTunes.PID_C) elif candidate in (CAR.HIGHLANDER, CAR.HIGHLANDERH, CAR.HIGHLANDER_TSS2, CAR.HIGHLANDERH_TSS2): stop_and_go = True @@ -99,7 +98,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.0 tire_stiffness_factor = 0.8 ret.mass = 4516. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid - set_lat_tune(ret.lateralTuning, LatTunes.PID_G) elif candidate in (CAR.AVALON, CAR.AVALON_2019, CAR.AVALONH_2019, CAR.AVALON_TSS2, CAR.AVALONH_TSS2): # starting from 2019, all Avalon variants have stop and go @@ -109,21 +107,28 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.8 # Found at https://pressroom.toyota.com/releases/2016+avalon+product+specs.download tire_stiffness_factor = 0.7983 ret.mass = 3505. * CV.LB_TO_KG + STD_CARGO_KG # mean between normal and hybrid - set_lat_tune(ret.lateralTuning, LatTunes.PID_H) - elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022): + elif candidate in (CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, + CAR.RAV4_TSS2_2023, CAR.RAV4H_TSS2_2023): stop_and_go = True ret.wheelbase = 2.68986 ret.steerRatio = 14.3 tire_stiffness_factor = 0.7933 ret.mass = 3585. * CV.LB_TO_KG + STD_CARGO_KG # Average between ICE and Hybrid - set_lat_tune(ret.lateralTuning, LatTunes.PID_D) + ret.lateralTuning.init('pid') + ret.lateralTuning.pid.kiBP = [0.0] + ret.lateralTuning.pid.kpBP = [0.0] + ret.lateralTuning.pid.kpV = [0.6] + ret.lateralTuning.pid.kiV = [0.1] + ret.lateralTuning.pid.kf = 0.00007818594 # 2019+ RAV4 TSS2 uses two different steering racks and specific tuning seems to be necessary. # See https://github.com/commaai/openpilot/pull/21429#issuecomment-873652891 for fw in car_fw: if fw.ecu == "eps" and (fw.fwVersion.startswith(b'\x02') or fw.fwVersion in [b'8965B42181\x00\x00\x00\x00\x00\x00']): - set_lat_tune(ret.lateralTuning, LatTunes.PID_I) + ret.lateralTuning.pid.kpV = [0.15] + ret.lateralTuning.pid.kiV = [0.05] + ret.lateralTuning.pid.kf = 0.00004 break elif candidate in (CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2): @@ -139,7 +144,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 16.0 # not optimized tire_stiffness_factor = 0.444 # not optimized yet ret.mass = 3677. * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - set_lat_tune(ret.lateralTuning, LatTunes.PID_D) elif candidate == CAR.SIENNA: stop_and_go = True @@ -147,14 +151,12 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 15.5 tire_stiffness_factor = 0.444 ret.mass = 4590. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_J) elif candidate in (CAR.LEXUS_IS, CAR.LEXUS_RC): ret.wheelbase = 2.79908 ret.steerRatio = 13.3 tire_stiffness_factor = 0.444 ret.mass = 3736.8 * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_L) elif candidate == CAR.LEXUS_CTH: stop_and_go = True @@ -162,7 +164,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 18.6 tire_stiffness_factor = 0.517 ret.mass = 3108 * CV.LB_TO_KG + STD_CARGO_KG # mean between min and max - set_lat_tune(ret.lateralTuning, LatTunes.PID_M) elif candidate in (CAR.LEXUS_NX, CAR.LEXUS_NXH, CAR.LEXUS_NX_TSS2, CAR.LEXUS_NXH_TSS2): stop_and_go = True @@ -170,7 +171,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.7 tire_stiffness_factor = 0.444 # not optimized yet ret.mass = 4070 * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_C) elif candidate == CAR.PRIUS_TSS2: stop_and_go = True @@ -178,7 +178,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 13.4 # True steerRatio from older prius tire_stiffness_factor = 0.6371 # hand-tune ret.mass = 3115. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_N) elif candidate == CAR.MIRAI: stop_and_go = True @@ -186,7 +185,6 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.8 tire_stiffness_factor = 0.8 ret.mass = 4300. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_C) elif candidate in (CAR.ALPHARD_TSS2, CAR.ALPHARDH_TSS2): stop_and_go = True @@ -194,14 +192,9 @@ class CarInterface(CarInterfaceBase): ret.steerRatio = 14.2 tire_stiffness_factor = 0.444 ret.mass = 4305. * CV.LB_TO_KG + STD_CARGO_KG - set_lat_tune(ret.lateralTuning, LatTunes.PID_J) ret.centerToFront = ret.wheelbase * 0.44 - # TODO: get actual value, for now starting with reasonable value for - # civic and scaling by mass and wheelbase - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) - # TODO: start from empirically derived lateral slip stiffness for the civic and scale by # mass and CG position, so all cars will have approximately similar dyn behaviors ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, @@ -212,13 +205,13 @@ class CarInterface(CarInterfaceBase): smartDsu = 0x2FF in fingerprint[0] # In TSS2 cars the camera does long control found_ecus = [fw.ecu for fw in car_fw] - ret.enableDsu = (len(found_ecus) > 0) and (Ecu.dsu not in found_ecus) and (candidate not in NO_DSU_CAR) and (not smartDsu) + ret.enableDsu = len(found_ecus) > 0 and Ecu.dsu not in found_ecus and candidate not in (NO_DSU_CAR | UNSUPPORTED_DSU_CAR) and not smartDsu ret.enableGasInterceptor = 0x201 in fingerprint[0] # if the smartDSU is detected, openpilot can send ACC_CMD (and the smartDSU will block it from the DSU) or not (the DSU is "connected") ret.openpilotLongitudinalControl = smartDsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) + ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR if not ret.openpilotLongitudinalControl: - ret.autoResumeSng = False ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL # we can't use the fingerprint to detect this reliably, since @@ -230,12 +223,23 @@ class CarInterface(CarInterfaceBase): # to a negative value, so it won't matter. ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else MIN_ACC_SPEED + tune = ret.longitudinalTuning + tune.deadzoneBP = [0., 9.] + tune.deadzoneV = [.0, .15] if candidate in TSS2_CAR or ret.enableGasInterceptor: - set_long_tune(ret.longitudinalTuning, LongTunes.TSS2) + tune.kpBP = [0., 5., 20.] + tune.kpV = [1.3, 1.0, 0.7] + tune.kiBP = [0., 5., 12., 20., 27.] + tune.kiV = [.35, .23, .20, .17, .1] if candidate in TSS2_CAR: - ret.stoppingDecelRate = 0.3 # reach stopping target smoothly + ret.vEgoStopping = 0.25 + ret.vEgoStarting = 0.25 + ret.stoppingDecelRate = 0.3 # reach stopping target smoothly else: - set_long_tune(ret.longitudinalTuning, LongTunes.TSS) + tune.kpBP = [0., 5., 35.] + tune.kiBP = [0., 35.] + tune.kpV = [3.6, 2.4, 1.5] + tune.kiV = [0.54, 0.36] return ret @@ -246,16 +250,19 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret) - if self.CS.low_speed_lockout and self.CP.openpilotLongitudinalControl: - events.add(EventName.lowSpeedLockout) - if ret.vEgo < self.CP.minEnableSpeed and self.CP.openpilotLongitudinalControl: - events.add(EventName.belowEngageSpeed) - if c.actuators.accel > 0.3: - # some margin on the actuator to not false trigger cancellation while stopping - events.add(EventName.speedTooLow) - if ret.vEgo < 0.001: - # while in standstill, send a user alert - events.add(EventName.manualRestart) + if self.CP.openpilotLongitudinalControl: + if ret.cruiseState.standstill and not ret.brakePressed and not self.CP.enableGasInterceptor: + events.add(EventName.resumeRequired) + if self.CS.low_speed_lockout: + events.add(EventName.lowSpeedLockout) + if ret.vEgo < self.CP.minEnableSpeed: + events.add(EventName.belowEngageSpeed) + if c.actuators.accel > 0.3: + # some margin on the actuator to not false trigger cancellation while stopping + events.add(EventName.speedTooLow) + if ret.vEgo < 0.001: + # while in standstill, send a user alert + events.add(EventName.manualRestart) ret.events = events.to_msg() @@ -263,5 +270,5 @@ class CarInterface(CarInterfaceBase): # pass in a car.CarControl # to be called @ 100hz - def apply(self, c): - return self.CC.update(c, self.CS) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, now_nanos) diff --git a/selfdrive/loggerd/tests/__init__.py b/selfdrive/car/toyota/tests/__init__.py similarity index 100% rename from selfdrive/loggerd/tests/__init__.py rename to selfdrive/car/toyota/tests/__init__.py diff --git a/selfdrive/car/toyota/tests/test_toyota.py b/selfdrive/car/toyota/tests/test_toyota.py new file mode 100755 index 0000000000..5648f75fe7 --- /dev/null +++ b/selfdrive/car/toyota/tests/test_toyota.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +import unittest + +from selfdrive.car.toyota.values import TSS2_CAR, ANGLE_CONTROL_CAR + + +class TestToyotaInterfaces(unittest.TestCase): + def test_angle_car_set(self): + self.assertTrue(len(ANGLE_CONTROL_CAR - TSS2_CAR) == 0) + + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/car/toyota/toyotacan.py b/selfdrive/car/toyota/toyotacan.py index 7ab3ab3e78..a78b70608c 100644 --- a/selfdrive/car/toyota/toyotacan.py +++ b/selfdrive/car/toyota/toyotacan.py @@ -17,7 +17,7 @@ def create_lta_steer_command(packer, steer, steer_req, raw_cnt): "SETME_X1": 1, "SETME_X3": 3, "PERCENTAGE": 100, - "SETME_X64": 0x64, + "SETME_X64": 0, "ANGLE": 0, "STEER_ANGLE_CMD": steer, "STEER_REQUEST": steer_req, @@ -66,13 +66,13 @@ 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, enabled): +def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_depart, right_lane_depart, enabled, stock_lkas_hud): values = { "TWO_BEEPS": chime, "LDA_ALERT": steer, "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" : 1 if enabled else 0, + "BARRIERS": 1 if enabled else 0, # static signals "SET_ME_X02": 2, @@ -96,4 +96,9 @@ def create_ui_command(packer, steer, chime, left_line, right_line, left_lane_dep "ADJUSTING_CAMERA": 0, "LDW_EXIST": 1, } + + # lane sway functionality + # not all cars have LKAS_HUD — update with camera values if available + values.update(stock_lkas_hud) + return packer.make_can_msg("LKAS_HUD", 0, values) diff --git a/selfdrive/car/toyota/tunes.py b/selfdrive/car/toyota/tunes.py deleted file mode 100644 index b73ab4c8c9..0000000000 --- a/selfdrive/car/toyota/tunes.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -from enum import Enum - -class LongTunes(Enum): - TSS2 = 0 - TSS = 1 - -class LatTunes(Enum): - INDI_PRIUS = 0 - LQR_RAV4 = 1 - PID_A = 2 - PID_B = 3 - PID_C = 4 - PID_D = 5 - PID_E = 6 - PID_F = 7 - PID_G = 8 - PID_I = 9 - PID_H = 10 - PID_J = 11 - PID_K = 12 - PID_L = 13 - PID_M = 14 - PID_N = 15 - - -###### LONG ###### -def set_long_tune(tune, name): - # Improved longitudinal tune - if name == LongTunes.TSS2: - tune.deadzoneBP = [0., 8.05] - tune.deadzoneV = [.0, .14] - tune.kpBP = [0., 5., 20.] - tune.kpV = [1.3, 1.0, 0.7] - tune.kiBP = [0., 5., 12., 20., 27.] - tune.kiV = [.35, .23, .20, .17, .1] - # Default longitudinal tune - elif name == LongTunes.TSS: - tune.deadzoneBP = [0., 9.] - tune.deadzoneV = [.0, .15] - tune.kpBP = [0., 5., 35.] - tune.kiBP = [0., 35.] - tune.kpV = [3.6, 2.4, 1.5] - tune.kiV = [0.54, 0.36] - else: - raise NotImplementedError('This longitudinal tune does not exist') - - -###### LAT ###### -def set_lat_tune(tune, name, MAX_LAT_ACCEL=2.5, FRICTION=0.01, steering_angle_deadzone_deg=0.0, use_steering_angle=True): - if 'PID' in str(name): - tune.init('pid') - tune.pid.kiBP = [0.0] - tune.pid.kpBP = [0.0] - if name == LatTunes.PID_A: - tune.pid.kpV = [0.2] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00003 - elif name == LatTunes.PID_C: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.1] - tune.pid.kf = 0.00006 - elif name == LatTunes.PID_D: - tune.pid.kpV = [0.6] - tune.pid.kiV = [0.1] - tune.pid.kf = 0.00007818594 - elif name == LatTunes.PID_F: - tune.pid.kpV = [0.723] - tune.pid.kiV = [0.0428] - tune.pid.kf = 0.00006 - elif name == LatTunes.PID_G: - tune.pid.kpV = [0.18] - tune.pid.kiV = [0.015] - tune.pid.kf = 0.00012 - elif name == LatTunes.PID_H: - tune.pid.kpV = [0.17] - tune.pid.kiV = [0.03] - tune.pid.kf = 0.00006 - elif name == LatTunes.PID_I: - tune.pid.kpV = [0.15] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00004 - elif name == LatTunes.PID_J: - tune.pid.kpV = [0.19] - tune.pid.kiV = [0.02] - tune.pid.kf = 0.00007818594 - elif name == LatTunes.PID_L: - tune.pid.kpV = [0.3] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00006 - elif name == LatTunes.PID_M: - tune.pid.kpV = [0.3] - tune.pid.kiV = [0.05] - tune.pid.kf = 0.00007 - elif name == LatTunes.PID_N: - tune.pid.kpV = [0.35] - tune.pid.kiV = [0.15] - tune.pid.kf = 0.00007818594 - else: - raise NotImplementedError('This PID tune does not exist') - else: - raise NotImplementedError('This lateral tune does not exist') diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 819b85d759..2398e012d6 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -18,6 +18,7 @@ class CarControllerParams: ACCEL_MAX = 1.5 # m/s2, lower than allowed 2.0 m/s2 for tuning reasons ACCEL_MIN = -3.5 # m/s2 + STEER_STEP = 1 STEER_MAX = 1500 STEER_ERROR_MAX = 350 # max delta between torque cmd and torque motor @@ -48,7 +49,9 @@ class CAR: CAMRY_TSS2 = "TOYOTA CAMRY 2021" # TSS 2.5 CAMRYH_TSS2 = "TOYOTA CAMRY HYBRID 2021" CHR = "TOYOTA C-HR 2018" + CHR_TSS2 = "TOYOTA C-HR 2021" CHRH = "TOYOTA C-HR HYBRID 2018" + CHRH_TSS2 = "TOYOTA C-HR HYBRID 2022" COROLLA = "TOYOTA COROLLA 2017" COROLLA_TSS2 = "TOYOTA COROLLA TSS2 2019" # LSS2 Lexus UX Hybrid is same as a TSS2 Corolla Hybrid @@ -64,8 +67,10 @@ class CAR: RAV4H = "TOYOTA RAV4 HYBRID 2017" RAV4_TSS2 = "TOYOTA RAV4 2019" RAV4_TSS2_2022 = "TOYOTA RAV4 2022" + RAV4_TSS2_2023 = "TOYOTA RAV4 2023" RAV4H_TSS2 = "TOYOTA RAV4 HYBRID 2019" RAV4H_TSS2_2022 = "TOYOTA RAV4 HYBRID 2022" + RAV4H_TSS2_2023 = "TOYOTA RAV4 HYBRID 2023" MIRAI = "TOYOTA MIRAI 2021" # TSS 2.5 SIENNA = "TOYOTA SIENNA 2018" @@ -87,10 +92,6 @@ class CAR: class Footnote(Enum): - DSU = CarFootnote( - "When the Driver Support Unit (DSU) is disconnected, openpilot Adaptive Cruise Control (ACC) will replace " + - "stock Adaptive Cruise Control (ACC). NOTE: disconnecting the DSU disables Automatic Emergency Braking (AEB).", - Column.LONGITUDINAL) CAMRY = CarFootnote( "openpilot operates above 28mph for Camry 4CYL L, 4CYL LE and 4CYL SE which don't have Full-Speed Range Dynamic Radar Cruise Control.", Column.FSR_LONGITUDINAL) @@ -107,77 +108,82 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { CAR.ALPHARD_TSS2: ToyotaCarInfo("Toyota Alphard 2019-20"), CAR.ALPHARDH_TSS2: ToyotaCarInfo("Toyota Alphard Hybrid 2021"), CAR.AVALON: [ - ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota Avalon 2017-18", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Avalon 2016", "Toyota Safety Sense P"), + ToyotaCarInfo("Toyota Avalon 2017-18"), ], - CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21", footnotes=[Footnote.DSU]), - CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21", footnotes=[Footnote.DSU]), + CAR.AVALON_2019: ToyotaCarInfo("Toyota Avalon 2019-21"), + CAR.AVALONH_2019: ToyotaCarInfo("Toyota Avalon Hybrid 2019-21"), CAR.AVALON_TSS2: ToyotaCarInfo("Toyota Avalon 2022"), CAR.AVALONH_TSS2: ToyotaCarInfo("Toyota Avalon Hybrid 2022"), CAR.CAMRY: ToyotaCarInfo("Toyota Camry 2018-20", video_link="https://www.youtube.com/watch?v=fkcjviZY9CM", footnotes=[Footnote.CAMRY]), CAR.CAMRYH: ToyotaCarInfo("Toyota Camry Hybrid 2018-20", video_link="https://www.youtube.com/watch?v=Q2DYY0AWKgk"), - CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-22", footnotes=[Footnote.CAMRY]), - CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-22"), - CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-21"), - CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-19"), - CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19", footnotes=[Footnote.DSU]), + CAR.CAMRY_TSS2: ToyotaCarInfo("Toyota Camry 2021-23", footnotes=[Footnote.CAMRY]), + CAR.CAMRYH_TSS2: ToyotaCarInfo("Toyota Camry Hybrid 2021-23"), + CAR.CHR: ToyotaCarInfo("Toyota C-HR 2017-20"), + CAR.CHR_TSS2: ToyotaCarInfo("Toyota C-HR 2021"), + CAR.CHRH: ToyotaCarInfo("Toyota C-HR Hybrid 2017-20"), + CAR.CHRH_TSS2: ToyotaCarInfo("Toyota C-HR Hybrid 2021-22"), + CAR.COROLLA: ToyotaCarInfo("Toyota Corolla 2017-19"), CAR.COROLLA_TSS2: [ ToyotaCarInfo("Toyota Corolla 2020-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), - ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-21", min_enable_speed=7.5), + ToyotaCarInfo("Toyota Corolla Cross (Non-US only) 2020-23", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Hatchback 2019-22", video_link="https://www.youtube.com/watch?v=_66pXk0CBYA"), ], CAR.COROLLAH_TSS2: [ ToyotaCarInfo("Toyota Corolla Hybrid 2020-22"), + ToyotaCarInfo("Toyota Corolla Hybrid (Non-US only) 2020-23", min_enable_speed=7.5), ToyotaCarInfo("Toyota Corolla Cross Hybrid (Non-US only) 2020-22", min_enable_speed=7.5), ToyotaCarInfo("Lexus UX Hybrid 2019-22"), ], - CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo", footnotes=[Footnote.DSU]), - CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-22"), - CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19", footnotes=[Footnote.DSU]), - CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-22"), + CAR.HIGHLANDER: ToyotaCarInfo("Toyota Highlander 2017-19", video_link="https://www.youtube.com/watch?v=0wS0wXSLzoo"), + CAR.HIGHLANDER_TSS2: ToyotaCarInfo("Toyota Highlander 2020-23"), + CAR.HIGHLANDERH: ToyotaCarInfo("Toyota Highlander Hybrid 2017-19"), + CAR.HIGHLANDERH_TSS2: ToyotaCarInfo("Toyota Highlander Hybrid 2020-23"), CAR.PRIUS: [ - ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0", [Footnote.DSU]), - ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Toyota Prius 2016", "Toyota Safety Sense P", "https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarInfo("Toyota Prius 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), + ToyotaCarInfo("Toyota Prius Prime 2017-20", video_link="https://www.youtube.com/watch?v=8zopPJI8XQ0"), ], - CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED, footnotes=[Footnote.DSU]), + CAR.PRIUS_V: ToyotaCarInfo("Toyota Prius v 2017", "Toyota Safety Sense P", min_enable_speed=MIN_ACC_SPEED), CAR.PRIUS_TSS2: [ ToyotaCarInfo("Toyota Prius 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ToyotaCarInfo("Toyota Prius Prime 2021-22", video_link="https://www.youtube.com/watch?v=J58TvCpUd4U"), ], CAR.RAV4: [ - ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Toyota RAV4 2017-18", footnotes=[Footnote.DSU]) + ToyotaCarInfo("Toyota RAV4 2016", "Toyota Safety Sense P"), + ToyotaCarInfo("Toyota RAV4 2017-18") ], CAR.RAV4H: [ - ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26", [Footnote.DSU]), - ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26", footnotes=[Footnote.DSU]) + ToyotaCarInfo("Toyota RAV4 Hybrid 2016", "Toyota Safety Sense P", "https://youtu.be/LhT5VzJVfNI?t=26"), + ToyotaCarInfo("Toyota RAV4 Hybrid 2017-18", video_link="https://youtu.be/LhT5VzJVfNI?t=26") ], CAR.RAV4_TSS2: ToyotaCarInfo("Toyota RAV4 2019-21", video_link="https://www.youtube.com/watch?v=wJxjDd42gGA"), CAR.RAV4_TSS2_2022: ToyotaCarInfo("Toyota RAV4 2022"), + CAR.RAV4_TSS2_2023: ToyotaCarInfo("Toyota RAV4 2023"), CAR.RAV4H_TSS2: ToyotaCarInfo("Toyota RAV4 Hybrid 2019-21"), CAR.RAV4H_TSS2_2022: ToyotaCarInfo("Toyota RAV4 Hybrid 2022", video_link="https://youtu.be/U0nH9cnrFB0"), + CAR.RAV4H_TSS2_2023: ToyotaCarInfo("Toyota RAV4 Hybrid 2023"), CAR.MIRAI: ToyotaCarInfo("Toyota Mirai 2021"), - CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", footnotes=[Footnote.DSU], min_enable_speed=MIN_ACC_SPEED), + CAR.SIENNA: ToyotaCarInfo("Toyota Sienna 2018-20", video_link="https://www.youtube.com/watch?v=q1UPOo4Sh68", min_enable_speed=MIN_ACC_SPEED), # Lexus - CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), - CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18", "Lexus Safety System+", footnotes=[Footnote.DSU]), + CAR.LEXUS_CTH: ToyotaCarInfo("Lexus CT Hybrid 2017-18", "Lexus Safety System+"), + CAR.LEXUS_ESH: ToyotaCarInfo("Lexus ES Hybrid 2017-18"), CAR.LEXUS_ES_TSS2: ToyotaCarInfo("Lexus ES 2019-22"), - CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-22", video_link="https://youtu.be/BZ29osRVJeg?t=12"), + CAR.LEXUS_ESH_TSS2: ToyotaCarInfo("Lexus ES Hybrid 2019-23", video_link="https://youtu.be/BZ29osRVJeg?t=12"), CAR.LEXUS_IS: ToyotaCarInfo("Lexus IS 2017-19"), - CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19", footnotes=[Footnote.DSU]), - CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19", footnotes=[Footnote.DSU]), + CAR.LEXUS_NX: ToyotaCarInfo("Lexus NX 2018-19"), + CAR.LEXUS_NXH: ToyotaCarInfo("Lexus NX Hybrid 2018-19"), CAR.LEXUS_NX_TSS2: ToyotaCarInfo("Lexus NX 2020-21"), CAR.LEXUS_NXH_TSS2: ToyotaCarInfo("Lexus NX Hybrid 2020-21"), - CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2017-20"), + CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2018-20"), CAR.LEXUS_RX: [ - ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Lexus RX 2017-19", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"), + ToyotaCarInfo("Lexus RX 2017-19"), ], CAR.LEXUS_RXH: [ - ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+", footnotes=[Footnote.DSU]), - ToyotaCarInfo("Lexus RX Hybrid 2017-19", footnotes=[Footnote.DSU]), + ToyotaCarInfo("Lexus RX Hybrid 2016", "Lexus Safety System+"), + ToyotaCarInfo("Lexus RX Hybrid 2017-19"), ], CAR.LEXUS_RX_TSS2: ToyotaCarInfo("Lexus RX 2020-22"), CAR.LEXUS_RXH_TSS2: ToyotaCarInfo("Lexus RX Hybrid 2020-21"), @@ -213,16 +219,19 @@ FW_QUERY_CONFIG = FwQueryConfig( Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, TOYOTA_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, TOYOTA_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera, Ecu.fwdRadar, Ecu.dsu, Ecu.abs, Ecu.eps], bus=0, ), Request( [StdQueries.SHORT_TESTER_PRESENT_REQUEST, StdQueries.OBD_VERSION_REQUEST], [StdQueries.SHORT_TESTER_PRESENT_RESPONSE, StdQueries.OBD_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine], bus=0, ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.DEFAULT_DIAGNOSTIC_REQUEST, StdQueries.EXTENDED_DIAGNOSTIC_REQUEST, StdQueries.UDS_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.DEFAULT_DIAGNOSTIC_RESPONSE, StdQueries.EXTENDED_DIAGNOSTIC_RESPONSE, StdQueries.UDS_VERSION_RESPONSE], + whitelist_ecus=[Ecu.engine, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.abs, Ecu.eps], bus=0, ), ], @@ -230,7 +239,7 @@ FW_QUERY_CONFIG = FwQueryConfig( # FIXME: On some models, abs can sometimes be missing Ecu.abs: [CAR.RAV4, CAR.COROLLA, CAR.HIGHLANDER, CAR.SIENNA, CAR.LEXUS_IS], # On some models, the engine can show on two different addresses - Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.LEXUS_IS], + Ecu.engine: [CAR.CAMRY, CAR.COROLLA_TSS2, CAR.CHR, CAR.CHR_TSS2, CAR.LEXUS_IS, CAR.LEXUS_RC], } ) @@ -347,6 +356,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F4104100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', @@ -521,14 +531,18 @@ FW_VERSIONS = { CAR.CAMRY_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B33630\x00\x00\x00\x00\x00\x00', + b'8965B33640\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152606370\x00\x00\x00\x00\x00\x00', b'\x01F152606390\x00\x00\x00\x00\x00\x00', b'\x01F152606400\x00\x00\x00\x00\x00\x00', + b'\x01F152606431\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q5000\x00\x00\x00\x00', + b'\x018966306Q9000\x00\x00\x00\x00', + b'\x018966306R3000\x00\x00\x00\x00', b'\x018966306T3100\x00\x00\x00\x00', b'\x018966306T3200\x00\x00\x00\x00', b'\x018966306T4000\x00\x00\x00\x00', @@ -536,12 +550,15 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F6201200\x00\x00\x00\x00', + b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F0602100\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F0602200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F3305200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CAMRYH_TSS2: { @@ -552,10 +569,12 @@ FW_VERSIONS = { (Ecu.abs, 0x7b0, None): [ b'F152633D00\x00\x00\x00\x00\x00\x00', b'F152633D60\x00\x00\x00\x00\x00\x00', + b'F152633310\x00\x00\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x018966306Q6000\x00\x00\x00\x00', b'\x018966306Q7000\x00\x00\x00\x00', + b'\x018966306V1000\x00\x00\x00\x00', b'\x01896633T20000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 15): [ @@ -566,6 +585,7 @@ FW_VERSIONS = { b'\x028646F3305200\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F3305300\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F3305500\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.CHR: { @@ -628,6 +648,30 @@ FW_VERSIONS = { b'8646FF407000 ', ], }, + CAR.CHR_TSS2: { + (Ecu.abs, 0x7b0, None): [ + b'F152610260\x00\x00\x00\x00\x00\x00', + b'F1526F4270\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B10091\x00\x00\x00\x00\x00\x00', + b'8965B10110\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F459000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x7e0, None): [ + b'\x0331014000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821FF410200\x00\x00\x00\x00', + b'\x018821FF410300\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646FF410200\x00\x00\x00\x008646GF408200\x00\x00\x00\x00', + b'\x028646FF411100\x00\x00\x00\x008646GF409000\x00\x00\x00\x00', + ], + }, CAR.CHRH: { (Ecu.engine, 0x700, None): [ b'\x0289663F405100\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -679,8 +723,26 @@ FW_VERSIONS = { b'8646FF404000 ', b'8646FF406000 ', b'8646FF407000 ', + b'8646FF407100 ', ], }, + CAR.CHRH_TSS2: { + (Ecu.eps, 0x7a1, None): [ + b'8965B10092\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152610041\x00\x00\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x0189663F438000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 15): [ + b'\x018821FF410500\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 109): [ + b'\x028646FF413100\x00\x00\x00\x008646GF411100\x00\x00\x00\x00', + ], + }, CAR.COROLLA: { (Ecu.engine, 0x7e0, None): [ b'\x0230ZC2000\x00\x00\x00\x00\x00\x00\x00\x0050212000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -717,6 +779,7 @@ FW_VERSIONS = { }, CAR.COROLLA_TSS2: { (Ecu.engine, 0x700, None): [ + b'\x01896630A22000\x00\x00\x00\x00', b'\x01896630ZG2000\x00\x00\x00\x00', b'\x01896630ZG5000\x00\x00\x00\x00', b'\x01896630ZG5100\x00\x00\x00\x00', @@ -738,6 +801,7 @@ FW_VERSIONS = { b'\x018966312Q8000\x00\x00\x00\x00', b'\x018966312R0000\x00\x00\x00\x00', b'\x018966312R0100\x00\x00\x00\x00', + b'\x018966312R0200\x00\x00\x00\x00', b'\x018966312R1000\x00\x00\x00\x00', b'\x018966312R1100\x00\x00\x00\x00', b'\x018966312R3100\x00\x00\x00\x00', @@ -745,6 +809,7 @@ FW_VERSIONS = { b'\x018966312S7000\x00\x00\x00\x00', b'\x018966312W3000\x00\x00\x00\x00', b'\x018966312W9000\x00\x00\x00\x00', + b'\x01896637644000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ b'\x0230A10000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', @@ -759,6 +824,7 @@ FW_VERSIONS = { b'\x03312N6100\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203202\x00\x00\x00\x00', b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203302\x00\x00\x00\x00', + b'\x03312N6200\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00895231203402\x00\x00\x00\x00', b'\x02312K4000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02312U5000\x00\x00\x00\x00\x00\x00\x00\x00A0202000\x00\x00\x00\x00\x00\x00\x00\x00', ], @@ -772,6 +838,7 @@ FW_VERSIONS = { b'\x018965B1255000\x00\x00\x00\x00', b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B76012\x00\x00\x00\x00\x00\x00', b'\x018965B12510\x00\x00\x00\x00\x00\x00', b'\x018965B1256000\x00\x00\x00\x00', ], @@ -789,6 +856,7 @@ FW_VERSIONS = { b'\x01F152612B60\x00\x00\x00\x00\x00\x00', b'\x01F152612B61\x00\x00\x00\x00\x00\x00', b'\x01F152612B62\x00\x00\x00\x00\x00\x00', + b'\x01F152612B70\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', @@ -796,6 +864,8 @@ FW_VERSIONS = { b'F152602191\x00\x00\x00\x00\x00\x00', b'\x01F152612862\x00\x00\x00\x00\x00\x00', b'\x01F152612B91\x00\x00\x00\x00\x00\x00', + b'\x01F15260A070\x00\x00\x00\x00\x00\x00', + b'\x01F152676250\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301100\x00\x00\x00\x00', @@ -813,6 +883,7 @@ FW_VERSIONS = { b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, CAR.COROLLAH_TSS2: { @@ -820,18 +891,22 @@ FW_VERSIONS = { b'\x01896630ZJ1000\x00\x00\x00\x00', b'\x01896630ZU8000\x00\x00\x00\x00', b'\x01896637621000\x00\x00\x00\x00', + b'\x01896637623000\x00\x00\x00\x00', b'\x01896637624000\x00\x00\x00\x00', b'\x01896637626000\x00\x00\x00\x00', + b'\x01896637639000\x00\x00\x00\x00', b'\x01896637648000\x00\x00\x00\x00', b'\x01896637643000\x00\x00\x00\x00', b'\x02896630A07000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630A21000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZJ5000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZK8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZN8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZQ3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZR2000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZT8000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x02896630ZT9000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', + b'\x02896630ZZ0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312K6000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312L0000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', b'\x028966312Q3000\x00\x00\x00\x008966A4703000\x00\x00\x00\x00', @@ -844,6 +919,7 @@ FW_VERSIONS = { b'8965B12361\x00\x00\x00\x00\x00\x00', b'8965B12451\x00\x00\x00\x00\x00\x00', b'8965B16011\x00\x00\x00\x00\x00\x00', + b'8965B16101\x00\x00\x00\x00\x00\x00', b'8965B76012\x00\x00\x00\x00\x00\x00', b'8965B76050\x00\x00\x00\x00\x00\x00', b'\x018965B12350\x00\x00\x00\x00\x00\x00', @@ -891,6 +967,7 @@ FW_VERSIONS = { b'\x028646F1202100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b'\x028646F1202200\x00\x00\x00\x008646G2601500\x00\x00\x00\x00', b'\x028646F1601100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', + b'\x028646F1601200\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', b"\x028646F1601300\x00\x00\x00\x008646G2601400\x00\x00\x00\x00", b'\x028646F4203400\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F76020C0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', @@ -968,12 +1045,14 @@ FW_VERSIONS = { b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00', b'8965B48320\x00\x00\x00\x00\x00\x00', + b'8965B48400\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15260E051\x00\x00\x00\x00\x00\x00', b'\x01F15260E061\x00\x00\x00\x00\x00\x00', b'\x01F15260E110\x00\x00\x00\x00\x00\x00', b'\x01F15260E170\x00\x00\x00\x00\x00\x00', + b'\x01F15260E05300\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ b'\x01896630E62100\x00\x00\x00\x00', @@ -991,6 +1070,8 @@ FW_VERSIONS = { b'\x01896630ED9100\x00\x00\x00\x00', b'\x01896630EE1000\x00\x00\x00\x00', b'\x01896630EE1100\x00\x00\x00\x00', + b'\x01896630EG3000\x00\x00\x00\x00', + b'\x01896630EG5000\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', @@ -1001,20 +1082,24 @@ FW_VERSIONS = { b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.HIGHLANDERH_TSS2: { (Ecu.eps, 0x7a1, None): [ b'8965B48241\x00\x00\x00\x00\x00\x00', b'8965B48310\x00\x00\x00\x00\x00\x00', + b'8965B48400\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F15264872300\x00\x00\x00\x00', b'\x01F15264872400\x00\x00\x00\x00', b'\x01F15264872500\x00\x00\x00\x00', + b'\x01F15264872600\x00\x00\x00\x00', b'\x01F15264873500\x00\x00\x00\x00', b'\x01F152648C6300\x00\x00\x00\x00', b'\x01F152648J4000\x00\x00\x00\x00', + b'\x01F152648J5000\x00\x00\x00\x00', b'\x01F152648J6000\x00\x00\x00\x00', ], (Ecu.engine, 0x700, None): [ @@ -1022,7 +1107,9 @@ FW_VERSIONS = { b'\x01896630EA1000\x00\x00\x00\x00', b'\x01896630EE4000\x00\x00\x00\x00', b'\x01896630EE4100\x00\x00\x00\x00', + b'\x01896630EE5000\x00\x00\x00\x00', b'\x01896630EE6000\x00\x00\x00\x00', + b'\x01896630EF8000\x00\x00\x00\x00', b'\x02896630E66000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896630E66100\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x01896630EA1000\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', @@ -1038,6 +1125,7 @@ FW_VERSIONS = { b'\x028646F0E02100\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G5301200\x00\x00\x00\x00', b'\x028646F4803000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + b'\x028646F4803200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', ], }, CAR.LEXUS_IS: { @@ -1321,6 +1409,7 @@ FW_VERSIONS = { b'\x01F15260R302\x00\x00\x00\x00\x00\x00', b'\x01F152642551\x00\x00\x00\x00\x00\x00', b'\x01F152642561\x00\x00\x00\x00\x00\x00', + b'\x01F152642601\x00\x00\x00\x00\x00\x00', b'\x01F152642700\x00\x00\x00\x00\x00\x00', b'\x01F152642701\x00\x00\x00\x00\x00\x00', b'\x01F152642710\x00\x00\x00\x00\x00\x00', @@ -1363,10 +1452,11 @@ FW_VERSIONS = { ], (Ecu.engine, 0x700, None): [ b'\x01896634AA0000\x00\x00\x00\x00', - b'\x01896634AA0100\x00\x00\x00\x00', + b'\x01896634AA0100\x00\x00\x00\x00', b'\x01896634AA1000\x00\x00\x00\x00', b'\x01896634A88000\x00\x00\x00\x00', b'\x01896634A89000\x00\x00\x00\x00', + b'\x01896634A89100\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F0R01100\x00\x00\x00\x00', @@ -1375,6 +1465,23 @@ FW_VERSIONS = { b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', ], }, + CAR.RAV4_TSS2_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F15260R450\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896634AJ2000\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F0R03100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', + ], + }, CAR.RAV4H_TSS2: { (Ecu.engine, 0x700, None): [ b'\x01896634A15000\x00\x00\x00\x00', @@ -1392,6 +1499,7 @@ FW_VERSIONS = { b'\x02896634A14001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A23000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00', b'\x02896634A23001\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', + b'\x02896634A23101\x00\x00\x00\x00897CF1203001\x00\x00\x00\x00', b'\x02896634A14001\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', b'\x02896634A14101\x00\x00\x00\x00897CF4801001\x00\x00\x00\x00', ], @@ -1461,6 +1569,23 @@ FW_VERSIONS = { b'\x028646F0R02100\x00\x00\x00\x008646G0R01100\x00\x00\x00\x00', ], }, + CAR.RAV4H_TSS2_2023: { + (Ecu.abs, 0x7b0, None): [ + b'\x01F15264283200\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'\x028965B0R11000\x00\x00\x00\x008965B0R12000\x00\x00\x00\x00', + ], + (Ecu.engine, 0x700, None): [ + b'\x01896634AE1001\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F0R03100\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0R05100\x00\x00\x00\x008646G0R02100\x00\x00\x00\x00', + ], + }, CAR.SIENNA: { (Ecu.engine, 0x700, None): [ b'\x01896630832100\x00\x00\x00\x00', @@ -1557,14 +1682,17 @@ FW_VERSIONS = { b'\x028966333S8000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333T0100\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x028966333V4000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', + b'\x028966333W1000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896633T09000\x00\x00\x00\x00897CF3307001\x00\x00\x00\x00', b'\x01896633T38000\x00\x00\x00\x00', + b'\x01896633T58000\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152633423\x00\x00\x00\x00\x00\x00', b'F152633680\x00\x00\x00\x00\x00\x00', b'F152633681\x00\x00\x00\x00\x00\x00', b'F152633F50\x00\x00\x00\x00\x00\x00', + b'F152633F51\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B33252\x00\x00\x00\x00\x00\x00', @@ -1580,6 +1708,7 @@ FW_VERSIONS = { b'\x018821F6201300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F0610000\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', b'\x028646F33030D0\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303100\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', b'\x028646F3303200\x00\x00\x00\x008646G26011A0\x00\x00\x00\x00', @@ -1644,7 +1773,10 @@ FW_VERSIONS = { (Ecu.engine, 0x700, None): [ b'\x018966378B2100\x00\x00\x00\x00', b'\x018966378B3000\x00\x00\x00\x00', + b'\x018966378B4100\x00\x00\x00\x00', + b'\x018966378G2000\x00\x00\x00\x00', b'\x018966378G3000\x00\x00\x00\x00', + b'\x018966378B2000\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152678221\x00\x00\x00\x00\x00\x00', @@ -1665,6 +1797,7 @@ FW_VERSIONS = { CAR.LEXUS_NXH_TSS2: { (Ecu.engine, 0x7e0, None): [ b'\x0237887000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', + b'\x02378A0000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'F152678210\x00\x00\x00\x00\x00\x00', @@ -1674,6 +1807,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x750, 0xf): [ b'\x018821F3301400\x00\x00\x00\x00', + b'\x018821F3301300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ b'\x028646F78030A0\x00\x00\x00\x008646G2601200\x00\x00\x00\x00', @@ -1711,22 +1845,29 @@ FW_VERSIONS = { ], }, CAR.LEXUS_RC: { + (Ecu.engine, 0x700, None): [ + b'\x01896632478200\x00\x00\x00\x00', + ], (Ecu.engine, 0x7e0, None): [ b'\x0232484000\x00\x00\x00\x00\x00\x00\x00\x0052422000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ + b'F152624150\x00\x00\x00\x00\x00\x00', b'F152624221\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ + b'881512407000\x00\x00\x00\x00', b'881512409100\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ b'8965B24081\x00\x00\x00\x00\x00\x00', + b'8965B24320\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ b'8821F4702300\x00\x00\x00\x00', ], (Ecu.fwdCamera, 0x750, 0x6d): [ + b'8646F2401200\x00\x00\x00\x00', b'8646F2402200\x00\x00\x00\x00', ], }, @@ -1985,7 +2126,9 @@ DBC = { CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_RXH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.CHR: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + CAR.CHR_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CHRH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), + CAR.CHRH_TSS2: dbc_dict('toyota_nodsu_pt_generated', None), CAR.CAMRY: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRYH: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.CAMRY_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -2001,6 +2144,7 @@ DBC = { CAR.AVALONH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.RAV4_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.RAV4_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None), + CAR.RAV4_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None), CAR.COROLLA_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.COROLLAH_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_ES_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -2011,6 +2155,7 @@ DBC = { CAR.LEXUS_CTH: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.RAV4H_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.RAV4H_TSS2_2022: dbc_dict('toyota_nodsu_pt_generated', None), + CAR.RAV4H_TSS2_2023: dbc_dict('toyota_nodsu_pt_generated', None), CAR.LEXUS_NXH: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_NX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_NX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -2025,17 +2170,23 @@ DBC = { EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_IS: 77, CAR.LEXUS_RC: 77, CAR.LEXUS_CTH: 100, CAR.PRIUS_V: 100}) # Toyota/Lexus Safety Sense 2.0 and 2.5 -TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, - 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.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2} +TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_TSS2, CAR.COROLLAH_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_ESH_TSS2, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, + CAR.RAV4H_TSS2_2023, 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.LEXUS_NXH_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.AVALONH_TSS2, CAR.ALPHARDH_TSS2, CAR.CHR_TSS2, CAR.CHRH_TSS2} NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CHRH, CAR.CAMRY, CAR.CAMRYH} -# these cars have a radar which sends ACC messages instead of the camera -RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022} +# the DSU uses the AEB message for longitudinal on these cars +UNSUPPORTED_DSU_CAR = {CAR.LEXUS_IS, CAR.LEXUS_RC} -EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, - CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, +# these cars have a radar which sends ACC messages instead of the camera +RADAR_ACC_CAR = {CAR.RAV4H_TSS2_2022, CAR.RAV4_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023, CAR.CHR_TSS2, CAR.CHRH_TSS2} + +# these cars use the Lane Tracing Assist (LTA) message for lateral control +ANGLE_CONTROL_CAR = {CAR.RAV4H_TSS2_2023, CAR.RAV4_TSS2_2023} + +EV_HYBRID_CAR = {CAR.AVALONH_2019, CAR.AVALONH_TSS2, CAR.CAMRYH, CAR.CAMRYH_TSS2, CAR.CHRH, CAR.CHRH_TSS2, CAR.COROLLAH_TSS2, CAR.HIGHLANDERH, CAR.HIGHLANDERH_TSS2, CAR.PRIUS, + CAR.PRIUS_V, CAR.RAV4H, CAR.RAV4H_TSS2, CAR.RAV4H_TSS2_2022, CAR.RAV4H_TSS2_2023, CAR.LEXUS_CTH, CAR.MIRAI, CAR.LEXUS_ESH, CAR.LEXUS_ESH_TSS2, CAR.LEXUS_NXH, CAR.LEXUS_RXH, CAR.LEXUS_RXH_TSS2, CAR.LEXUS_NXH_TSS2, CAR.PRIUS_TSS2, CAR.ALPHARDH_TSS2} # no resume button press required diff --git a/selfdrive/car/volkswagen/carcontroller.py b/selfdrive/car/volkswagen/carcontroller.py index fff5548671..c17b632450 100644 --- a/selfdrive/car/volkswagen/carcontroller.py +++ b/selfdrive/car/volkswagen/carcontroller.py @@ -2,7 +2,8 @@ from cereal import car from opendbc.can.packer import CANPacker from common.numpy_fast import clip from common.conversions import Conversions as CV -from selfdrive.car import apply_std_steer_torque_limits +from common.realtime import DT_CTRL +from selfdrive.car import apply_driver_steer_torque_limits from selfdrive.car.volkswagen import mqbcan, pqcan from selfdrive.car.volkswagen.values import CANBUS, PQ_CARS, CarControllerParams @@ -23,14 +24,14 @@ class CarController: self.hcaSameTorqueCount = 0 self.hcaEnabledFrameCount = 0 - def update(self, CC, CS, ext_bus): + def update(self, CC, CS, ext_bus, now_nanos): actuators = CC.actuators hud_control = CC.hudControl can_sends = [] # **** Steering Controls ************************************************ # - if self.frame % self.CCP.HCA_STEP == 0: + if self.frame % self.CCP.STEER_STEP == 0: # Logic to avoid HCA state 4 "refused": # * Don't steer unless HCA is in state 3 "ready" or 5 "active" # * Don't steer at standstill @@ -43,20 +44,20 @@ class CarController: if CC.latActive: new_steer = int(round(actuators.steer * self.CCP.STEER_MAX)) - apply_steer = apply_std_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP) + apply_steer = apply_driver_steer_torque_limits(new_steer, self.apply_steer_last, CS.out.steeringTorque, self.CCP) if apply_steer == 0: hcaEnabled = False self.hcaEnabledFrameCount = 0 else: self.hcaEnabledFrameCount += 1 - if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.HCA_STEP): # 118s + if self.hcaEnabledFrameCount >= 118 * (100 / self.CCP.STEER_STEP): # 118s hcaEnabled = False self.hcaEnabledFrameCount = 0 else: hcaEnabled = True if self.apply_steer_last == apply_steer: self.hcaSameTorqueCount += 1 - if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.HCA_STEP): # 1.9s + if self.hcaSameTorqueCount > 1.9 * (100 / self.CCP.STEER_STEP): # 1.9s apply_steer -= (1, -1)[apply_steer < 0] self.hcaSameTorqueCount = 0 else: @@ -88,10 +89,13 @@ class CarController: CS.out.steeringPressed, hud_alert, hud_control)) if self.frame % self.CCP.ACC_HUD_STEP == 0 and self.CP.openpilotLongitudinalControl: + lead_distance = 0 + if hud_control.leadVisible and self.frame * DT_CTRL > 1.0: # Don't display lead until we know the scaling factor + lead_distance = 512 if CS.upscale_lead_car_signal else 8 acc_hud_status = self.CCS.acc_hud_status_value(CS.out.cruiseState.available, CS.out.accFaulted, CC.longActive) set_speed = hud_control.setSpeed * CV.MS_TO_KPH # FIXME: follow the recent displayed-speed updates, also use mph_kmh toggle to fix display rounding problem? can_sends.append(self.CCS.create_acc_hud_control(self.packer_pt, CANBUS.pt, acc_hud_status, set_speed, - hud_control.leadVisible)) + lead_distance)) # **** Stock ACC Button Controls **************************************** # @@ -103,6 +107,7 @@ class CarController: new_actuators = actuators.copy() new_actuators.steer = self.apply_steer_last / self.CCP.STEER_MAX + new_actuators.steerOutputCan = self.apply_steer_last self.gra_acc_counter_last = CS.gra_stock_values["COUNTER"] self.frame += 1 diff --git a/selfdrive/car/volkswagen/carstate.py b/selfdrive/car/volkswagen/carstate.py index 5dc4543c0d..f3cd2808a8 100644 --- a/selfdrive/car/volkswagen/carstate.py +++ b/selfdrive/car/volkswagen/carstate.py @@ -12,6 +12,8 @@ class CarState(CarStateBase): super().__init__(CP) self.CCP = CarControllerParams(CP) self.button_states = {button.event_type: False for button in self.CCP.BUTTONS} + self.esp_hold_confirmation = False + self.upscale_lead_car_signal = False def create_button_events(self, pt_cp, buttons): button_events = [] @@ -42,7 +44,7 @@ class CarState(CarStateBase): ret.vEgoRaw = float(np.mean([ret.wheelSpeeds.fl, ret.wheelSpeeds.fr, ret.wheelSpeeds.rl, ret.wheelSpeeds.rr])) ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = ret.vEgo < 0.1 + ret.standstill = ret.vEgoRaw == 0 # Update steering angle, rate, yaw rate, and driver input torque. VW send # the sign/direction in a separate signal so they must be recombined. @@ -61,7 +63,9 @@ class CarState(CarStateBase): ret.gas = pt_cp.vl["Motor_20"]["MO_Fahrpedalrohwert_01"] / 100.0 ret.gasPressed = ret.gas > 0 ret.brake = pt_cp.vl["ESP_05"]["ESP_Bremsdruck"] / 250.0 # FIXME: this is pressure in Bar, not sure what OP expects - ret.brakePressed = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + brake_pedal_pressed = bool(pt_cp.vl["Motor_14"]["MO_Fahrer_bremst"]) + brake_pressure_detected = bool(pt_cp.vl["ESP_05"]["ESP_Fahrer_bremst"]) + ret.brakePressed = brake_pedal_pressed or brake_pressure_detected ret.parkingBrake = bool(pt_cp.vl["Kombi_01"]["KBI_Handbremse"]) # FIXME: need to include an EPB check as well # Update gear and/or clutch position data. @@ -105,6 +109,7 @@ class CarState(CarStateBase): ret.stockAeb = bool(ext_cp.vl["ACC_10"]["ANB_Teilbremsung_Freigabe"]) or bool(ext_cp.vl["ACC_10"]["ANB_Zielbremsung_Freigabe"]) # Update ACC radar status. + self.acc_type = ext_cp.vl["ACC_06"]["ACC_Typ"] if pt_cp.vl["TSK_06"]["TSK_Status"] == 2: # ACC okay and enabled, but not currently engaged ret.cruiseState.available = True @@ -137,6 +142,9 @@ class CarState(CarStateBase): # Additional safety checks performed in CarInterface. ret.espDisabled = pt_cp.vl["ESP_21"]["ESP_Tastung_passiv"] != 0 + # Digital instrument clusters expect the ACC HUD lead car distance to be scaled differently + self.upscale_lead_car_signal = bool(pt_cp.vl["Kombi_03"]["KBI_Variante"]) + return ret def update_pq(self, pt_cp, cam_cp, ext_cp, trans_type): @@ -152,7 +160,7 @@ class CarState(CarStateBase): # vEgo obtained from Bremse_1 vehicle speed rather than Bremse_3 wheel speeds because Bremse_3 isn't present on NSF ret.vEgoRaw = pt_cp.vl["Bremse_1"]["Geschwindigkeit_neu__Bremse_1_"] * CV.KPH_TO_MS ret.vEgo, ret.aEgo = self.update_speed_kf(ret.vEgoRaw) - ret.standstill = ret.vEgo < 0.1 + ret.standstill = ret.vEgoRaw == 0 # Update steering angle, rate, yaw rate, and driver input torque. VW send # the sign/direction in a separate signal so they must be recombined. @@ -216,16 +224,17 @@ class CarState(CarStateBase): ret.stockAeb = False # Update ACC radar status. - self.acc_type = 0 # TODO: this is ACC "basic" with nonzero min speed, support FtS (1) later + self.acc_type = ext_cp.vl["ACC_System"]["ACS_Typ_ACC"] ret.cruiseState.available = bool(pt_cp.vl["Motor_5"]["GRA_Hauptschalter"]) - ret.cruiseState.enabled = bool(pt_cp.vl["Motor_2"]["GRA_Status"]) + ret.cruiseState.enabled = pt_cp.vl["Motor_2"]["GRA_Status"] in (1, 2) if self.CP.pcmCruise: - ret.accFaulted = ext_cp.vl["ACC_GRA_Anziege"]["ACA_StaACC"] in (6, 7) - # TODO: update opendbc with PQ TSK state for OP long accFaulted + ret.accFaulted = ext_cp.vl["ACC_GRA_Anzeige"]["ACA_StaACC"] in (6, 7) + else: + ret.accFaulted = pt_cp.vl["Motor_2"]["GRA_Status"] == 3 # Update ACC setpoint. When the setpoint reads as 255, the driver has not # yet established an ACC setpoint, so treat it as zero. - ret.cruiseState.speed = ext_cp.vl["ACC_GRA_Anziege"]["ACA_V_Wunsch"] * CV.KPH_TO_MS + ret.cruiseState.speed = ext_cp.vl["ACC_GRA_Anzeige"]["ACA_V_Wunsch"] * CV.KPH_TO_MS if ret.cruiseState.speed > 70: # 255 kph in m/s == no current setpoint ret.cruiseState.speed = 0 @@ -266,8 +275,9 @@ class CarState(CarStateBase): ("Comfort_Signal_Right", "Blinkmodi_02"), # Right turn signal including comfort blink interval ("AB_Gurtschloss_FA", "Airbag_02"), # Seatbelt status, driver ("AB_Gurtschloss_BF", "Airbag_02"), # Seatbelt status, passenger - ("ESP_Fahrer_bremst", "ESP_05"), # Brake pedal pressed - ("ESP_Bremsdruck", "ESP_05"), # Brake pressure applied + ("ESP_Fahrer_bremst", "ESP_05"), # Driver applied brake pressure over threshold + ("MO_Fahrer_bremst", "Motor_14"), # Brake pedal switch + ("ESP_Bremsdruck", "ESP_05"), # Brake pressure ("MO_Fahrpedalrohwert_01", "Motor_20"), # Accelerator pedal value ("EPS_Lenkmoment", "LH_EPS_03"), # Absolute driver torque input ("EPS_VZ_Lenkmoment", "LH_EPS_03"), # Driver torque input sign @@ -275,6 +285,7 @@ class CarState(CarStateBase): ("ESP_Tastung_passiv", "ESP_21"), # Stability control disabled ("ESP_Haltebestaetigung", "ESP_21"), # ESP hold confirmation ("KBI_Handbremse", "Kombi_01"), # Manual handbrake applied + ("KBI_Variante", "Kombi_03"), # Digital/full-screen instrument cluster installed ("TSK_Status", "TSK_06"), # ACC engagement status from drivetrain coordinator ("GRA_Hauptschalter", "GRA_ACC_01"), # ACC button, on/off ("GRA_Abbrechen", "GRA_ACC_01"), # ACC button, cancel @@ -302,9 +313,11 @@ class CarState(CarStateBase): ("ESP_02", 50), # From J104 ABS/ESP controller ("GRA_ACC_01", 33), # From J533 CAN gateway (via LIN from steering wheel controls) ("Gateway_72", 10), # From J533 CAN gateway (aggregated data) + ("Motor_14", 10), # From J623 Engine control module ("Airbag_02", 5), # From J234 Airbag control module ("Kombi_01", 2), # From J285 Instrument cluster ("Blinkmodi_02", 1), # From J519 BCM (sent at 1Hz when no lights active, 50Hz when active) + ("Kombi_03", 0), # From J285 instrument cluster (not present on older cars, 1Hz when present) ] if CP.transmissionType == TransmissionType.automatic: @@ -316,7 +329,6 @@ class CarState(CarStateBase): elif CP.transmissionType == TransmissionType.manual: signals += [("MO_Kuppl_schalter", "Motor_14"), # Clutch switch ("BCM1_Rueckfahrlicht_Schalter", "Gateway_72")] # Reverse light from BCM - checks.append(("Motor_14", 10)) # From J623 Engine control module if CP.networkLocation == NetworkLocation.fwdCamera: # Radars are here on CANBUS.pt @@ -480,13 +492,15 @@ class MqbExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ ("ACC_Wunschgeschw_02", "ACC_02"), # ACC set speed + ("ACC_Typ", "ACC_06"), # Basic vs FtS vs SnG ("AWV2_Freigabe", "ACC_10"), # FCW brake jerk release ("ANB_Teilbremsung_Freigabe", "ACC_10"), # AEB partial braking release ("ANB_Zielbremsung_Freigabe", "ACC_10"), # AEB target braking release ] fwd_radar_checks = [ - ("ACC_10", 50), # From J428 ACC radar control module - ("ACC_02", 17), # From J428 ACC radar control module + ("ACC_06", 50), # From J428 ACC radar control module + ("ACC_10", 50), # From J428 ACC radar control module + ("ACC_02", 17), # From J428 ACC radar control module ] bsm_radar_signals = [ ("SWA_Infostufe_SWA_li", "SWA_01"), # Blind spot object info, left @@ -495,24 +509,26 @@ class MqbExtraSignals: ("SWA_Warnung_SWA_re", "SWA_01"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_01", 20), # From J1086 Lane Change Assist + ("SWA_01", 20), # From J1086 Lane Change Assist ] class PqExtraSignals: # Additional signal and message lists for optional or bus-portable controllers fwd_radar_signals = [ - ("ACA_StaACC", "ACC_GRA_Anziege", 0), # ACC drivetrain coordinator status - ("ACA_V_Wunsch", "ACC_GRA_Anziege", 0), # ACC set speed + ("ACS_Typ_ACC", "ACC_System"), # Basic vs FtS (no SnG support on PQ) + ("ACA_StaACC", "ACC_GRA_Anzeige"), # ACC drivetrain coordinator status + ("ACA_V_Wunsch", "ACC_GRA_Anzeige"), # ACC set speed ] fwd_radar_checks = [ - ("ACC_GRA_Anziege", 25), # From J428 ACC radar control module + ("ACC_System", 50), # From J428 ACC radar control module + ("ACC_GRA_Anzeige", 25), # From J428 ACC radar control module ] bsm_radar_signals = [ - ("SWA_Infostufe_SWA_li", "SWA_1", 0), # Blind spot object info, left - ("SWA_Warnung_SWA_li", "SWA_1", 0), # Blind spot object warning, left - ("SWA_Infostufe_SWA_re", "SWA_1", 0), # Blind spot object info, right - ("SWA_Warnung_SWA_re", "SWA_1", 0), # Blind spot object warning, right + ("SWA_Infostufe_SWA_li", "SWA_1"), # Blind spot object info, left + ("SWA_Warnung_SWA_li", "SWA_1"), # Blind spot object warning, left + ("SWA_Infostufe_SWA_re", "SWA_1"), # Blind spot object info, right + ("SWA_Warnung_SWA_re", "SWA_1"), # Blind spot object warning, right ] bsm_radar_checks = [ - ("SWA_1", 20), # From J1086 Lane Change Assist + ("SWA_1", 20), # From J1086 Lane Change Assist ] diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 870f3ab163..2f8bd8661b 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,8 +1,7 @@ from cereal import car from panda import Panda from common.conversions import Conversions as CV -from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, \ - gen_empty_fingerprint, get_safety_config +from selfdrive.car import STD_CARGO_KG, get_safety_config from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter @@ -22,24 +21,24 @@ class CarInterface(CarInterfaceBase): self.cp_ext = self.cp_cam @staticmethod - def get_params(candidate, fingerprint=gen_empty_fingerprint(), car_fw=None, experimental_long=False): - ret = CarInterfaceBase.get_std_params(candidate, fingerprint) + def _get_params(ret, candidate, fingerprint, car_fw, experimental_long): ret.carName = "volkswagen" - ret.radarOffCan = True + ret.radarUnavailable = True + + use_off_car_defaults = len(fingerprint[0]) == 0 # Pick sensible carParams during offline doc generation/CI jobs if candidate in PQ_CARS: # Set global PQ35/PQ46/NMS parameters ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagenPq)] ret.enableBsm = 0x3BA in fingerprint[0] # SWA_1 - if 0x440 in fingerprint[0]: # Getriebe_1 + if 0x440 in fingerprint[0] or use_off_car_defaults: # Getriebe_1 ret.transmissionType = TransmissionType.automatic else: ret.transmissionType = TransmissionType.manual if any(msg in fingerprint[1] for msg in (0x1A0, 0xC2)): # Bremse_1, Lenkwinkel_1 ret.networkLocation = NetworkLocation.gateway - ret.experimentalLongitudinalAvailable = True else: ret.networkLocation = NetworkLocation.fwdCamera @@ -56,7 +55,7 @@ class CarInterface(CarInterfaceBase): ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.volkswagen)] ret.enableBsm = 0x30F in fingerprint[0] # SWA_01 - if 0xAD in fingerprint[0]: # Getriebe_11 + if 0xAD in fingerprint[0] or use_off_car_defaults: # Getriebe_11 ret.transmissionType = TransmissionType.automatic elif 0x187 in fingerprint[0]: # EV_Gearshift ret.transmissionType = TransmissionType.direct @@ -73,7 +72,6 @@ class CarInterface(CarInterfaceBase): ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 ret.steerRatio = 15.6 # Let the params learner figure this out - tire_stiffness_factor = 1.0 # Let the params learner figure this out ret.lateralTuning.pid.kpBP = [0.] ret.lateralTuning.pid.kiBP = [0.] ret.lateralTuning.pid.kf = 0.00006 @@ -82,7 +80,8 @@ class CarInterface(CarInterfaceBase): # Global longitudinal tuning defaults, can be overridden per-vehicle - if experimental_long and candidate in PQ_CARS: + ret.experimentalLongitudinalAvailable = ret.networkLocation == NetworkLocation.gateway or use_off_car_defaults + if experimental_long: # Proof-of-concept, prep for E2E only. No radar points available. Panda ALLOW_DEBUG firmware required. ret.openpilotLongitudinalControl = True ret.safetyConfigs[0].safetyParam |= Panda.FLAG_VOLKSWAGEN_LONG_CONTROL @@ -90,7 +89,11 @@ class CarInterface(CarInterfaceBase): ret.minEnableSpeed = 4.5 ret.pcmCruise = not ret.openpilotLongitudinalControl - ret.longitudinalActuatorDelayUpperBound = 0.5 # s + ret.stoppingControl = True + ret.startingState = True + ret.startAccel = 1.0 + ret.vEgoStarting = 1.0 + ret.vEgoStopping = 1.0 ret.longitudinalTuning.kpV = [0.1] ret.longitudinalTuning.kiV = [0.0] @@ -104,6 +107,11 @@ class CarInterface(CarInterfaceBase): ret.mass = 2011 + STD_CARGO_KG ret.wheelbase = 2.98 + elif candidate == CAR.CRAFTER_MK2: + ret.mass = 2100 + STD_CARGO_KG + ret.wheelbase = 3.64 # SWB, LWB is 4.49, TBD how to detect difference + ret.minSteerSpeed = 50 * CV.KPH_TO_MS + elif candidate == CAR.GOLF_MK7: ret.mass = 1397 + STD_CARGO_KG ret.wheelbase = 2.62 @@ -131,7 +139,6 @@ class CarInterface(CarInterfaceBase): elif candidate == CAR.SHARAN_MK2: ret.mass = 1639 + STD_CARGO_KG ret.wheelbase = 2.92 - ret.minEnableSpeed = 30 * CV.KPH_TO_MS ret.minSteerSpeed = 50 * CV.KPH_TO_MS ret.steerActuatorDelay = 0.2 @@ -180,6 +187,10 @@ class CarInterface(CarInterfaceBase): ret.mass = 1227 + STD_CARGO_KG ret.wheelbase = 2.64 + elif candidate == CAR.SKODA_FABIA_MK4: + ret.mass = 1266 + STD_CARGO_KG + ret.wheelbase = 2.56 + elif candidate == CAR.SKODA_KAMIQ_MK1: ret.mass = 1265 + STD_CARGO_KG ret.wheelbase = 2.66 @@ -208,10 +219,7 @@ class CarInterface(CarInterfaceBase): raise ValueError(f"unsupported car {candidate}") ret.autoResumeSng = ret.minEnableSpeed == -1 - ret.rotationalInertia = scale_rot_inertia(ret.mass, ret.wheelbase) ret.centerToFront = ret.wheelbase * 0.45 - ret.tireStiffnessFront, ret.tireStiffnessRear = scale_tire_stiffness(ret.mass, ret.wheelbase, ret.centerToFront, - tire_stiffness_factor=tire_stiffness_factor) return ret # returns a car.CarState @@ -231,7 +239,7 @@ class CarInterface(CarInterfaceBase): events.add(EventName.belowSteerSpeed) if self.CS.CP.openpilotLongitudinalControl: - if ret.vEgo < self.CP.minEnableSpeed + 2.: + if ret.vEgo < self.CP.minEnableSpeed + 0.5: events.add(EventName.belowEngageSpeed) if c.enabled and ret.vEgo < self.CP.minEnableSpeed: events.add(EventName.speedTooLow) @@ -240,5 +248,5 @@ class CarInterface(CarInterfaceBase): return ret - def apply(self, c): - return self.CC.update(c, self.CS, self.ext_bus) + def apply(self, c, now_nanos): + return self.CC.update(c, self.CS, self.ext_bus, now_nanos) diff --git a/selfdrive/car/volkswagen/mqbcan.py b/selfdrive/car/volkswagen/mqbcan.py index 3819f4f76f..30a51f6fe6 100644 --- a/selfdrive/car/volkswagen/mqbcan.py +++ b/selfdrive/car/volkswagen/mqbcan.py @@ -36,3 +36,73 @@ def create_acc_buttons_control(packer, bus, gra_stock_values, counter, cancel=Fa }) return packer.make_can_msg("GRA_ACC_01", bus, values) + + +def acc_control_value(main_switch_on, acc_faulted, long_active): + if acc_faulted: + acc_control = 6 + elif long_active: + acc_control = 3 + elif main_switch_on: + acc_control = 2 + else: + acc_control = 0 + + return acc_control + + +def acc_hud_status_value(main_switch_on, acc_faulted, long_active): + # TODO: happens to resemble the ACC control value for now, but extend this for init/gas override later + return acc_control_value(main_switch_on, acc_faulted, long_active) + + +def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, stopping, starting, esp_hold): + commands = [] + + acc_06_values = { + "ACC_Typ": acc_type, + "ACC_Status_ACC": acc_control, + "ACC_StartStopp_Info": enabled, + "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, + "ACC_zul_Regelabw_unten": 0.2, # TODO: dynamic adjustment of comfort-band + "ACC_zul_Regelabw_oben": 0.2, # TODO: dynamic adjustment of comfort-band + "ACC_neg_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits + "ACC_pos_Sollbeschl_Grad_02": 4.0 if enabled else 0, # TODO: dynamic adjustment of jerk limits + "ACC_Anfahren": starting, + "ACC_Anhalten": stopping, + } + commands.append(packer.make_can_msg("ACC_06", bus, acc_06_values)) + + if starting: + acc_hold_type = 4 # hold release / startup + elif esp_hold: + acc_hold_type = 3 # hold standby + elif stopping: + acc_hold_type = 1 # hold request + else: + acc_hold_type = 0 + + acc_07_values = { + "ACC_Anhalteweg": 0.75 if stopping else 20.46, # Distance to stop (stopping coordinator handles terminal roll-out) + "ACC_Freilauf_Info": 2 if enabled else 0, + "ACC_Folgebeschl": 3.02, # Not using secondary controller accel unless and until we understand its impact + "ACC_Sollbeschleunigung_02": accel if enabled else 3.01, + "ACC_Anforderung_HMS": acc_hold_type, + "ACC_Anfahren": starting, + "ACC_Anhalten": stopping, + } + commands.append(packer.make_can_msg("ACC_07", bus, acc_07_values)) + + return commands + + +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): + values = { + "ACC_Status_Anzeige": acc_hud_status, + "ACC_Wunschgeschw_02": set_speed if set_speed < 250 else 327.36, + "ACC_Gesetzte_Zeitluecke": 3, + "ACC_Display_Prio": 3, + "ACC_Abstandsindex": lead_distance, + } + + return packer.make_can_msg("ACC_02", bus, values) diff --git a/selfdrive/car/volkswagen/pqcan.py b/selfdrive/car/volkswagen/pqcan.py index 0bcbf6abb3..84200c2921 100644 --- a/selfdrive/car/volkswagen/pqcan.py +++ b/selfdrive/car/volkswagen/pqcan.py @@ -66,6 +66,7 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, "ACS_Sta_ADR": acc_control, "ACS_StSt_Info": acc_control != 1, "ACS_Typ_ACC": acc_type, + "ACS_Anhaltewunsch": acc_type == 1 and stopping, "ACS_Sollbeschl": accel if acc_control == 1 else 3.01, "ACS_zul_Regelabw": 0.2 if acc_control == 1 else 1.27, "ACS_max_AendGrad": 3.0 if acc_control == 1 else 5.08, @@ -76,15 +77,15 @@ def create_acc_accel_control(packer, bus, acc_type, enabled, accel, acc_control, return commands -def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_visible): +def create_acc_hud_control(packer, bus, acc_hud_status, set_speed, lead_distance): values = { "ACA_StaACC": acc_hud_status, "ACA_Zeitluecke": 2, "ACA_V_Wunsch": set_speed, - "ACA_gemZeitl": 8 if lead_visible else 0, + "ACA_gemZeitl": lead_distance, # TODO: ACA_ID_StaACC, ACA_AnzDisplay, ACA_kmh_mph, ACA_PrioDisp, ACA_Aend_Zeitluecke # display/display-prio handling probably needed to stop confusing the instrument cluster # kmh_mph handling probably needed to resolve rounding errors in displayed setpoint } - return packer.make_can_msg("ACC_GRA_Anziege", bus, values) + return packer.make_can_msg("ACC_GRA_Anzeige", bus, values) diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index 8c679c7406..bdd82088a1 100755 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -18,7 +18,7 @@ Button = namedtuple('Button', ['event_type', 'can_addr', 'can_msg', 'values']) class CarControllerParams: - HCA_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz + STEER_STEP = 2 # HCA_01/HCA_1 message frequency 50Hz ACC_CONTROL_STEP = 2 # ACC_06/ACC_07/ACC_System frequency 50Hz ACCEL_MAX = 2.0 # 2.0 m/s max acceleration @@ -36,7 +36,7 @@ class CarControllerParams: if CP.carFingerprint in PQ_CARS: self.LDW_STEP = 5 # LDW_1 message frequency 20Hz - self.ACC_HUD_STEP = 4 # ACC_GRA_Anziege frequency 25Hz + self.ACC_HUD_STEP = 4 # ACC_GRA_Anzeige frequency 25Hz self.STEER_DRIVER_ALLOWANCE = 80 # Driver intervention threshold 0.8 Nm self.STEER_DELTA_UP = 6 # Max HCA reached in 1.00s (STEER_MAX / (50Hz * 1.00)) self.STEER_DELTA_DOWN = 10 # Min HCA reached in 0.60s (STEER_MAX / (50Hz * 0.60)) @@ -111,6 +111,7 @@ class CANBUS: class CAR: ARTEON_MK1 = "VOLKSWAGEN ARTEON 1ST GEN" # Chassis AN, Mk1 VW Arteon and variants ATLAS_MK1 = "VOLKSWAGEN ATLAS 1ST GEN" # Chassis CA, Mk1 VW Atlas and Atlas Cross Sport + CRAFTER_MK2 = "VOLKSWAGEN CRAFTER 2ND GEN" # Chassis SY/SZ, Mk2 VW Crafter, VW Grand California, MAN TGE GOLF_MK7 = "VOLKSWAGEN GOLF 7TH GEN" # Chassis 5G/AU/BA/BE, Mk7 VW Golf and variants JETTA_MK7 = "VOLKSWAGEN JETTA 7TH GEN" # Chassis BU, Mk7 VW Jetta PASSAT_MK8 = "VOLKSWAGEN PASSAT 8TH GEN" # Chassis 3G, Mk8 VW Passat and variants @@ -122,12 +123,13 @@ class CAR: TIGUAN_MK2 = "VOLKSWAGEN TIGUAN 2ND GEN" # Chassis AD/BW, Mk2 VW Tiguan and variants TOURAN_MK2 = "VOLKSWAGEN TOURAN 2ND GEN" # Chassis 1T, Mk2 VW Touran and variants TRANSPORTER_T61 = "VOLKSWAGEN TRANSPORTER T6.1" # Chassis 7H/7L, T6-facelift Transporter/Multivan/Caravelle/California - TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW VW T-Roc and variants + TROC_MK1 = "VOLKSWAGEN T-ROC 1ST GEN" # Chassis A1, Mk1 VW T-Roc and variants AUDI_A3_MK3 = "AUDI A3 3RD GEN" # Chassis 8V/FF, Mk3 Audi A3 and variants AUDI_Q2_MK1 = "AUDI Q2 1ST GEN" # Chassis GA, Mk1 Audi Q2 (RoW) and Q2L (China only) AUDI_Q3_MK2 = "AUDI Q3 2ND GEN" # Chassis 8U/F3/FS, Mk2 Audi Q3 and variants SEAT_ATECA_MK1 = "SEAT ATECA 1ST GEN" # Chassis 5F, Mk1 SEAT Ateca and CUPRA Ateca SEAT_LEON_MK3 = "SEAT LEON 3RD GEN" # Chassis 5F, Mk3 SEAT Leon and variants + SKODA_FABIA_MK4 = "SKODA FABIA 4TH GEN" # Chassis PJ, Mk4 Skoda Fabia SKODA_KAMIQ_MK1 = "SKODA KAMIQ 1ST GEN" # Chassis NW, Mk1 Skoda Kamiq SKODA_KAROQ_MK1 = "SKODA KAROQ 1ST GEN" # Chassis NU, Mk1 Skoda Karoq SKODA_KODIAQ_MK1 = "SKODA KODIAQ 1ST GEN" # Chassis NS, Mk1 Skoda Kodiaq @@ -151,75 +153,83 @@ class Footnote(Enum): PASSAT = CarFootnote( "Refers only to the MQB-based European B8 Passat, not the NMS Passat in the USA/China/Mideast markets.", Column.MODEL) - VW_HARNESS = CarFootnote( - "Model-years 2021 and beyond may have a new camera harness design, which isn't yet available from the comma " + - "store. Before ordering, remove the Lane Assist camera cover and check to see if the connector is black " + - "(older design) or light brown (newer design). In the interim, if your car has a J533 connector CAN gateway " + - "inside the dashboard, choose \"VW J533 Development\" from the vehicle drop-down for a suitable harness. " + - "(Some newer models are also observed to not have a J533 connector.)", - Column.MODEL) - VW_VARIANT = CarFootnote( - "Includes versions with extra rear cargo space (may be called Variant, Estate, SportWagen, Shooting Brake, etc.)", - Column.MODEL) + VW_EXP_LONG = CarFootnote ( + "Only available for vehicles using a gateway (J533) harness. At this time, vehicles using a camera harness " + + "are limited to using stock ACC.", + Column.LONGITUDINAL) + VW_MQB_A0 = CarFootnote( + "Model-years 2022 and beyond may have a combined CAN gateway and BCM, which is supported by openpilot " + + "in software, but doesn't yet have a harness available from the comma store.", + Column.HARNESS) @dataclass class VWCarInfo(CarInfo): package: str = "Adaptive Cruise Control (ACC) & Lane Assist" - harness: Enum = Harness.vw + harness: Enum = Harness.j533 + + def init_make(self, CP: car.CarParams): + self.footnotes.insert(0, Footnote.VW_EXP_LONG) CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { CAR.ARTEON_MK1: [ - VWCarInfo("Volkswagen Arteon 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon R 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon eHybrid 2020-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen CC 2018-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533, video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon R 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon eHybrid 2020-22", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), ], CAR.ATLAS_MK1: [ - VWCarInfo("Volkswagen Atlas 2018-23", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Atlas Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont Cross Sport 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Teramont X 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Atlas 2018-23"), + VWCarInfo("Volkswagen Atlas Cross Sport 2021-22"), + VWCarInfo("Volkswagen Teramont 2018-22"), + VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"), + VWCarInfo("Volkswagen Teramont X 2021-22"), + ], + CAR.CRAFTER_MK2: [ + VWCarInfo("Volkswagen Crafter 2017-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("Volkswagen e-Crafter 2018-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("Volkswagen Grand California 2019-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("MAN TGE 2017-23", video_link="https://youtu.be/4100gLeabmo"), + VWCarInfo("MAN eTGE 2020-23", video_link="https://youtu.be/4100gLeabmo"), ], CAR.GOLF_MK7: [ VWCarInfo("Volkswagen e-Golf 2014-20"), - VWCarInfo("Volkswagen Golf 2015-20", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf 2015-20"), VWCarInfo("Volkswagen Golf Alltrack 2015-19"), VWCarInfo("Volkswagen Golf GTD 2015-20"), VWCarInfo("Volkswagen Golf GTE 2015-20"), VWCarInfo("Volkswagen Golf GTI 2015-21"), - VWCarInfo("Volkswagen Golf R 2015-19", footnotes=[Footnote.VW_VARIANT]), + VWCarInfo("Volkswagen Golf R 2015-19"), VWCarInfo("Volkswagen Golf SportsVan 2015-20"), ], CAR.JETTA_MK7: [ - VWCarInfo("Volkswagen Jetta 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Jetta GLI 2021-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Jetta 2018-22"), + VWCarInfo("Volkswagen Jetta GLI 2021-22"), ], CAR.PASSAT_MK8: [ - VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.PASSAT, Footnote.VW_VARIANT], harness=Harness.j533), - VWCarInfo("Volkswagen Passat Alltrack 2015-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Passat GTE 2015-22", footnotes=[Footnote.VW_HARNESS, Footnote.VW_VARIANT], harness=Harness.j533), + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), + VWCarInfo("Volkswagen Passat Alltrack 2015-22"), + VWCarInfo("Volkswagen Passat GTE 2015-22"), ], - CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22", harness=Harness.j533), + CAR.PASSAT_NMS: VWCarInfo("Volkswagen Passat NMS 2017-22"), CAR.POLO_MK6: [ - VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Polo 2020-22", footnotes=[Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo GTI 2020-22", footnotes=[Footnote.VW_MQB_A0]), ], CAR.SHARAN_MK2: [ - VWCarInfo("Volkswagen Sharan 2018-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("SEAT Alhambra 2018-20", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Sharan 2018-22"), + VWCarInfo("SEAT Alhambra 2018-20"), ], - CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2019-22", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022"), + CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), + CAR.TIGUAN_MK2: VWCarInfo("Volkswagen Tiguan 2018-23"), CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2017"), CAR.TRANSPORTER_T61: [ - VWCarInfo("Volkswagen Caravelle 2020", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), - VWCarInfo("Volkswagen California 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + VWCarInfo("Volkswagen Caravelle 2020"), + VWCarInfo("Volkswagen California 2021"), ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_HARNESS], harness=Harness.j533), + CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2021", footnotes=[Footnote.VW_MQB_A0]), CAR.AUDI_A3_MK3: [ VWCarInfo("Audi A3 2014-19"), VWCarInfo("Audi A3 Sportback e-tron 2017-18"), @@ -227,14 +237,15 @@ CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { VWCarInfo("Audi S3 2015-17"), ], CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018"), - CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2020-21"), + CAR.AUDI_Q3_MK2: VWCarInfo("Audi Q3 2019-23"), CAR.SEAT_ATECA_MK1: VWCarInfo("SEAT Ateca 2018"), CAR.SEAT_LEON_MK3: VWCarInfo("SEAT Leon 2014-20"), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.KAMIQ]), - CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21", footnotes=[Footnote.VW_HARNESS]), - CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2018-19"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020"), - CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-18"), + CAR.SKODA_FABIA_MK4: VWCarInfo("Škoda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0]), + CAR.SKODA_KAMIQ_MK1: VWCarInfo("Škoda Kamiq 2021", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), + CAR.SKODA_KAROQ_MK1: VWCarInfo("Škoda Karoq 2019-21"), + CAR.SKODA_KODIAQ_MK1: VWCarInfo("Škoda Kodiaq 2017-23"), + CAR.SKODA_SCALA_MK1: VWCarInfo("Škoda Scala 2020", footnotes=[Footnote.VW_MQB_A0]), + CAR.SKODA_SUPERB_MK3: VWCarInfo("Škoda Superb 2015-22"), CAR.SKODA_OCTAVIA_MK3: [ VWCarInfo("Škoda Octavia 2015, 2018-19"), VWCarInfo("Škoda Octavia RS 2016"), @@ -279,27 +290,34 @@ FW_VERSIONS = { CAR.ARTEON_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x873G0906259F \xf1\x890004', + b'\xf1\x873G0906259N \xf1\x890004', b'\xf1\x873G0906259P \xf1\x890001', b'\xf1\x875NA907115H \xf1\x890002', + b'\xf1\x873G0906259G \xf1\x890004', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158L \xf1\x893611', b'\xf1\x870GC300011L \xf1\x891401', + b'\xf1\x870GC300014M \xf1\x892802', b'\xf1\x870GC300040P \xf1\x891401', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121157161111572900', b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1616001613121177161113772900', b'\xf1\x873Q0959655DL\xf1\x890732\xf1\x82\0161812141812171105141123052J00', + b'\xf1\x873Q0959655CK\xf1\x890711\xf1\x82\x0e1712141712141105121122052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571B41815A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571B00817A1', - b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\00567B0020800', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020800', + b'\xf1\x875WA907145M \xf1\x891051\xf1\x82\x002MB4092M7N', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572T \xf1\x890383', b'\xf1\x875Q0907572J \xf1\x890654', + b'\xf1\x875Q0907572R \xf1\x890771', ], }, CAR.ATLAS_MK1: { @@ -315,11 +333,13 @@ FW_VERSIONS = { b'\xf1\x8703H906026J \xf1\x899971', b'\xf1\x8703H906026S \xf1\x896693', b'\xf1\x8703H906026S \xf1\x899970', + b'\xf1\x873CN906259 \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158A \xf1\x893387', b'\xf1\x8709G927158DR\xf1\x893536', b'\xf1\x8709G927158DR\xf1\x893742', + b'\xf1\x8709G927158F \xf1\x893489', b'\xf1\x8709G927158FT\xf1\x893835', b'\xf1\x8709G927158GL\xf1\x893939', ], @@ -333,6 +353,7 @@ FW_VERSIONS = { (Ecu.eps, 0x712, None): [ b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\00571B60924A1', b'\xf1\x873QF909144B \xf1\x891582\xf1\x82\x0571B6G920A1', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6080105', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820528B6090105', ], (Ecu.fwdRadar, 0x757, None): [ @@ -344,6 +365,23 @@ FW_VERSIONS = { b'\xf1\x875Q0907572P \xf1\x890682', ], }, + CAR.CRAFTER_MK2: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906056EK\xf1\x896391', + ], + # Only current upstreamed vehicle has a manual transmission + #(Ecu.transmission, 0x7e1, None): [ + #], + (Ecu.srs, 0x715, None): [ + b'\xf1\x873Q0959655BG\xf1\x890703\xf1\x82\x0e16120016130012051G1313052900', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872N0909143E \xf1\x897021\xf1\x82\x05163AZ306A2', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572M \xf1\x890233', + ], + }, CAR.GOLF_MK7: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906016A \xf1\x897697', @@ -359,12 +397,14 @@ FW_VERSIONS = { b'\xf1\x8704L906021DT\xf1\x895520', b'\xf1\x8704L906021DT\xf1\x898127', b'\xf1\x8704L906021N \xf1\x895518', + b'\xf1\x8704L906026BN\xf1\x891197', b'\xf1\x8704L906026BP\xf1\x897608', b'\xf1\x8704L906026NF\xf1\x899528', b'\xf1\x8704L906056CL\xf1\x893823', b'\xf1\x8704L906056CR\xf1\x895813', b'\xf1\x8704L906056HE\xf1\x893758', b'\xf1\x8704L906056HN\xf1\x896590', + b'\xf1\x8704L906056HT\xf1\x896591', b'\xf1\x870EA906016A \xf1\x898343', b'\xf1\x870EA906016E \xf1\x894219', b'\xf1\x870EA906016F \xf1\x894238', @@ -372,6 +412,7 @@ FW_VERSIONS = { b'\xf1\x870EA906016Q \xf1\x895993', b'\xf1\x870EA906016S \xf1\x897207', b'\xf1\x875G0906259 \xf1\x890007', + b'\xf1\x875G0906259D \xf1\x890002', b'\xf1\x875G0906259J \xf1\x890002', b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259N \xf1\x890003', @@ -396,6 +437,7 @@ FW_VERSIONS = { b'\xf1\x870CW300041H \xf1\x891010', b'\xf1\x870CW300042F \xf1\x891604', b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043E \xf1\x891603', b'\xf1\x870CW300044S \xf1\x894530', b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300045 \xf1\x894531', @@ -407,6 +449,7 @@ FW_VERSIONS = { b'\xf1\x870D9300012 \xf1\x895045', b'\xf1\x870D9300014M \xf1\x895004', b'\xf1\x870D9300014Q \xf1\x895006', + b'\xf1\x870D9300020J \xf1\x894902', b'\xf1\x870D9300020Q \xf1\x895201', b'\xf1\x870D9300020S \xf1\x895201', b'\xf1\x870D9300040A \xf1\x893613', @@ -432,6 +475,7 @@ FW_VERSIONS = { b'\xf1\x875Q0959655AA\xf1\x890388\xf1\x82\x111413001113120053114317121C111C9113', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\x1314160011123300314211012230229333463100', b'\xf1\x875Q0959655BS\xf1\x890403\xf1\x82\x1314160011123300314240012250229333463100', + b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2251229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142404A2252229333463100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x13141600111233003142405A2252229333463100', b'\xf1\x875Q0959655C \xf1\x890361\xf1\x82\x111413001112120004110415121610169112', @@ -454,6 +498,7 @@ FW_VERSIONS = { b'\xf1\x873Q0909144F \xf1\x895043\xf1\x82\x0561A01612A0', b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\x0566A0J612A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A00514A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A01613A1', b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566A0J712A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571A0J714A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571A0JA15A1', @@ -473,6 +518,7 @@ FW_VERSIONS = { b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\x0511A00403A0', b'\xf1\x875Q0909144R \xf1\x891061\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00404A1', + b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00504A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A00604A1', b'\xf1\x875Q0909144S \xf1\x891063\xf1\x82\x0516A07A02A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521A00507A1', @@ -554,7 +600,9 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8703N906026E \xf1\x892114', b'\xf1\x8704E906023AH\xf1\x893379', + b'\xf1\x8704L906026DP\xf1\x891538', b'\xf1\x8704L906026ET\xf1\x891990', + b'\xf1\x8704L906026FP\xf1\x892012', b'\xf1\x8704L906026GA\xf1\x892013', b'\xf1\x8704L906026KD\xf1\x894798', b'\xf1\x873G0906264 \xf1\x890004', @@ -562,6 +610,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870CW300043H \xf1\x891601', b'\xf1\x870CW300048R \xf1\x890610', + b'\xf1\x870D9300013A \xf1\x894905', b'\xf1\x870D9300014L \xf1\x895002', b'\xf1\x870D9300041A \xf1\x894801', b'\xf1\x870DD300045T \xf1\x891601', @@ -570,6 +619,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AE\xf1\x890195\xf1\x82\r56140056130012416612124111', + b'\xf1\x873Q0959655AF\xf1\x890195\xf1\x82\r56140056130012026612120211', b'\xf1\x873Q0959655AN\xf1\x890306\xf1\x82\r58160058140013036914110311', b'\xf1\x873Q0959655BA\xf1\x890195\xf1\x82\r56140056130012516612125111', b'\xf1\x873Q0959655BB\xf1\x890195\xf1\x82\r56140056130012026612120211', @@ -579,6 +629,8 @@ FW_VERSIONS = { ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00611A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566B00711A1', + b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514B0060703', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0060803', b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820522B0080803', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521B00606A1', @@ -587,6 +639,7 @@ FW_VERSIONS = { b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567B0020600', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x873Q0907572A \xf1\x890126', b'\xf1\x873Q0907572A \xf1\x890130', b'\xf1\x873Q0907572B \xf1\x890192', b'\xf1\x873Q0907572C \xf1\x890195', @@ -683,46 +736,60 @@ FW_VERSIONS = { }, CAR.TIGUAN_MK2: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8703N906026D \xf1\x893680', b'\xf1\x8704E906027NB\xf1\x899504', b'\xf1\x8704L906026EJ\xf1\x893661', b'\xf1\x8704L906027G \xf1\x899893', b'\xf1\x875N0906259 \xf1\x890002', + b'\xf1\x875NA906259H \xf1\x890002', b'\xf1\x875NA907115E \xf1\x890005', b'\xf1\x8783A907115B \xf1\x890005', + b'\xf1\x8783A907115F \xf1\x890002', b'\xf1\x8783A907115G \xf1\x890001', + b'\xf1\x8783A907115K \xf1\x890001', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158DT\xf1\x893698', + b'\xf1\x8709G927158FM\xf1\x893757', b'\xf1\x8709G927158GC\xf1\x893821', b'\xf1\x8709G927158GD\xf1\x893820', + b'\xf1\x8709G927158GM\xf1\x893936', b'\xf1\x870D9300043 \xf1\x895202', b'\xf1\x870DL300011N \xf1\x892001', b'\xf1\x870DL300011N \xf1\x892012', + b'\xf1\x870DL300012P \xf1\x892103', b'\xf1\x870DL300013A \xf1\x893005', b'\xf1\x870DL300013G \xf1\x892119', b'\xf1\x870DL300013G \xf1\x892120', + b'\xf1\x870DL300014C \xf1\x893703', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655AR\xf1\x890317\xf1\x82\02331310031333334313132573732379333313100', + b'\xf1\x875Q0959655BJ\xf1\x890336\xf1\x82\x1312110031333300314232583732379333423100', b'\xf1\x875Q0959655BM\xf1\x890403\xf1\x82\02316143231313500314641011750179333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02312110031333300314240583752379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\02331310031333336313140013950399333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140013750379333423100', b'\xf1\x875Q0959655BT\xf1\x890403\xf1\x82\x1331310031333334313140573752379333423100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1316143231313500314647021750179333613100', + b'\xf1\x875Q0959655CG\xf1\x890421\xf1\x82\x1331310031333300314240024050409333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143M \xf1\x892041\xf1\x820529A6060603', b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567A6000600', + b'\xf1\x875QF909144A \xf1\x895581\xf1\x82\x0571A60834A1', b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\00571A60634A1', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571A62A32A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A60604A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\00521A60804A1', + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x002SA6092SOM', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x872Q0907572J \xf1\x890156', + b'\xf1\x872Q0907572M \xf1\x890233', b'\xf1\x872Q0907572Q \xf1\x890342', b'\xf1\x872Q0907572R \xf1\x890372', b'\xf1\x872Q0907572T \xf1\x890383', @@ -793,6 +860,7 @@ FW_VERSIONS = { b'\xf1\x875G0906259L \xf1\x890002', b'\xf1\x875G0906259Q \xf1\x890002', b'\xf1\x878V0906259F \xf1\x890002', + b'\xf1\x878V0906259H \xf1\x890002', b'\xf1\x878V0906259J \xf1\x890002', b'\xf1\x878V0906259K \xf1\x890001', b'\xf1\x878V0906264B \xf1\x890003', @@ -803,6 +871,7 @@ FW_VERSIONS = { b'\xf1\x870CW300044T \xf1\x895245', b'\xf1\x870CW300048 \xf1\x895201', b'\xf1\x870D9300012 \xf1\x894912', + b'\xf1\x870D9300012 \xf1\x894931', b'\xf1\x870D9300012K \xf1\x894513', b'\xf1\x870D9300013B \xf1\x894931', b'\xf1\x870D9300041N \xf1\x894512', @@ -825,12 +894,14 @@ FW_VERSIONS = { b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890825\xf1\x82\023111112111111--171115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\023121111111211--261117141112231291163221', + b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13111112111111--241115141112221291163221', b'\xf1\x875Q0959655J \xf1\x890830\xf1\x82\x13121111111111--341117141212231291163221', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112110004110411111421149114', b'\xf1\x875Q0959655N \xf1\x890361\xf1\x82\0211212001112111104110411111521159114', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144H \xf1\x895061\xf1\x82\00566G0HA14A1', + b'\xf1\x873Q0909144J \xf1\x895063\xf1\x82\x0566G0HA14A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G01A16A1', b'\xf1\x873Q0909144K \xf1\x895072\xf1\x82\x0571G0HA16A1', b'\xf1\x873Q0909144L \xf1\x895081\xf1\x82\x0571G0JA14A1', @@ -869,20 +940,24 @@ FW_VERSIONS = { CAR.AUDI_Q3_MK2: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705E906018N \xf1\x899970', + b'\xf1\x8705L906022M \xf1\x890901', b'\xf1\x8783A906259 \xf1\x890001', b'\xf1\x8783A906259 \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x8709G927158CN\xf1\x893608', + b'\xf1\x870GC300045D \xf1\x892802', b'\xf1\x870GC300046F \xf1\x892701', ], (Ecu.srs, 0x715, None): [ b'\xf1\x875Q0959655BF\xf1\x890403\xf1\x82\x1321211111211200311121232152219321422111', + b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111224118A119321532111', b'\xf1\x875Q0959655CC\xf1\x890421\xf1\x82\x131111111111120031111237116A119321532111', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000300', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567G6000800', + b'\xf1\x875QF909144B \xf1\x895582\xf1\x82\x0571G60533A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572R \xf1\x890372', @@ -911,28 +986,53 @@ FW_VERSIONS = { b'\xf1\x8704L906021EL\xf1\x897542', b'\xf1\x8704L906026BP\xf1\x891198', b'\xf1\x8704L906026BP\xf1\x897608', + b'\xf1\x8704L906056CR\xf1\x892797', b'\xf1\x8705E906018AS\xf1\x899596', + b'\xf1\x878V0906264H \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300041D \xf1\x891004', + b'\xf1\x870CW300041G \xf1\x891003', b'\xf1\x870CW300050J \xf1\x891908', b'\xf1\x870D9300042M \xf1\x895016', ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890189\xf1\x82\r11110011110011021511110200', + b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11110011110011021511110200', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r12110012120012021612110200', + b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\x0e1312001313001305171311052900', b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\0161312001313001305171311052900', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909144AB\xf1\x891082\xf1\x82\00521N01342A1', b'\xf1\x875Q0909144P \xf1\x891043\xf1\x82\00511N01805A0', + b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\x0521N01309A1', b'\xf1\x875Q0909144T \xf1\x891072\xf1\x82\00521N05808A1', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572B \xf1\x890200\xf1\x82\00101', b'\xf1\x875Q0907572H \xf1\x890620', + b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', ], }, + CAR.SKODA_FABIA_MK4: { + (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8705E906018CF\xf1\x891905', + ], + (Ecu.transmission, 0x7e1, None): [ + b'\xf1\x870CW300051M \xf1\x891936', + ], + (Ecu.srs, 0x715, None): [ + b'\xf1\x875QF959655AT\xf1\x890755\xf1\x82\x1311110011110011111100110200--1111120749', + ], + (Ecu.eps, 0x712, None): [ + b'\xf1\x872Q1909144S \xf1\x896042', + ], + (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', + ], + }, CAR.SKODA_KAMIQ_MK1: { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8705C906032M \xf1\x891333', @@ -976,31 +1076,48 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'\xf1\x8704E906027DD\xf1\x893123', b'\xf1\x8704L906026DE\xf1\x895418', + b'\xf1\x8704L906026EJ\xf1\x893661', + b'\xf1\x8704L906026HT\xf1\x893617', + b'\xf1\x8783A907115E \xf1\x890001', + b'\xf1\x8705E906018DJ\xf1\x890915', b'\xf1\x875NA907115E \xf1\x890003', + b'\xf1\x875NA907115E \xf1\x890005', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x870D9300043 \xf1\x895202', + b'\xf1\x870DL300011N \xf1\x892014', b'\xf1\x870DL300012M \xf1\x892107', b'\xf1\x870DL300012N \xf1\x892110', b'\xf1\x870DL300013G \xf1\x892119', + b'\xf1\x870GC300014N \xf1\x892801', + b'\xf1\x870GC300046Q \xf1\x892802', ], (Ecu.srs, 0x715, None): [ - b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\0161213001211001205212111052100', - b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\0161213001211001205212112052100', + b'\xf1\x873Q0959655AP\xf1\x890306\xf1\x82\r11110011110011421111314211', + b'\xf1\x873Q0959655BJ\xf1\x890703\xf1\x82\x0e1213001211001205212111052100', + b'\xf1\x873Q0959655BK\xf1\x890703\xf1\x82\x0e1213001211001244212111442100', + b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e1213001211001205212112052100', b'\xf1\x873Q0959655CQ\xf1\x890720\xf1\x82\x0e1213111211001205212112052111', + b'\xf1\x873Q0959655DJ\xf1\x890731\xf1\x82\x0e1513001511001205232113052J00', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6050405', b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6060405', + b'\xf1\x875Q0909143P \xf1\x892051\xf1\x820527T6070405', + b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G500', b'\xf1\x875Q0910143C \xf1\x892211\xf1\x82\x0567T600G600', ], (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x872Q0907572Q \xf1\x890342', b'\xf1\x872Q0907572R \xf1\x890372', + b'\xf1\x872Q0907572T \xf1\x890383', + b'\xf1\x872Q0907572AA\xf1\x890396', + b'\xf1\x872Q0907572AB\xf1\x890397', ], }, CAR.SKODA_OCTAVIA_MK3: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704C906025L \xf1\x896198', b'\xf1\x8704E906016ER\xf1\x895823', b'\xf1\x8704E906027HD\xf1\x893742', b'\xf1\x8704E906027MH\xf1\x894786', @@ -1012,6 +1129,7 @@ FW_VERSIONS = { b'\xf1\x870CW300041L \xf1\x891601', b'\xf1\x870CW300041N \xf1\x891605', b'\xf1\x870CW300043B \xf1\x891601', + b'\xf1\x870CW300043P \xf1\x891605', b'\xf1\x870D9300041C \xf1\x894936', b'\xf1\x870D9300041J \xf1\x894902', b'\xf1\x870D9300041P \xf1\x894507', @@ -1021,6 +1139,7 @@ FW_VERSIONS = { b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', b'\xf1\x873Q0959655BH\xf1\x890703\xf1\x82\0163221003221002105755331052100', + b'\xf1\x873Q0959655CM\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x873Q0959655CN\xf1\x890720\xf1\x82\x0e3221003221002105755331052100', b'\xf1\x875QD959655 \xf1\x890388\xf1\x82\x111101000011110006110411111111119111', ], @@ -1047,6 +1166,7 @@ FW_VERSIONS = { b'\xf1\x870CW300050 \xf1\x891709', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x872Q0959655AJ\xf1\x890250\xf1\x82\x1211110411110411--04040404131111112H14', b'\xf1\x872Q0959655AM\xf1\x890351\xf1\x82\022111104111104112104040404111111112H14', ], (Ecu.eps, 0x712, None): [ @@ -1058,9 +1178,11 @@ FW_VERSIONS = { }, CAR.SKODA_SUPERB_MK3: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x8704L906026ET\xf1\x891343', b'\xf1\x8704L906026FP\xf1\x891196', b'\xf1\x8704L906026KB\xf1\x894071', b'\xf1\x8704L906026KD\xf1\x894798', + b'\xf1\x873G0906259 \xf1\x890004', b'\xf1\x873G0906259B \xf1\x890002', b'\xf1\x873G0906264A \xf1\x890002', ], @@ -1068,12 +1190,16 @@ FW_VERSIONS = { b'\xf1\x870CW300042H \xf1\x891601', b'\xf1\x870D9300011T \xf1\x894801', b'\xf1\x870D9300012 \xf1\x894940', + b'\xf1\x870D9300041H \xf1\x894905', b'\xf1\x870GC300043 \xf1\x892301', + b'\xf1\x870D9300043F \xf1\x895202', ], (Ecu.srs, 0x715, None): [ + b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\x12111200111121001121110012211292221111', b'\xf1\x875Q0959655AE\xf1\x890130\xf1\x82\022111200111121001121118112231292221111', b'\xf1\x875Q0959655AK\xf1\x890130\xf1\x82\022111200111121001121110012211292221111', b'\xf1\x875Q0959655BH\xf1\x890336\xf1\x82\02331310031313100313131013141319331413100', + b'\xf1\x875Q0959655CA\xf1\x890403\xf1\x82\x1331310031313100313151013141319331423100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x875Q0909143K \xf1\x892033\xf1\x820514UZ070203', diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index cbc54eadb8..1391d6570c 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -12,17 +12,16 @@ import cereal.messaging as messaging from common.conversions import Conversions as CV from panda import ALTERNATIVE_EXPERIENCE from system.swaglog import cloudlog -from system.version import is_tested_branch, get_short_branch +from system.version import is_release_branch, get_short_branch from selfdrive.boardd.boardd import can_list_to_can_capnp from selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can from selfdrive.controls.lib.lateral_planner import CAMERA_OFFSET -from selfdrive.controls.lib.drive_helpers import V_CRUISE_INITIAL, update_v_cruise, initialize_v_cruise -from selfdrive.controls.lib.drive_helpers import get_lag_adjusted_curvature -from selfdrive.controls.lib.latcontrol import LatControl +from selfdrive.controls.lib.drive_helpers import VCruiseHelper, get_lag_adjusted_curvature +from selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED from selfdrive.controls.lib.longcontrol import LongControl from selfdrive.controls.lib.latcontrol_pid import LatControlPID from selfdrive.controls.lib.latcontrol_indi import LatControlINDI -from selfdrive.controls.lib.latcontrol_angle import LatControlAngle +from selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from selfdrive.controls.lib.latcontrol_torque import LatControlTorque from selfdrive.controls.lib.events import Events, ET from selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert @@ -39,7 +38,7 @@ REPLAY = "REPLAY" in os.environ SIMULATION = "SIMULATION" in os.environ NOSENSOR = "NOSENSOR" in os.environ IGNORE_PROCESSES = {"uploader", "deleter", "loggerd", "logmessaged", "tombstoned", "statsd", - "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad", "laikad"} | \ + "logcatd", "proclogd", "clocksd", "updated", "timezoned", "manage_athenad"} | \ {k for k, v in managed_processes.items() if not v.enabled} ThermalStatus = log.DeviceState.ThermalStatus @@ -49,7 +48,6 @@ Desire = log.LateralPlan.Desire LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeDirection = log.LateralPlan.LaneChangeDirection EventName = car.CarEvent.EventName -ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type SafetyModel = car.CarParams.SafetyModel @@ -82,13 +80,13 @@ class Controls: self.log_sock = messaging.sub_sock('androidLog') - params = Params() + self.params = Params() self.sm = sm if self.sm is None: ignore = ['testJoystick'] if SIMULATION: ignore += ['driverCameraState', 'managerState'] - if params.get_bool('WideCameraOnly'): + if self.params.get_bool('WideCameraOnly'): ignore += ['roadCameraState'] self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', @@ -101,26 +99,24 @@ class Controls: get_one_can(self.can_sock) num_pandas = len(messaging.recv_one_retry(self.sm.sock['pandaStates']).pandaStates) - self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], num_pandas) + experimental_long_allowed = self.params.get_bool("ExperimentalLongitudinalEnabled") and not is_release_branch() + self.CI, self.CP = get_car(self.can_sock, self.pm.sock['sendcan'], experimental_long_allowed, num_pandas) else: self.CI, self.CP = CI, CI.CP - self.joystick_mode = params.get_bool("JoystickDebugMode") or (self.CP.notCar and sm is None) + self.joystick_mode = self.params.get_bool("JoystickDebugMode") or self.CP.notCar # set alternative experiences from parameters - self.disengage_on_accelerator = params.get_bool("DisengageOnAccelerator") + self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.CP.alternativeExperience = 0 if not self.disengage_on_accelerator: self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - if self.CP.dashcamOnly and params.get_bool("DashcamOverride"): - self.CP.dashcamOnly = False - # read params - self.is_metric = params.get_bool("IsMetric") - self.is_ldw_enabled = params.get_bool("IsLdwEnabled") - openpilot_enabled_toggle = params.get_bool("OpenpilotEnabledToggle") - passive = params.get_bool("Passive") or not openpilot_enabled_toggle + self.is_metric = self.params.get_bool("IsMetric") + self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled") + openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") + passive = self.params.get_bool("Passive") or not openpilot_enabled_toggle # detect sound card presence and ensure successful init sounds_available = HARDWARE.get_sound_card_online() @@ -134,20 +130,17 @@ class Controls: safety_config.safetyModel = car.CarParams.SafetyModel.noOutput self.CP.safetyConfigs = [safety_config] - if is_tested_branch(): - self.CP.experimentalLongitudinalAvailable = False - # Write CarParams for radard cp_bytes = self.CP.to_bytes() - params.put("CarParams", cp_bytes) + self.params.put("CarParams", cp_bytes) put_nonblocking("CarParamsCache", cp_bytes) put_nonblocking("CarParamsPersistent", cp_bytes) # cleanup old params - if not self.CP.experimentalLongitudinalAvailable: - params.remove("ExperimentalLongitudinalEnabled") + if not self.CP.experimentalLongitudinalAvailable or is_release_branch(): + self.params.remove("ExperimentalLongitudinalEnabled") if not self.CP.openpilotLongitudinalControl: - params.remove("EndToEndLong") + self.params.remove("ExperimentalMode") self.CC = car.CarControl.new_message() self.CS_prev = car.CarState.new_message() @@ -171,28 +164,28 @@ class Controls: self.state = State.disabled self.enabled = False self.active = False - self.can_rcv_timeout = False self.soft_disable_timer = 0 - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL - self.v_cruise_kph_last = 0 self.mismatch_counter = 0 self.cruise_mismatch_counter = 0 - self.can_rcv_timeout_counter = 0 + self.can_rcv_timeout_counter = 0 # conseuctive timeout count + self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count self.last_blinker_frame = 0 + self.last_steering_pressed_frame = 0 self.distance_traveled = 0 self.last_functional_fan_frame = 0 self.events_prev = [] self.current_alert_types = [ET.PERMANENT] self.logged_comm_issue = None - self.button_timers = {ButtonEvent.Type.decelCruise: 0, ButtonEvent.Type.accelCruise: 0} self.last_actuators = car.CarControl.Actuators.new_message() self.steer_limited = False self.desired_curvature = 0.0 self.desired_curvature_rate = 0.0 + self.experimental_mode = False + self.v_cruise_helper = VCruiseHelper(self.CP) # TODO: no longer necessary, aside from process replay self.sm['liveParameters'].valid = True + self.can_log_mono_time = 0 self.startup_event = get_startup_event(car_recognized, controller_available, len(self.CP.carFw) > 0) @@ -219,7 +212,7 @@ class Controls: controls_state = Params().get("ReplayControlsState") if controls_state is not None: controls_state = log.ControlsState.from_bytes(controls_state) - self.v_cruise_kph = controls_state.vCruise + self.v_cruise_helper.v_cruise_kph = controls_state.vCruise if any(ps.controlsAllowed for ps in self.sm['pandaStates']): self.state = State.enabled @@ -245,17 +238,20 @@ class Controls: # Block resume if cruise never previously enabled resume_pressed = any(be.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for be in CS.buttonEvents) - if not self.CP.pcmCruise and self.v_cruise_kph == V_CRUISE_INITIAL and resume_pressed: + if not self.CP.pcmCruise and not self.v_cruise_helper.v_cruise_initialized and resume_pressed: self.events.add(EventName.resumeBlocked) # Disable on rising edge of accelerator or brake. Also disable on brake when speed > 0 if (CS.gasPressed and not self.CS_prev.gasPressed and self.disengage_on_accelerator) or \ - (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)): + (CS.brakePressed and (not self.CS_prev.brakePressed or not CS.standstill)) or \ + (CS.regenBraking and (not self.CS_prev.regenBraking or not CS.standstill)): self.events.add(EventName.pedalPressed) + if CS.brakePressed and CS.standstill: + self.events.add(EventName.preEnableStandstill) + if CS.gasPressed: - self.events.add(EventName.pedalPressedPreEnable if self.disengage_on_accelerator else - EventName.gasPressedOverride) + self.events.add(EventName.gasPressedOverride) if not self.CP.notCar: self.events.add_from_msg(self.sm['driverMonitoringState'].events) @@ -280,9 +276,10 @@ class Controls: # self.events.add(EventName.highCpuUsage) # Alert if fan isn't spinning for 5 seconds - if self.sm['peripheralState'].pandaType == PandaType.dos: - if self.sm['peripheralState'].fanSpeedRpm == 0 and self.sm['deviceState'].fanSpeedPercentDesired > 50: - if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 5.0: + if self.sm['peripheralState'].pandaType != log.PandaState.PandaType.unknown: + if self.sm['peripheralState'].fanSpeedRpm < 500 and self.sm['deviceState'].fanSpeedPercentDesired > 50: + # allow enough time for the fan controller in the panda to recover from stalls + if (self.sm.frame - self.last_functional_fan_frame) * DT_CTRL > 15.0: self.events.add(EventName.fanMalfunction) else: self.last_functional_fan_frame = self.sm.frame @@ -351,9 +348,10 @@ class Controls: self.events.add(EventName.canError) # generic catch-all. ideally, a more specific event should be added above instead + can_rcv_timeout = self.can_rcv_timeout_counter >= 5 has_disable_events = self.events.any(ET.NO_ENTRY) and (self.events.any(ET.SOFT_DISABLE) or self.events.any(ET.IMMEDIATE_DISABLE)) no_system_errors = (not has_disable_events) or (len(self.events) == num_events) - if (not self.sm.all_checks() or self.can_rcv_timeout) and no_system_errors: + if (not self.sm.all_checks() or can_rcv_timeout) and no_system_errors: if not self.sm.all_alive(): self.events.add(EventName.commIssue) elif not self.sm.all_freq_ok(): @@ -365,7 +363,7 @@ class Controls: 'invalid': [s for s, valid in self.sm.valid.items() if not valid], 'not_alive': [s for s, alive in self.sm.alive.items() if not alive], 'not_freq_ok': [s for s, freq_ok in self.sm.freq_ok.items() if not freq_ok], - 'can_rcv_timeout': self.can_rcv_timeout, + 'can_rcv_timeout': can_rcv_timeout, } if logs != self.logged_comm_issue: cloudlog.event("commIssue", error=True, **logs) @@ -422,22 +420,14 @@ class Controls: if self.sm['liveLocationKalman'].excessiveResets: self.events.add(EventName.localizerMalfunction) - # Only allow engagement with brake pressed when stopped behind another stopped car - speeds = self.sm['longitudinalPlan'].speeds - if len(speeds) > 1: - v_future = speeds[-1] - else: - v_future = 100.0 - if CS.brakePressed and v_future >= self.CP.vEgoStarting \ - and self.CP.openpilotLongitudinalControl and CS.vEgo < 0.3: - self.events.add(EventName.noTarget) - def data_sample(self): """Receive data from sockets and update carState""" # Update carState from CAN can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) CS = self.CI.update(self.CC, can_strs) + if len(can_strs) and REPLAY: + self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime self.sm.update(0) @@ -455,9 +445,9 @@ class Controls: # Check for CAN timeout if not can_strs: self.can_rcv_timeout_counter += 1 - self.can_rcv_timeout = True + self.can_rcv_cum_timeout_counter += 1 else: - self.can_rcv_timeout = False + self.can_rcv_timeout_counter = 0 # When the panda and controlsd do not agree on controls_allowed # we want to disengage openpilot. However the status from the panda goes through @@ -478,20 +468,7 @@ class Controls: def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" - self.v_cruise_kph_last = self.v_cruise_kph - - if CS.cruiseState.available: - # if stock cruise is completely disabled, then we can use our own set speed logic - if not self.CP.pcmCruise: - self.v_cruise_kph = update_v_cruise(self.v_cruise_kph, CS.vEgo, CS.gasPressed, CS.buttonEvents, - self.button_timers, self.enabled, self.is_metric) - self.v_cruise_cluster_kph = self.v_cruise_kph - else: - self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH - self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH - else: - self.v_cruise_kph = V_CRUISE_INITIAL - self.v_cruise_cluster_kph = V_CRUISE_INITIAL + self.v_cruise_helper.update_v_cruise(CS, self.enabled, self.is_metric) # decrement the soft disable timer at every step, as it's reset on # entrance in SOFT_DISABLING state @@ -536,10 +513,7 @@ class Controls: # PRE ENABLING elif self.state == State.preEnabled: - if self.events.any(ET.NO_ENTRY): - self.state = State.disabled - self.current_alert_types.append(ET.NO_ENTRY) - elif not self.events.any(ET.PRE_ENABLE): + if not self.events.any(ET.PRE_ENABLE): self.state = State.enabled else: self.current_alert_types.append(ET.PRE_ENABLE) @@ -569,9 +543,7 @@ class Controls: else: self.state = State.enabled self.current_alert_types.append(ET.ENABLE) - if not self.CP.pcmCruise: - self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last) - self.v_cruise_cluster_kph = self.v_cruise_kph + self.v_cruise_helper.initialize_v_cruise(CS, self.experimental_mode) # Check if openpilot is engaged and actuators are enabled self.enabled = self.state in ENABLED_STATES @@ -583,9 +555,9 @@ class Controls: """Given the state, this function returns a CarControl packet""" # Update VehicleModel - params = self.sm['liveParameters'] - x = max(params.stiffnessFactor, 0.1) - sr = max(params.steerRatio, 0.1) + lp = self.sm['liveParameters'] + x = max(lp.stiffnessFactor, 0.1) + sr = max(lp.steerRatio, 0.1) self.VM.update_params(x, sr) # Update Torque Params @@ -599,14 +571,21 @@ class Controls: CC = car.CarControl.new_message() CC.enabled = self.enabled + # Check which actuators can be enabled + standstill = CS.vEgo <= max(self.CP.minSteerSpeed, MIN_LATERAL_CONTROL_SPEED) or CS.standstill CC.latActive = self.active and not CS.steerFaultTemporary and not CS.steerFaultPermanent and \ - CS.vEgo > self.CP.minSteerSpeed and not CS.standstill - CC.longActive = self.active and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl + (not standstill or self.joystick_mode) + CC.longActive = self.enabled and not self.events.any(ET.OVERRIDE_LONGITUDINAL) and self.CP.openpilotLongitudinalControl actuators = CC.actuators actuators.longControlState = self.LoC.long_control_state + # Enable blinkers while lane changing + if self.sm['lateralPlan'].laneChangeState != LaneChangeState.off: + CC.leftBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.left + CC.rightBlinker = self.sm['lateralPlan'].laneChangeDirection == LaneChangeDirection.right + if CS.leftBlinker or CS.rightBlinker: self.last_blinker_frame = self.sm.frame @@ -619,7 +598,7 @@ class Controls: if not self.joystick_mode: # accel PID loop - pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_kph * CV.KPH_TO_MS) + pid_accel_limits = self.CI.get_pid_accel_limits(self.CP, CS.vEgo, self.v_cruise_helper.v_cruise_kph * CV.KPH_TO_MS) t_since_plan = (self.sm.frame - self.sm.rcv_frame['longitudinalPlan']) * DT_CTRL actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) @@ -628,9 +607,10 @@ class Controls: lat_plan.psis, lat_plan.curvatures, lat_plan.curvatureRates) - actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, params, + actuators.steer, actuators.steeringAngleDeg, lac_log = self.LaC.update(CC.latActive, CS, self.VM, lp, self.last_actuators, self.steer_limited, self.desired_curvature, self.desired_curvature_rate, self.sm['liveLocationKalman']) + actuators.curvature = self.desired_curvature else: lac_log = log.ControlsState.LateralDebugState.new_message() if self.sm.rcv_frame['testJoystick'] > 0: @@ -647,29 +627,34 @@ class Controls: lac_log.output = actuators.steer lac_log.saturated = abs(actuators.steer) >= 0.9 + if CS.steeringPressed: + self.last_steering_pressed_frame = self.sm.frame + recent_steer_pressed = (self.sm.frame - self.last_steering_pressed_frame)*DT_CTRL < 2.0 + # Send a "steering required alert" if saturation count has reached the limit - if lac_log.active and not CS.steeringPressed and self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: - undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 - turning = abs(lac_log.desiredLateralAccel) > 1.0 - good_speed = CS.vEgo > 5 - max_torque = abs(self.last_actuators.steer) > 0.99 - if undershooting and turning and good_speed and max_torque: - self.events.add(EventName.steerSaturated) - elif lac_log.active and not CS.steeringPressed and lac_log.saturated: - dpath_points = lat_plan.dPathPoints - if len(dpath_points): - # Check if we deviated from the path - # TODO use desired vs actual curvature - if self.CP.steerControlType == car.CarParams.SteerControlType.angle: - steering_value = actuators.steeringAngleDeg - else: - steering_value = actuators.steer + if lac_log.active and not recent_steer_pressed: + if self.CP.lateralTuning.which() == 'torque' and not self.joystick_mode: + undershooting = abs(lac_log.desiredLateralAccel) / abs(1e-3 + lac_log.actualLateralAccel) > 1.2 + turning = abs(lac_log.desiredLateralAccel) > 1.0 + good_speed = CS.vEgo > 5 + max_torque = abs(self.last_actuators.steer) > 0.99 + if undershooting and turning and good_speed and max_torque: + lac_log.active and self.events.add(EventName.steerSaturated) + elif lac_log.saturated: + dpath_points = lat_plan.dPathPoints + if len(dpath_points): + # Check if we deviated from the path + # TODO use desired vs actual curvature + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + steering_value = actuators.steeringAngleDeg + else: + steering_value = actuators.steer - left_deviation = steering_value > 0 and dpath_points[0] < -0.20 - right_deviation = steering_value < 0 and dpath_points[0] > 0.20 + left_deviation = steering_value > 0 and dpath_points[0] < -0.20 + right_deviation = steering_value < 0 and dpath_points[0] > 0.20 - if left_deviation or right_deviation: - self.events.add(EventName.steerSaturated) + if left_deviation or right_deviation: + self.events.add(EventName.steerSaturated) # Ensure no NaNs/Infs for p in ACTUATOR_FIELDS: @@ -683,16 +668,6 @@ class Controls: return CC, lac_log - def update_button_timers(self, buttonEvents): - # increment timer for buttons still pressed - for k in self.button_timers: - if self.button_timers[k] > 0: - self.button_timers[k] += 1 - - for b in buttonEvents: - if b.type.raw in self.button_timers: - self.button_timers[b.type.raw] = 1 if b.pressed else 0 - def publish_logs(self, CS, start_time, CC, lac_log): """Send actuators and hud commands to the car, send controlsstate and MPC logging""" @@ -715,7 +690,7 @@ class Controls: CC.cruiseControl.resume = self.enabled and CS.cruiseState.standstill and speeds[-1] > 0.1 hudControl = CC.hudControl - hudControl.setSpeed = float(self.v_cruise_cluster_kph * CV.KPH_TO_MS) + hudControl.setSpeed = float(self.v_cruise_helper.v_cruise_cluster_kph * CV.KPH_TO_MS) hudControl.speedVisible = self.enabled hudControl.lanesVisible = self.enabled hudControl.leadVisible = self.sm['longitudinalPlan'].hasLead @@ -759,19 +734,24 @@ class Controls: if not self.read_only and self.initialized: # send car controls over can - self.last_actuators, can_sends = self.CI.apply(CC) + now_nanos = self.can_log_mono_time if REPLAY else int(sec_since_boot() * 1e9) + self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) CC.actuatorsOutput = self.last_actuators - self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 + if self.CP.steerControlType == car.CarParams.SteerControlType.angle: + self.steer_limited = abs(CC.actuators.steeringAngleDeg - CC.actuatorsOutput.steeringAngleDeg) > \ + STEER_ANGLE_SATURATION_THRESHOLD + else: + self.steer_limited = abs(CC.actuators.steer - CC.actuatorsOutput.steer) > 1e-2 force_decel = (self.sm['driverMonitoringState'].awarenessStatus < 0.) or \ (self.state == State.softDisabling) # Curvature & Steering angle - params = self.sm['liveParameters'] + lp = self.sm['liveParameters'] - steer_angle_without_offset = math.radians(CS.steeringAngleDeg - params.angleOffsetDeg) - curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, params.roll) + steer_angle_without_offset = math.radians(CS.steeringAngleDeg - lp.angleOffsetDeg) + curvature = -self.VM.calc_curvature(steer_angle_without_offset, CS.vEgo, lp.roll) # controlsState dat = messaging.new_message('controlsState') @@ -786,7 +766,6 @@ class Controls: controlsState.alertType = current_alert.alert_type controlsState.alertSound = current_alert.audible_alert - controlsState.canMonoTimes = list(CS.canMonoTimes) controlsState.longitudinalPlanMonoTime = self.sm.logMonoTime['longitudinalPlan'] controlsState.lateralPlanMonoTime = self.sm.logMonoTime['lateralPlan'] controlsState.enabled = self.enabled @@ -798,15 +777,16 @@ class Controls: controlsState.engageable = not self.events.any(ET.NO_ENTRY) controlsState.longControlState = self.LoC.long_control_state controlsState.vPid = float(self.LoC.v_pid) - controlsState.vCruise = float(self.v_cruise_kph) - controlsState.vCruiseCluster = float(self.v_cruise_cluster_kph) + controlsState.vCruise = float(self.v_cruise_helper.v_cruise_kph) + controlsState.vCruiseCluster = float(self.v_cruise_helper.v_cruise_cluster_kph) controlsState.upAccelCmd = float(self.LoC.pid.p) controlsState.uiAccelCmd = float(self.LoC.pid.i) controlsState.ufAccelCmd = float(self.LoC.pid.f) controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.can_rcv_timeout_counter + controlsState.canErrorCounter = self.can_rcv_cum_timeout_counter + controlsState.experimentalMode = self.experimental_mode lat_tuning = self.CP.lateralTuning.which() if self.joystick_mode: @@ -856,6 +836,9 @@ class Controls: start_time = sec_since_boot() self.prof.checkpoint("Ratekeeper", ignore=True) + self.is_metric = self.params.get_bool("IsMetric") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + # Sample data from sockets and get a carState CS = self.data_sample() cloudlog.timestamp("Data sampled") @@ -878,7 +861,6 @@ class Controls: self.publish_logs(CS, start_time, CC, lac_log) self.prof.checkpoint("Sent") - self.update_button_timers(CS.buttonEvents) self.CS_prev = CS def controlsd_thread(self): @@ -887,6 +869,7 @@ class Controls: self.rk.monitor_time() self.prof.display() + def main(sm=None, pm=None, logcan=None): controls = Controls(sm, pm, logcan) controls.controlsd_thread() diff --git a/selfdrive/controls/lib/desire_helper.py b/selfdrive/controls/lib/desire_helper.py index d41d6780e3..4790b8f0eb 100644 --- a/selfdrive/controls/lib/desire_helper.py +++ b/selfdrive/controls/lib/desire_helper.py @@ -5,7 +5,7 @@ from common.realtime import DT_MDL LaneChangeState = log.LateralPlan.LaneChangeState LaneChangeDirection = log.LateralPlan.LaneChangeDirection -LANE_CHANGE_SPEED_MIN = 15 * CV.MPH_TO_MS +LANE_CHANGE_SPEED_MIN = 20 * CV.MPH_TO_MS LANE_CHANGE_TIME_MAX = 10. DESIRES = { diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index f81cd0c40c..05c3897335 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -8,19 +8,22 @@ from selfdrive.modeld.constants import T_IDXS # WARNING: this value was determined based on the model's training distribution, # model predictions above this speed can be unpredictable -V_CRUISE_MAX = 145 # kph -V_CRUISE_MIN = 8 # kph -V_CRUISE_ENABLE_MIN = 40 # kph -V_CRUISE_INITIAL = 255 # kph +# V_CRUISE's are in kph +V_CRUISE_MIN = 8 +V_CRUISE_MAX = 145 +V_CRUISE_UNSET = 255 +V_CRUISE_INITIAL = 40 +V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105 +IMPERIAL_INCREMENT = 1.6 # should be CV.MPH_TO_KPH, but this causes rounding errors -LAT_MPC_N = 16 -LON_MPC_N = 32 +MIN_SPEED = 1.0 CONTROL_N = 17 CAR_ROTATION_RADIUS = 0.0 # EU guidelines MAX_LATERAL_JERK = 5.0 +ButtonEvent = car.CarState.ButtonEvent ButtonType = car.CarState.ButtonEvent.Type CRUISE_LONG_PRESS = 50 CRUISE_NEAREST_FUNC = { @@ -33,6 +36,111 @@ CRUISE_INTERVAL_SIGN = { } +class VCruiseHelper: + def __init__(self, CP): + self.CP = CP + self.v_cruise_kph = V_CRUISE_UNSET + self.v_cruise_cluster_kph = V_CRUISE_UNSET + self.v_cruise_kph_last = 0 + self.button_timers = {ButtonType.decelCruise: 0, ButtonType.accelCruise: 0} + self.button_change_states = {btn: {"standstill": False, "enabled": False} for btn in self.button_timers} + + @property + def v_cruise_initialized(self): + return self.v_cruise_kph != V_CRUISE_UNSET + + def update_v_cruise(self, CS, enabled, is_metric): + self.v_cruise_kph_last = self.v_cruise_kph + + if CS.cruiseState.available: + if not self.CP.pcmCruise: + # if stock cruise is completely disabled, then we can use our own set speed logic + self._update_v_cruise_non_pcm(CS, enabled, is_metric) + self.v_cruise_cluster_kph = self.v_cruise_kph + self.update_button_timers(CS, enabled) + else: + self.v_cruise_kph = CS.cruiseState.speed * CV.MS_TO_KPH + self.v_cruise_cluster_kph = CS.cruiseState.speedCluster * CV.MS_TO_KPH + else: + self.v_cruise_kph = V_CRUISE_UNSET + self.v_cruise_cluster_kph = V_CRUISE_UNSET + + def _update_v_cruise_non_pcm(self, CS, enabled, is_metric): + # handle button presses. TODO: this should be in state_control, but a decelCruise press + # would have the effect of both enabling and changing speed is checked after the state transition + if not enabled: + return + + long_press = False + button_type = None + + v_cruise_delta = 1. if is_metric else IMPERIAL_INCREMENT + + for b in CS.buttonEvents: + if b.type.raw in self.button_timers and not b.pressed: + if self.button_timers[b.type.raw] > CRUISE_LONG_PRESS: + return # end long press + button_type = b.type.raw + break + else: + for k in self.button_timers.keys(): + if self.button_timers[k] and self.button_timers[k] % CRUISE_LONG_PRESS == 0: + button_type = k + long_press = True + break + + if button_type is None: + return + + # Don't adjust speed when pressing resume to exit standstill + cruise_standstill = self.button_change_states[button_type]["standstill"] or CS.cruiseState.standstill + if button_type == ButtonType.accelCruise and cruise_standstill: + return + + # Don't adjust speed if we've enabled since the button was depressed (some ports enable on rising edge) + if not self.button_change_states[button_type]["enabled"]: + return + + v_cruise_delta = v_cruise_delta * (5 if long_press else 1) + if long_press and self.v_cruise_kph % v_cruise_delta != 0: # partial interval + self.v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](self.v_cruise_kph / v_cruise_delta) * v_cruise_delta + else: + self.v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] + + # If set is pressed while overriding, clip cruise speed to minimum of vEgo + if CS.gasPressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): + self.v_cruise_kph = max(self.v_cruise_kph, CS.vEgo * CV.MS_TO_KPH) + + self.v_cruise_kph = clip(round(self.v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) + + def update_button_timers(self, CS, enabled): + # increment timer for buttons still pressed + for k in self.button_timers: + if self.button_timers[k] > 0: + self.button_timers[k] += 1 + + for b in CS.buttonEvents: + if b.type.raw in self.button_timers: + # Start/end timer and store current state on change of button pressed + self.button_timers[b.type.raw] = 1 if b.pressed else 0 + self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled} + + def initialize_v_cruise(self, CS, experimental_mode: bool) -> None: + # initializing is handled by the PCM + if self.CP.pcmCruise: + return + + initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode else V_CRUISE_INITIAL + + # 250kph or above probably means we never had a set speed + if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_kph_last < 250: + self.v_cruise_kph = self.v_cruise_kph_last + else: + self.v_cruise_kph = int(round(clip(CS.vEgo * CV.MS_TO_KPH, initial, V_CRUISE_MAX))) + + self.v_cruise_cluster_kph = self.v_cruise_kph + + def apply_deadzone(error, deadzone): if error > deadzone: error -= deadzone @@ -43,66 +151,22 @@ def apply_deadzone(error, deadzone): return error +def apply_center_deadzone(error, deadzone): + if (error > - deadzone) and (error < deadzone): + error = 0. + return error + + def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) -def update_v_cruise(v_cruise_kph, v_ego, gas_pressed, buttonEvents, button_timers, enabled, metric): - # handle button presses. TODO: this should be in state_control, but a decelCruise press - # would have the effect of both enabling and changing speed is checked after the state transition - if not enabled: - return v_cruise_kph - - long_press = False - button_type = None - - # should be CV.MPH_TO_KPH, but this causes rounding errors - v_cruise_delta = 1. if metric else 1.6 - - for b in buttonEvents: - if b.type.raw in button_timers and not b.pressed: - if button_timers[b.type.raw] > CRUISE_LONG_PRESS: - return v_cruise_kph # end long press - button_type = b.type.raw - break - else: - for k in button_timers.keys(): - if button_timers[k] and button_timers[k] % CRUISE_LONG_PRESS == 0: - button_type = k - long_press = True - break - - if button_type: - v_cruise_delta = v_cruise_delta * (5 if long_press else 1) - if long_press and v_cruise_kph % v_cruise_delta != 0: # partial interval - v_cruise_kph = CRUISE_NEAREST_FUNC[button_type](v_cruise_kph / v_cruise_delta) * v_cruise_delta - else: - v_cruise_kph += v_cruise_delta * CRUISE_INTERVAL_SIGN[button_type] - - # If set is pressed while overriding, clip cruise speed to minimum of vEgo - if gas_pressed and button_type in (ButtonType.decelCruise, ButtonType.setCruise): - v_cruise_kph = max(v_cruise_kph, v_ego * CV.MS_TO_KPH) - - v_cruise_kph = clip(round(v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX) - - return v_cruise_kph - - -def initialize_v_cruise(v_ego, buttonEvents, v_cruise_last): - for b in buttonEvents: - # 250kph or above probably means we never had a set speed - if b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) and v_cruise_last < 250: - return v_cruise_last - - return int(round(clip(v_ego * CV.MS_TO_KPH, V_CRUISE_ENABLE_MIN, V_CRUISE_MAX))) - - def get_lag_adjusted_curvature(CP, v_ego, psis, curvatures, curvature_rates): if len(psis) != CONTROL_N: psis = [0.0]*CONTROL_N curvatures = [0.0]*CONTROL_N curvature_rates = [0.0]*CONTROL_N - v_ego = max(v_ego, 0.1) + v_ego = max(MIN_SPEED, v_ego) # TODO this needs more thought, use .2s extra for now to estimate other delays delay = CP.steerActuatorDelay + .2 diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 44426620e9..e4ddfb5326 100644 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -230,7 +230,7 @@ def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM return StartupAlert("WARNING: This branch is not tested", branch, alert_status=AlertStatus.userPrompt) def below_engage_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - return NoEntryAlert(f"Speed Below {get_display_speed(CP.minEnableSpeed, metric)}") + return NoEntryAlert(f"Drive above {get_display_speed(CP.minEnableSpeed, metric)} to engage") def below_steer_speed_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: @@ -250,10 +250,9 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag def no_gps_alert(CP: car.CarParams, CS: car.CarState, 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", - "Hardware malfunctioning if sky is visible" if gps_integrated else "Check GPS antenna placement", + "Hardware malfunctioning if sky is visible", AlertStatus.normal, AlertSize.mid, Priority.LOWER, VisualAlert.none, AudibleAlert.none, .2, creation_delay=300.) @@ -293,7 +292,7 @@ def calibration_invalid_alert(CP: car.CarParams, CS: car.CarState, sm: messaging rpy = sm['liveCalibration'].rpyCalib yaw = math.degrees(rpy[2] if len(rpy) == 3 else math.nan) pitch = math.degrees(rpy[1] if len(rpy) == 3 else math.nan) - angles = f"Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°" + angles = f"Remount Device (Pitch: {pitch:.1f}°, Yaw: {yaw:.1f}°)" return NormalPermanentAlert("Calibration Invalid", angles) @@ -318,9 +317,9 @@ def modeld_lagging_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubM def wrong_car_mode_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - text = "Cruise Mode Disabled" + text = "Enable Adaptive Cruise to Engage" if CP.carName == "honda": - text = "Main Switch Off" + text = "Enable Main Switch to Engage" return NoEntryAlert(text) @@ -501,9 +500,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { EventName.resumeRequired: { ET.WARNING: Alert( - "STOPPED", - "Press Resume to Go", - AlertStatus.userPrompt, AlertSize.mid, + "Press Resume to Exit Standstill", + "", + AlertStatus.userPrompt, AlertSize.small, Priority.LOW, VisualAlert.none, AudibleAlert.none, .2), }, @@ -597,6 +596,7 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { EventName.buttonCancel: { ET.USER_DISABLE: EngagementAlert(AudibleAlert.disengage), + ET.NO_ENTRY: NoEntryAlert("Cancel Pressed"), }, EventName.brakeHold: { @@ -615,9 +615,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { visual_alert=VisualAlert.brakePressed), }, - EventName.pedalPressedPreEnable: { + EventName.preEnableStandstill: { ET.PRE_ENABLE: Alert( - "Release Pedal to Engage", + "Release Brake to Engage", "", AlertStatus.normal, AlertSize.small, Priority.LOWEST, VisualAlert.none, AudibleAlert.none, .1, creation_delay=1.), @@ -729,8 +729,8 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { }, EventName.espDisabled: { - ET.SOFT_DISABLE: soft_disable_alert("ESP Off"), - ET.NO_ENTRY: NoEntryAlert("ESP Off"), + ET.SOFT_DISABLE: soft_disable_alert("Electronic Stability Control Disabled"), + ET.NO_ENTRY: NoEntryAlert("Electronic Stability Control Disabled"), }, EventName.lowBattery: { @@ -806,9 +806,9 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { }, EventName.accFaulted: { - ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Faulted"), - ET.PERMANENT: NormalPermanentAlert("Cruise Faulted", ""), - ET.NO_ENTRY: NoEntryAlert("Cruise Faulted"), + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"), + ET.PERMANENT: NormalPermanentAlert("Cruise Fault: Restart the car to engage"), + ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"), }, EventName.accFaultedTemp: { @@ -916,15 +916,6 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { ET.NO_ENTRY: NoEntryAlert("Harness Relay Malfunction"), }, - EventName.noTarget: { - ET.IMMEDIATE_DISABLE: Alert( - "openpilot Canceled", - "No close lead car", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.disengage, 3.), - ET.NO_ENTRY: NoEntryAlert("No Close Lead Car"), - }, - EventName.speedTooLow: { ET.IMMEDIATE_DISABLE: Alert( "openpilot Canceled", diff --git a/selfdrive/controls/lib/latcontrol.py b/selfdrive/controls/lib/latcontrol.py index 78b59fda59..d38959c560 100644 --- a/selfdrive/controls/lib/latcontrol.py +++ b/selfdrive/controls/lib/latcontrol.py @@ -3,7 +3,7 @@ from abc import abstractmethod, ABC from common.numpy_fast import clip from common.realtime import DT_CTRL -MIN_STEER_SPEED = 0.3 +MIN_LATERAL_CONTROL_SPEED = 0.3 # m/s class LatControl(ABC): @@ -11,6 +11,7 @@ class LatControl(ABC): self.sat_count_rate = 1.0 * DT_CTRL self.sat_limit = CP.steerLimitTimer self.sat_count = 0. + self.sat_check_min_speed = 10. # we define the steer torque scale as [-1.0...1.0] self.steer_max = 1.0 @@ -23,7 +24,7 @@ class LatControl(ABC): self.sat_count = 0. def _check_saturation(self, saturated, CS, steer_limited): - if saturated and CS.vEgo > 10. and not steer_limited and not CS.steeringPressed: + if saturated and CS.vEgo > self.sat_check_min_speed and not steer_limited and not CS.steeringPressed: self.sat_count += self.sat_count_rate else: self.sat_count -= self.sat_count_rate diff --git a/selfdrive/controls/lib/latcontrol_angle.py b/selfdrive/controls/lib/latcontrol_angle.py index 0e5be4a977..9ed140d38e 100644 --- a/selfdrive/controls/lib/latcontrol_angle.py +++ b/selfdrive/controls/lib/latcontrol_angle.py @@ -1,16 +1,20 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl STEER_ANGLE_SATURATION_THRESHOLD = 2.5 # Degrees class LatControlAngle(LatControl): + def __init__(self, CP, CI): + super().__init__(CP, CI) + self.sat_check_min_speed = 5. + def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): angle_log = log.ControlsState.LateralAngleState.new_message() - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: angle_log.active = False angle_steers_des = float(CS.steeringAngleDeg) else: @@ -19,7 +23,7 @@ class LatControlAngle(LatControl): angle_steers_des += params.angleOffsetDeg angle_control_saturated = abs(angle_steers_des - CS.steeringAngleDeg) > STEER_ANGLE_SATURATION_THRESHOLD - angle_log.saturated = self._check_saturation(angle_control_saturated, CS, steer_limited) + angle_log.saturated = self._check_saturation(angle_control_saturated, CS, False) angle_log.steeringAngleDeg = float(CS.steeringAngleDeg) 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 2bc3cef76b..dca82c6729 100644 --- a/selfdrive/controls/lib/latcontrol_indi.py +++ b/selfdrive/controls/lib/latcontrol_indi.py @@ -5,7 +5,7 @@ from cereal import log from common.filter_simple import FirstOrderFilter from common.numpy_fast import clip, interp from common.realtime import DT_CTRL -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl class LatControlINDI(LatControl): @@ -82,7 +82,7 @@ class LatControlINDI(LatControl): rate_des = VM.get_steer_from_curvature(-desired_curvature_rate, CS.vEgo, 0) indi_log.steeringRateDesiredDeg = math.degrees(rate_des) - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: indi_log.active = False self.steer_filter.x = 0.0 output_steer = 0 diff --git a/selfdrive/controls/lib/latcontrol_pid.py b/selfdrive/controls/lib/latcontrol_pid.py index 6bd678073e..6696d2e304 100644 --- a/selfdrive/controls/lib/latcontrol_pid.py +++ b/selfdrive/controls/lib/latcontrol_pid.py @@ -1,7 +1,7 @@ import math from cereal import log -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl from selfdrive.controls.lib.pid import PIDController @@ -28,7 +28,7 @@ class LatControlPID(LatControl): pid_log.steeringAngleDesiredDeg = angle_steers_des pid_log.angleError = error - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: output_steer = 0.0 pid_log.active = False self.pid.reset() diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 51676086ba..2f56094379 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -2,7 +2,7 @@ import math from cereal import log from common.numpy_fast import interp -from selfdrive.controls.lib.latcontrol import LatControl, MIN_STEER_SPEED +from selfdrive.controls.lib.latcontrol import LatControl from selfdrive.controls.lib.pid import PIDController from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY @@ -17,7 +17,8 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY # friction in the steering wheel that needs to be overcome to # move it at all, this is compensated for too. -LOW_SPEED_FACTOR = 200 +LOW_SPEED_X = [0, 10, 20, 30] +LOW_SPEED_Y = [15, 13, 10, 5] class LatControlTorque(LatControl): @@ -38,7 +39,7 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, last_actuators, steer_limited, desired_curvature, desired_curvature_rate, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() - if CS.vEgo < MIN_STEER_SPEED or not active: + if not active: output_torque = 0.0 pid_log.active = False else: @@ -57,8 +58,9 @@ class LatControlTorque(LatControl): actual_lateral_accel = actual_curvature * CS.vEgo ** 2 lateral_accel_deadzone = curvature_deadzone * CS.vEgo ** 2 - setpoint = desired_lateral_accel + LOW_SPEED_FACTOR * desired_curvature - measurement = actual_lateral_accel + LOW_SPEED_FACTOR * actual_curvature + low_speed_factor = interp(CS.vEgo, LOW_SPEED_X, LOW_SPEED_Y)**2 + setpoint = desired_lateral_accel + low_speed_factor * desired_curvature + measurement = actual_lateral_accel + low_speed_factor * actual_curvature error = setpoint - measurement gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY pid_log.error = self.torque_from_lateral_accel(error, self.torque_params, error, diff --git a/selfdrive/controls/lib/lateral_mpc_lib/SConscript b/selfdrive/controls/lib/lateral_mpc_lib/SConscript index df1e2a2a1a..745ed99d10 100644 --- a/selfdrive/controls/lib/lateral_mpc_lib/SConscript +++ b/selfdrive/controls/lib/lateral_mpc_lib/SConscript @@ -44,9 +44,10 @@ generated_files = [ ] + build_files acados_dir = '#third_party/acados' -acados_templates_dir = '#pyextra/acados_template/c_templates_tera' +acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['lat_mpc.py', + '#/selfdrive/modeld/constants.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', f'{acados_dir}/x86_64/lib/libacados.so', f'{acados_dir}/larch64/lib/libacados.so', @@ -70,8 +71,8 @@ lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_lat", LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) # generate cython stuff -acados_ocp_solver_pyx = File("#pyextra/acados_template/acados_ocp_solver_pyx.pyx") -acados_ocp_solver_common = File("#pyextra/acados_template/acados_solver_common.pxd") +acados_ocp_solver_pyx = File("#third_party/acados/acados_template/acados_ocp_solver_pyx.pyx") +acados_ocp_solver_common = File("#third_party/acados/acados_template/acados_solver_common.pxd") libacados_ocp_solver_pxd = File(f'{gen}/acados_solver.pxd') libacados_ocp_solver_c = File(f'{gen}/acados_ocp_solver_pyx.c') diff --git a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py index 9607532ace..ca7b991e69 100755 --- a/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py +++ b/selfdrive/controls/lib/lateral_mpc_lib/lat_mpc.py @@ -3,12 +3,12 @@ import os import numpy as np from casadi import SX, vertcat, sin, cos - from common.realtime import sec_since_boot +# WARNING: imports outside of constants will not trigger a rebuild from selfdrive.modeld.constants import T_IDXS if __name__ == '__main__': # generating code - from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: from selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error @@ -17,12 +17,12 @@ EXPORT_DIR = os.path.join(LAT_MPC_DIR, "c_generated_code") JSON_FILE = os.path.join(LAT_MPC_DIR, "acados_ocp_lat.json") X_DIM = 4 P_DIM = 2 -N = 16 COST_E_DIM = 3 COST_DIM = COST_E_DIM + 2 SPEED_OFFSET = 10.0 MODEL_NAME = 'lat' ACADOS_SOLVER_TYPE = 'SQP_RTI' +N = 32 def gen_lat_model(): model = AcadosModel() @@ -168,14 +168,14 @@ class LateralMpc(): self.solver.constraints_set(0, "lbx", x0_cp) self.solver.constraints_set(0, "ubx", x0_cp) self.yref[:,0] = y_pts - v_ego = p_cp[0] + v_ego = p_cp[0, 0] # rotation_radius = p_cp[1] self.yref[:,1] = heading_pts * (v_ego + SPEED_OFFSET) self.yref[:,2] = yaw_rate_pts * (v_ego + SPEED_OFFSET) for i in range(N): self.solver.cost_set(i, "yref", self.yref[i]) - self.solver.set(i, "p", p_cp) - self.solver.set(N, "p", p_cp) + self.solver.set(i, "p", p_cp[i]) + self.solver.set(N, "p", p_cp[N]) self.solver.cost_set(N, "yref", self.yref[N][:COST_E_DIM]) t = sec_since_boot() diff --git a/selfdrive/controls/lib/lateral_planner.py b/selfdrive/controls/lib/lateral_planner.py index 29137defd3..7230b5ad14 100644 --- a/selfdrive/controls/lib/lateral_planner.py +++ b/selfdrive/controls/lib/lateral_planner.py @@ -4,7 +4,7 @@ from common.numpy_fast import interp from system.swaglog import cloudlog from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import LateralMpc from selfdrive.controls.lib.lateral_mpc_lib.lat_mpc import N as LAT_MPC_N -from selfdrive.controls.lib.drive_helpers import CONTROL_N +from selfdrive.controls.lib.drive_helpers import CONTROL_N, MIN_SPEED from selfdrive.controls.lib.desire_helper import DesireHelper import cereal.messaging as messaging from cereal import log @@ -16,13 +16,12 @@ CAMERA_OFFSET = 0.04 PATH_COST = 1.0 LATERAL_MOTION_COST = 0.11 LATERAL_ACCEL_COST = 0.0 -LATERAL_JERK_COST = 0.05 +LATERAL_JERK_COST = 0.04 # Extreme steering rate is unpleasant, even # when it does not cause bad jerk. # TODO this cost should be lowered when low # speed lateral control is stable on all cars -STEERING_RATE_COST = 800.0 -MIN_SPEED = 1.5 +STEERING_RATE_COST = 700.0 class LateralPlanner: @@ -36,6 +35,7 @@ class LateralPlanner: self.solution_invalid_cnt = 0 self.path_xyz = np.zeros((TRAJECTORY_SIZE, 3)) + self.velocity_xyz = np.zeros((TRAJECTORY_SIZE, 3)) self.plan_yaw = np.zeros((TRAJECTORY_SIZE,)) self.plan_yaw_rate = np.zeros((TRAJECTORY_SIZE,)) self.t_idxs = np.arange(TRAJECTORY_SIZE) @@ -50,7 +50,6 @@ class LateralPlanner: def update(self, sm): # clip speed , lateral planning is not possible at 0 speed - self.v_ego = max(MIN_SPEED, sm['carState'].vEgo) measured_curvature = sm['controlsState'].curvature # Parse model predictions @@ -60,6 +59,10 @@ class LateralPlanner: self.t_idxs = np.array(md.position.t) self.plan_yaw = np.array(md.orientation.z) self.plan_yaw_rate = np.array(md.orientationRate.z) + self.velocity_xyz = np.column_stack([md.velocity.x, md.velocity.y, md.velocity.z]) + car_speed = np.linalg.norm(self.velocity_xyz, axis=1) + self.v_plan = np.clip(car_speed, MIN_SPEED, np.inf) + self.v_ego = self.v_plan[0] # Lane change logic desire_state = md.meta.desireState @@ -69,21 +72,20 @@ class LateralPlanner: lane_change_prob = self.l_lane_change_prob + self.r_lane_change_prob self.DH.update(sm['carState'], sm['carControl'].latActive, lane_change_prob) - d_path_xyz = self.path_xyz self.lat_mpc.set_weights(PATH_COST, LATERAL_MOTION_COST, LATERAL_ACCEL_COST, LATERAL_JERK_COST, STEERING_RATE_COST) - y_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(d_path_xyz, axis=1), d_path_xyz[:, 1]) - heading_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw) - yaw_rate_pts = np.interp(self.v_ego * self.t_idxs[:LAT_MPC_N + 1], np.linalg.norm(self.path_xyz, axis=1), self.plan_yaw_rate) + y_pts = self.path_xyz[:LAT_MPC_N+1, 1] + heading_pts = self.plan_yaw[:LAT_MPC_N+1] + yaw_rate_pts = self.plan_yaw_rate[:LAT_MPC_N+1] self.y_pts = y_pts assert len(y_pts) == LAT_MPC_N + 1 assert len(heading_pts) == LAT_MPC_N + 1 assert len(yaw_rate_pts) == LAT_MPC_N + 1 - lateral_factor = max(0, self.factor1 - (self.factor2 * self.v_ego**2)) - p = np.array([self.v_ego, lateral_factor]) + lateral_factor = np.clip(self.factor1 - (self.factor2 * self.v_plan**2), 0.0, np.inf) + p = np.column_stack([self.v_plan, lateral_factor]) self.lat_mpc.run(self.x0, p, y_pts, diff --git a/selfdrive/controls/lib/longcontrol.py b/selfdrive/controls/lib/longcontrol.py index db5bf4d3e6..e8095813f2 100644 --- a/selfdrive/controls/lib/longcontrol.py +++ b/selfdrive/controls/lib/longcontrol.py @@ -10,12 +10,14 @@ LongCtrlState = car.CarControl.Actuators.LongControlState def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, v_target_1sec, brake_pressed, cruise_standstill): + # Ignore cruise standstill if car has a gas interceptor + cruise_standstill = cruise_standstill and not CP.enableGasInterceptor accelerating = v_target_1sec > v_target planned_stop = (v_target < CP.vEgoStopping and v_target_1sec < CP.vEgoStopping and not accelerating) stay_stopped = (v_ego < CP.vEgoStopping and - (brake_pressed or cruise_standstill)) + (brake_pressed or cruise_standstill)) stopping_condition = planned_stop or stay_stopped starting_condition = (v_target_1sec > CP.vEgoStarting and @@ -28,10 +30,8 @@ def long_control_state_trans(CP, active, long_control_state, v_ego, v_target, long_control_state = LongCtrlState.off else: - if long_control_state == LongCtrlState.off: + if long_control_state in (LongCtrlState.off, LongCtrlState.pid): long_control_state = LongCtrlState.pid - - elif long_control_state == LongCtrlState.pid: if stopping_condition: long_control_state = LongCtrlState.stopping @@ -103,6 +103,7 @@ class LongControl: elif self.long_control_state == LongCtrlState.stopping: if output_accel > self.CP.stopAccel: + output_accel = min(output_accel, 0.0) output_accel -= self.CP.stoppingDecelRate * DT_CTRL self.reset(CS.vEgo) diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript index 5a9e69c297..7f5daf157c 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/SConscript @@ -51,9 +51,10 @@ generated_files = [ ] + build_files acados_dir = '#third_party/acados' -acados_templates_dir = '#pyextra/acados_template/c_templates_tera' +acados_templates_dir = '#third_party/acados/acados_template/c_templates_tera' source_list = ['long_mpc.py', + '#/selfdrive/modeld/constants.py', f'{acados_dir}/include/acados_c/ocp_nlp_interface.h', f'{acados_dir}/x86_64/lib/libacados.so', f'{acados_dir}/larch64/lib/libacados.so', @@ -77,8 +78,8 @@ lib_solver = lenv.SharedLibrary(f"{gen}/acados_ocp_solver_long", LIBS=['m', 'acados', 'hpipm', 'blasfeo', 'qpOASES_e']) # generate cython stuff -acados_ocp_solver_pyx = File("#pyextra/acados_template/acados_ocp_solver_pyx.pyx") -acados_ocp_solver_common = File("#pyextra/acados_template/acados_solver_common.pxd") +acados_ocp_solver_pyx = File("#third_party/acados/acados_template/acados_ocp_solver_pyx.pyx") +acados_ocp_solver_common = File("#third_party/acados/acados_template/acados_solver_common.pxd") libacados_ocp_solver_pxd = File(f'{gen}/acados_solver.pxd') libacados_ocp_solver_c = File(f'{gen}/acados_ocp_solver_pyx.c') diff --git a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py index 2aab4b71af..660002691a 100644 --- a/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py +++ b/selfdrive/controls/lib/longitudinal_mpc_lib/long_mpc.py @@ -5,11 +5,12 @@ import numpy as np from common.realtime import sec_since_boot from common.numpy_fast import clip from system.swaglog import cloudlog +# WARNING: imports outside of constants will not trigger a rebuild from selfdrive.modeld.constants import index_function from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU if __name__ == '__main__': # generating code - from pyextra.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver + from third_party.acados.acados_template import AcadosModel, AcadosOcp, AcadosOcpSolver else: from selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx import AcadosOcpSolverCython # pylint: disable=no-name-in-module, import-error @@ -36,7 +37,7 @@ A_EGO_COST = 0. J_EGO_COST = 5.0 A_CHANGE_COST = 200. DANGER_ZONE_COST = 100. -CRASH_DISTANCE = .5 +CRASH_DISTANCE = .25 LEAD_DANGER_FACTOR = 0.75 LIMIT_COST = 1e6 ACADOS_SOLVER_TYPE = 'SQP_RTI' @@ -49,6 +50,7 @@ MAX_T = 10.0 T_IDXS_LST = [index_function(idx, max_val=MAX_T, max_idx=N) for idx in range(N+1)] T_IDXS = np.array(T_IDXS_LST) +FCW_IDXS = T_IDXS < 5.0 T_DIFFS = np.diff(T_IDXS, prepend=[0.]) MIN_ACCEL = -3.5 MAX_ACCEL = 2.0 @@ -253,7 +255,8 @@ class LongitudinalMpc: cost_weights = [X_EGO_OBSTACLE_COST, X_EGO_COST, V_EGO_COST, A_EGO_COST, a_change_cost, J_EGO_COST] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, DANGER_ZONE_COST] elif self.mode == 'blended': - cost_weights = [0., 0.1, 0.2, 5.0, 0.0, 1.0] + a_change_cost = 40.0 if prev_accel_constraint else 0 + cost_weights = [0., 0.1, 0.2, 5.0, a_change_cost, 1.0] constraint_cost_weights = [LIMIT_COST, LIMIT_COST, LIMIT_COST, 50.0] else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner cost set') @@ -299,10 +302,12 @@ class LongitudinalMpc: return lead_xv def set_accel_limits(self, min_a, max_a): + # TODO this sets a max accel limit, but the minimum limit is only for cruise decel + # needs refactor self.cruise_min_a = min_a - self.cruise_max_a = max_a + self.max_a = max_a - def update(self, carstate, radarstate, v_cruise, x, v, a, j): + def update(self, radarstate, v_cruise, x, v, a, j): v_ego = self.x0[1] self.status = radarstate.leadOne.status or radarstate.leadTwo.status @@ -315,16 +320,17 @@ class LongitudinalMpc: lead_0_obstacle = lead_xv_0[:,0] + get_stopped_equivalence_factor(lead_xv_0[:,1]) lead_1_obstacle = lead_xv_1[:,0] + get_stopped_equivalence_factor(lead_xv_1[:,1]) + self.params[:,0] = MIN_ACCEL + self.params[:,1] = self.max_a + # Update in ACC mode or ACC/e2e blend if self.mode == 'acc': - self.params[:,0] = MIN_ACCEL - self.params[:,1] = self.cruise_max_a self.params[:,5] = LEAD_DANGER_FACTOR # Fake an obstacle for cruise, this ensures smooth acceleration to set speed # when the leads are no factor. v_lower = v_ego + (T_IDXS * self.cruise_min_a * 1.05) - v_upper = v_ego + (T_IDXS * self.cruise_max_a * 1.05) + v_upper = v_ego + (T_IDXS * self.max_a * 1.05) v_cruise_clipped = np.clip(v_cruise * np.ones(N+1), v_lower, v_upper) @@ -336,9 +342,6 @@ class LongitudinalMpc: x[:], v[:], a[:], j[:] = 0.0, 0.0, 0.0, 0.0 elif self.mode == 'blended': - self.params[:,0] = MIN_ACCEL - self.params[:,1] = MAX_ACCEL - self.params[:,5] = 1.0 x_obstacles = np.column_stack([lead_0_obstacle, @@ -350,7 +353,7 @@ class LongitudinalMpc: x_and_cruise = np.column_stack([x, cruise_target]) x = np.min(x_and_cruise, axis=1) - self.source = 'e2e' if x_and_cruise[0,0] < x_and_cruise[0,1] else 'cruise' + self.source = 'e2e' if x_and_cruise[1,0] < x_and_cruise[1,1] else 'cruise' else: raise NotImplementedError(f'Planner mode {self.mode} not recognized in planner update') @@ -368,7 +371,7 @@ class LongitudinalMpc: self.params[:,4] = T_FOLLOW self.run() - if (np.any(lead_xv_0[:,0] - self.x_sol[:,0] < CRASH_DISTANCE) and + if (np.any(lead_xv_0[FCW_IDXS,0] - self.x_sol[FCW_IDXS,0] < CRASH_DISTANCE) and radarstate.leadOne.modelProb > 0.9): self.crash_cnt += 1 else: diff --git a/selfdrive/controls/lib/longitudinal_planner.py b/selfdrive/controls/lib/longitudinal_planner.py index 018136e6f2..e959b14ee3 100755 --- a/selfdrive/controls/lib/longitudinal_planner.py +++ b/selfdrive/controls/lib/longitudinal_planner.py @@ -6,17 +6,15 @@ from common.numpy_fast import clip, interp import cereal.messaging as messaging from common.conversions import Conversions as CV from common.filter_simple import FirstOrderFilter -from common.params import Params from common.realtime import DT_MDL from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longcontrol import LongCtrlState -from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc +from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import LongitudinalMpc, MIN_ACCEL, MAX_ACCEL from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import T_IDXS as T_IDXS_MPC from selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, CONTROL_N from system.swaglog import cloudlog LON_MPC_STEP = 0.2 # first step is 0.2s -AWARENESS_DECEL = -0.2 # car smoothly decel at .2m/s^2 when user is distracted A_CRUISE_MIN = -1.2 A_CRUISE_MAX_VALS = [1.6, 1.2, 0.8, 0.6] A_CRUISE_MAX_BP = [0., 10.0, 25., 40.] @@ -48,32 +46,25 @@ def limit_accel_in_turns(v_ego, angle_steers, a_target, CP): class LongitudinalPlanner: def __init__(self, CP, init_v=0.0, init_a=0.0): self.CP = CP - self.params = Params() - self.param_read_counter = 0 - self.mpc = LongitudinalMpc() - self.read_param() - self.fcw = False self.a_desired = init_a self.v_desired_filter = FirstOrderFilter(init_v, 2.0, DT_MDL) + self.v_model_error = 0.0 self.v_desired_trajectory = np.zeros(CONTROL_N) self.a_desired_trajectory = np.zeros(CONTROL_N) self.j_desired_trajectory = np.zeros(CONTROL_N) self.solverExecutionTime = 0.0 - def read_param(self): - e2e = self.params.get_bool('EndToEndLong') and self.CP.openpilotLongitudinalControl - self.mpc.mode = 'blended' if e2e else 'acc' - - def parse_model(self, model_msg): + @staticmethod + def parse_model(model_msg, model_error): if (len(model_msg.position.x) == 33 and len(model_msg.velocity.x) == 33 and len(model_msg.acceleration.x) == 33): - x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) + x = np.interp(T_IDXS_MPC, T_IDXS, model_msg.position.x) - model_error * T_IDXS_MPC + v = np.interp(T_IDXS_MPC, T_IDXS, model_msg.velocity.x) - model_error a = np.interp(T_IDXS_MPC, T_IDXS, model_msg.acceleration.x) j = np.zeros(len(T_IDXS_MPC)) else: @@ -83,10 +74,8 @@ class LongitudinalPlanner: j = np.zeros(len(T_IDXS_MPC)) return x, v, a, j - def update(self, sm, read=True): - if self.param_read_counter % 50 == 0 and read: - self.read_param() - self.param_read_counter += 1 + def update(self, sm): + self.mpc.mode = 'blended' if sm['controlsState'].experimentalMode else 'acc' v_ego = sm['carState'].vEgo v_cruise_kph = sm['controlsState'].vCruise @@ -102,8 +91,12 @@ class LongitudinalPlanner: # No change cost when user is controlling the speed, or when standstill prev_accel_constraint = not (reset_state or sm['carState'].standstill) - 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) + if self.mpc.mode == 'acc': + 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) + else: + accel_limits = [MIN_ACCEL, MAX_ACCEL] + accel_limits_turns = [MIN_ACCEL, MAX_ACCEL] if reset_state: self.v_desired_filter.x = v_ego @@ -112,11 +105,12 @@ class LongitudinalPlanner: # Prevent divergence, smooth in current v_ego self.v_desired_filter.x = max(0.0, self.v_desired_filter.update(v_ego)) + # Compute model v_ego error + if len(sm['modelV2'].temporalPose.trans): + self.v_model_error = sm['modelV2'].temporalPose.trans[0] - v_ego if force_slow_decel: - # if required so, force a smooth deceleration - accel_limits_turns[1] = min(accel_limits_turns[1], AWARENESS_DECEL) - accel_limits_turns[0] = min(accel_limits_turns[0], accel_limits_turns[1]) + v_cruise = 0.0 # clip limits, cannot init MPC outside of bounds 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) @@ -124,16 +118,17 @@ class LongitudinalPlanner: self.mpc.set_weights(prev_accel_constraint) self.mpc.set_accel_limits(accel_limits_turns[0], accel_limits_turns[1]) self.mpc.set_cur_state(self.v_desired_filter.x, self.a_desired) - x, v, a, j = self.parse_model(sm['modelV2']) - self.mpc.update(sm['carState'], sm['radarState'], v_cruise, x, v, a, j) + x, v, a, j = self.parse_model(sm['modelV2'], self.v_model_error) + self.mpc.update(sm['radarState'], v_cruise, x, v, a, j) - 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) + self.v_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.v_solution) + self.a_desired_trajectory_full = np.interp(T_IDXS, T_IDXS_MPC, self.mpc.a_solution) + self.v_desired_trajectory = self.v_desired_trajectory_full[:CONTROL_N] + self.a_desired_trajectory = self.a_desired_trajectory_full[:CONTROL_N] self.j_desired_trajectory = np.interp(T_IDXS[:CONTROL_N], T_IDXS_MPC[:-1], self.mpc.j_solution) # TODO counter is only needed because radar is glitchy, remove once radar is gone - # TODO write fcw in e2e_long mode - self.fcw = self.mpc.mode == 'acc' and self.mpc.crash_cnt > 5 + self.fcw = self.mpc.crash_cnt > 2 and not sm['carState'].standstill if self.fcw: cloudlog.info("FCW triggered") diff --git a/selfdrive/controls/lib/radar_helpers.py b/selfdrive/controls/lib/radar_helpers.py index bf788190cd..4bb0179267 100644 --- a/selfdrive/controls/lib/radar_helpers.py +++ b/selfdrive/controls/lib/radar_helpers.py @@ -151,7 +151,8 @@ class Cluster(): def potential_low_speed_lead(self, v_ego): # stop for stuff in front of you and low speed, even without model confirmation - return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and self.dRel < 25 + # Radar points closer than 0.75, are almost always glitches on toyota radars + return abs(self.yRel) < 1.0 and (v_ego < v_ego_stationary) and (0.75 < self.dRel < 25) def is_potential_fcw(self, model_prob): return model_prob > .9 diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index f15ab2fa56..993774f719 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -20,7 +20,7 @@ class TestLatControl(unittest.TestCase): @parameterized.expand([(HONDA.CIVIC, LatControlPID), (TOYOTA.RAV4, LatControlTorque), (TOYOTA.PRIUS, LatControlINDI), (NISSAN.LEAF, LatControlAngle)]) def test_saturation(self, car_name, controller): CarInterface, CarController, CarState = interfaces[car_name] - CP = CarInterface.get_params(car_name) + CP = CarInterface.get_non_essential_params(car_name) CI = CarInterface(CP, CarController, CarState) VM = VehicleModel(CP) diff --git a/selfdrive/controls/lib/tests/test_vehicle_model.py b/selfdrive/controls/lib/tests/test_vehicle_model.py index 3e08cb0aa0..03d97a7e3f 100755 --- a/selfdrive/controls/lib/tests/test_vehicle_model.py +++ b/selfdrive/controls/lib/tests/test_vehicle_model.py @@ -12,7 +12,7 @@ from selfdrive.controls.lib.vehicle_model import VehicleModel, dyn_ss_sol, creat class TestVehicleModel(unittest.TestCase): def setUp(self): - CP = CarInterface.get_params(CAR.CIVIC) + CP = CarInterface.get_non_essential_params(CAR.CIVIC) self.VM = VehicleModel(CP) def test_round_trip_yaw_rate(self): diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 93d0c80dac..14934254af 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -1,12 +1,30 @@ #!/usr/bin/env python3 +import numpy as np from cereal import car from common.params import Params from common.realtime import Priority, config_realtime_process from system.swaglog import cloudlog +from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from selfdrive.controls.lib.lateral_planner import LateralPlanner import cereal.messaging as messaging +def cumtrapz(x, t): + return np.concatenate([[0], np.cumsum(((x[0:-1] + x[1:])/2) * np.diff(t))]) + +def publish_ui_plan(sm, pm, lateral_planner, longitudinal_planner): + plan_odo = cumtrapz(longitudinal_planner.v_desired_trajectory_full, T_IDXS) + model_odo = cumtrapz(lateral_planner.v_plan, T_IDXS) + + ui_send = messaging.new_message('uiPlan') + ui_send.valid = sm.all_checks(service_list=['carState', 'controlsState', 'modelV2']) + uiPlan = ui_send.uiPlan + uiPlan.frameId = sm['modelV2'].frameId + uiPlan.position.x = np.interp(plan_odo, model_odo, lateral_planner.lat_mpc.x_sol[:,0]).tolist() + uiPlan.position.y = np.interp(plan_odo, model_odo, lateral_planner.lat_mpc.x_sol[:,1]).tolist() + uiPlan.position.z = np.interp(plan_odo, model_odo, lateral_planner.path_xyz[:,2]).tolist() + uiPlan.accel = longitudinal_planner.a_desired_trajectory_full.tolist() + pm.send('uiPlan', ui_send) def plannerd_thread(sm=None, pm=None): config_realtime_process(5, Priority.CTRL_LOW) @@ -24,7 +42,7 @@ def plannerd_thread(sm=None, pm=None): poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) if pm is None: - pm = messaging.PubMaster(['longitudinalPlan', 'lateralPlan']) + pm = messaging.PubMaster(['longitudinalPlan', 'lateralPlan', 'uiPlan']) while True: sm.update() @@ -34,7 +52,7 @@ def plannerd_thread(sm=None, pm=None): lateral_planner.publish(sm, pm) longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) - + publish_ui_plan(sm, pm, lateral_planner, longitudinal_planner) def main(sm=None, pm=None): plannerd_thread(sm, pm) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 3d958139d6..34f0f274fe 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -8,9 +8,9 @@ from cereal import car from common.numpy_fast import interp from common.params import Params from common.realtime import Ratekeeper, Priority, config_realtime_process -from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid from selfdrive.controls.lib.radar_helpers import Cluster, Track, RADAR_TO_CAMERA from system.swaglog import cloudlog +from third_party.cluster.fastcluster_py import cluster_points_centroid class KalmanParams(): @@ -35,7 +35,7 @@ class KalmanParams(): self.K = [[interp(dt, dts, K0)], [interp(dt, dts, K1)]] -def laplacian_cdf(x, mu, b): +def laplacian_pdf(x, mu, b): b = max(b, 1e-4) return math.exp(-abs(x-mu)/b) @@ -45,9 +45,9 @@ def match_vision_to_cluster(v_ego, lead, clusters): offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA def prob(c): - prob_d = laplacian_cdf(c.dRel, offset_vision_dist, lead.xStd[0]) - prob_y = laplacian_cdf(c.yRel, -lead.y[0], lead.yStd[0]) - prob_v = laplacian_cdf(c.vRel + v_ego, lead.v[0], lead.vStd[0]) + prob_d = laplacian_pdf(c.dRel, offset_vision_dist, lead.xStd[0]) + prob_y = laplacian_pdf(c.yRel, -lead.y[0], lead.yStd[0]) + prob_v = laplacian_pdf(c.vRel + v_ego, lead.v[0], lead.vStd[0]) # This is isn't exactly right, but good heuristic return prob_d * prob_y * prob_v @@ -165,7 +165,6 @@ class RadarD(): dat.valid = sm.all_checks() and len(rr.errors) == 0 radarState = dat.radarState radarState.mdMonoTime = sm.logMonoTime['modelV2'] - radarState.canMonoTimes = list(rr.canMonoTimes) radarState.radarErrors = list(rr.errors) radarState.carStateMonoTime = sm.logMonoTime['carState'] diff --git a/selfdrive/controls/tests/test_clustering.py b/selfdrive/controls/tests/test_clustering.py index 290b267029..99c24d5938 100644 --- a/selfdrive/controls/tests/test_clustering.py +++ b/selfdrive/controls/tests/test_clustering.py @@ -7,8 +7,8 @@ from fastcluster import linkage_vector from scipy.cluster import _hierarchy from scipy.spatial.distance import pdist -from selfdrive.controls.lib.cluster.fastcluster_py import hclust, ffi -from selfdrive.controls.lib.cluster.fastcluster_py import cluster_points_centroid +from third_party.cluster.fastcluster_py import hclust, ffi +from third_party.cluster.fastcluster_py import cluster_points_centroid def fcluster(Z, t, criterion='inconsistent', depth=2, R=None, monocrit=None): diff --git a/selfdrive/controls/tests/test_cruise_speed.py b/selfdrive/controls/tests/test_cruise_speed.py old mode 100644 new mode 100755 index a972bfb073..b83116af45 --- a/selfdrive/controls/tests/test_cruise_speed.py +++ b/selfdrive/controls/tests/test_cruise_speed.py @@ -1,12 +1,18 @@ #!/usr/bin/env python3 -import unittest import numpy as np -from common.params import Params - +from parameterized import parameterized_class +import unittest +from selfdrive.controls.lib.drive_helpers import VCruiseHelper, V_CRUISE_MIN, V_CRUISE_MAX, V_CRUISE_INITIAL, IMPERIAL_INCREMENT +from cereal import car +from common.conversions import Conversions as CV from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver -def run_cruise_simulation(cruise, t_end=20.): +ButtonEvent = car.CarState.ButtonEvent +ButtonType = car.CarState.ButtonEvent.Type + + +def run_cruise_simulation(cruise, e2e, t_end=20.): man = Maneuver( '', duration=t_end, @@ -16,24 +22,130 @@ def run_cruise_simulation(cruise, t_end=20.): cruise_values=[cruise], prob_lead_values=[0.0], breakpoints=[0.], + e2e=e2e, ) valid, output = man.evaluate() assert valid - return output[-1,3] + return output[-1, 3] class TestCruiseSpeed(unittest.TestCase): def test_cruise_speed(self): - params = Params() for e2e in [False, True]: - params.put_bool("EndToEndLong", e2e) for speed in np.arange(5, 40, 5): print(f'Testing {speed} m/s') cruise_speed = float(speed) - simulation_steady_state = run_cruise_simulation(cruise_speed) + simulation_steady_state = run_cruise_simulation(cruise_speed, e2e) self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {speed} m/s') +# TODO: test pcmCruise +@parameterized_class(('pcm_cruise',), [(False,)]) +class TestVCruiseHelper(unittest.TestCase): + def setUp(self): + self.CP = car.CarParams(pcmCruise=self.pcm_cruise) # pylint: disable=E1101 + self.v_cruise_helper = VCruiseHelper(self.CP) + self.reset_cruise_speed_state() + + def reset_cruise_speed_state(self): + # Two resets previous cruise speed + for _ in range(2): + self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False) + + def enable(self, v_ego, experimental_mode): + # Simulates user pressing set with a current speed + self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego), experimental_mode) + + def test_adjust_speed(self): + """ + Asserts speed changes on falling edges of buttons. + """ + + self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) + + for btn in (ButtonType.accelCruise, ButtonType.decelCruise): + for pressed in (True, False): + CS = car.CarState(cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=btn, pressed=pressed)] + + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + self.assertEqual(pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + + def test_rising_edge_enable(self): + """ + Some car interfaces may enable on rising edge of a button, + ensure we don't adjust speed if enabled changes mid-press. + """ + + # NOTE: enabled is always one frame behind the result from button press in controlsd + for enabled, pressed in ((False, False), + (False, True), + (True, False)): + CS = car.CarState(cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)] + self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False) + if pressed: + self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) + + # Expected diff on enabling. Speed should not change on falling edge of pressed + self.assertEqual(not pressed, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + + def test_resume_in_standstill(self): + """ + Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill. + """ + + self.enable(0, False) + + for standstill in (True, False): + for pressed in (True, False): + CS = car.CarState(cruiseState={"available": True, "standstill": standstill}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=pressed)] + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + + # speed should only update if not at standstill and button falling edge + should_equal = standstill or pressed + self.assertEqual(should_equal, self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last) + + def test_set_gas_pressed(self): + """ + Asserts pressing set while enabled with gas pressed sets + the speed to the maximum of vEgo and current cruise speed. + """ + + for v_ego in np.linspace(0, 100, 101): + self.reset_cruise_speed_state() + self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False) + + # first decrement speed, then perform gas pressed logic + expected_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - IMPERIAL_INCREMENT + expected_v_cruise_kph = max(expected_v_cruise_kph, v_ego * CV.MS_TO_KPH) # clip to min of vEgo + expected_v_cruise_kph = float(np.clip(round(expected_v_cruise_kph, 1), V_CRUISE_MIN, V_CRUISE_MAX)) + + CS = car.CarState(vEgo=float(v_ego), gasPressed=True, cruiseState={"available": True}) + CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=False)] + self.v_cruise_helper.update_v_cruise(CS, enabled=True, is_metric=False) + + # TODO: fix skipping first run due to enabled on rising edge exception + if v_ego == 0.0: + continue + self.assertEqual(expected_v_cruise_kph, self.v_cruise_helper.v_cruise_kph) + + def test_initialize_v_cruise(self): + """ + Asserts allowed cruise speeds on enabling with SET. + """ + + for experimental_mode in (True, False): + for v_ego in np.linspace(0, 100, 101): + self.reset_cruise_speed_state() + self.assertFalse(self.v_cruise_helper.v_cruise_initialized) + + self.enable(float(v_ego), experimental_mode) + self.assertTrue(V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX) + self.assertTrue(self.v_cruise_helper.v_cruise_initialized) + + if __name__ == "__main__": unittest.main() diff --git a/selfdrive/controls/tests/test_following_distance.py b/selfdrive/controls/tests/test_following_distance.py index 3534f58235..5185867d2d 100644 --- a/selfdrive/controls/tests/test_following_distance.py +++ b/selfdrive/controls/tests/test_following_distance.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 import unittest import numpy as np -from common.params import Params - from selfdrive.controls.lib.longitudinal_mpc_lib.long_mpc import desired_follow_distance from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver + def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): man = Maneuver( '', @@ -16,7 +15,7 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): initial_distance_lead=100, speed_lead_values=[v_lead], breakpoints=[0.], - e2e=e2e + e2e=e2e, ) valid, output = man.evaluate() assert valid @@ -25,13 +24,11 @@ def run_following_distance_simulation(v_lead, t_end=100.0, e2e=False): class TestFollowingDistance(unittest.TestCase): def test_following_distance(self): - params = Params() for e2e in [False, True]: - params.put_bool("EndToEndLong", e2e) for speed in np.arange(0, 40, 5): print(f'Testing {speed} m/s') v_lead = float(speed) - simulation_steady_state = run_following_distance_simulation(v_lead) + simulation_steady_state = run_following_distance_simulation(v_lead, e2e=e2e) correct_steady_state = desired_follow_distance(v_lead, v_lead) err_ratio = 0.2 if e2e else 0.1 self.assertAlmostEqual(simulation_steady_state, correct_steady_state, delta=(err_ratio * correct_steady_state + .5)) diff --git a/selfdrive/controls/tests/test_lateral_mpc.py b/selfdrive/controls/tests/test_lateral_mpc.py index df5154b2b4..b569da09b4 100644 --- a/selfdrive/controls/tests/test_lateral_mpc.py +++ b/selfdrive/controls/tests/test_lateral_mpc.py @@ -17,7 +17,8 @@ def run_mpc(lat_mpc=None, v_ref=30., x_init=0., y_init=0., psi_init=0., curvatur curv_rate_pts = np.zeros(LAT_MPC_N + 1) x0 = np.array([x_init, y_init, psi_init, curvature_init]) - p = np.array([v_ref, CAR_ROTATION_RADIUS]) + p = np.column_stack([v_ref * np.ones(LAT_MPC_N + 1), + CAR_ROTATION_RADIUS * np.ones(LAT_MPC_N + 1)]) # converge in no more than 10 iterations for _ in range(10): diff --git a/selfdrive/controls/tests/test_startup.py b/selfdrive/controls/tests/test_startup.py index ba2d2f5c02..18c8e79026 100755 --- a/selfdrive/controls/tests/test_startup.py +++ b/selfdrive/controls/tests/test_startup.py @@ -108,6 +108,10 @@ class TestStartup(unittest.TestCase): finger = _FINGERPRINTS[car_model][0] for _ in range(1000): + # controlsd waits for boardd to echo back that it has changed the multiplexing mode + if not params.get_bool("ObdMultiplexingChanged"): + params.put_bool("ObdMultiplexingChanged", True) + msgs = [[addr, 0, b'\x00'*length, 0] for addr, length in finger.items()] pm.send('can', can_list_to_can_capnp(msgs)) diff --git a/selfdrive/controls/tests/test_state_machine.py b/selfdrive/controls/tests/test_state_machine.py index 36535dfdaf..d5f468f214 100755 --- a/selfdrive/controls/tests/test_state_machine.py +++ b/selfdrive/controls/tests/test_state_machine.py @@ -31,7 +31,7 @@ class TestStateMachine(unittest.TestCase): def setUp(self): CarInterface, CarController, CarState = interfaces["mock"] - CP = CarInterface.get_params("mock") + CP = CarInterface.get_non_essential_params("mock") CI = CarInterface(CP, CarController, CarState) self.controlsd = Controls(CI=CI) @@ -79,7 +79,7 @@ class TestStateMachine(unittest.TestCase): self.assertEqual(self.controlsd.state, State.disabled) def test_no_entry(self): - # disabled with enable events + # Make sure noEntry keeps us disabled for et in ENABLE_EVENT_TYPES: self.controlsd.events.add(make_event([ET.NO_ENTRY, et])) self.controlsd.state_transition(self.CS) @@ -87,11 +87,11 @@ class TestStateMachine(unittest.TestCase): self.controlsd.events.clear() def test_no_entry_pre_enable(self): - # preEnabled with preEnabled event + # preEnabled with noEntry event self.controlsd.state = State.preEnabled self.controlsd.events.add(make_event([ET.NO_ENTRY, ET.PRE_ENABLE])) self.controlsd.state_transition(self.CS) - self.assertEqual(self.controlsd.state, State.disabled) + self.assertEqual(self.controlsd.state, State.preEnabled) def test_maintain_states(self): # Given current state's event type, we should maintain state diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py index b883fbc23f..ff98c20e60 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/selfdrive/debug/can_print_changes.py @@ -54,6 +54,7 @@ def can_printer(bus=0, init_msgs=None, new_msgs=None, table=False): update(new_msgs, bus, dat, low_to_high, high_to_low) else: # Live mode + print(f"Waiting for messages on bus {bus}") try: while 1: can_recv = messaging.drain_sock(logcan) @@ -89,14 +90,17 @@ if __name__ == "__main__": formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument("--bus", type=int, help="CAN bus to print out", default=0) parser.add_argument("--table", action="store_true", help="Print a cabana-like table") - parser.add_argument("init", type=str, nargs='?', help="Route or segment to initialize with") + parser.add_argument("init", type=str, nargs='?', help="Route or segment to initialize with. Use empty quotes to compare against all zeros.") parser.add_argument("comp", type=str, nargs='?', help="Route or segment to compare against init") args = parser.parse_args() init_lr, new_lr = None, None if args.init: - init_lr = logreader_from_route_or_segment(args.init) + if args.init == '': + init_lr = [] + else: + init_lr = logreader_from_route_or_segment(args.init) if args.comp: new_lr = logreader_from_route_or_segment(args.comp) diff --git a/selfdrive/debug/cycle_alerts.py b/selfdrive/debug/cycle_alerts.py index b40c8e304c..71c7b34be5 100755 --- a/selfdrive/debug/cycle_alerts.py +++ b/selfdrive/debug/cycle_alerts.py @@ -51,7 +51,7 @@ def cycle_alerts(duration=200, is_metric=False): cameras = ['roadCameraState', 'wideRoadCameraState', 'driverCameraState'] CS = car.CarState.new_message() - CP = CarInterface.get_params("HONDA CIVIC 2016") + CP = CarInterface.get_non_essential_params("HONDA CIVIC 2016") sm = messaging.SubMaster(['deviceState', 'pandaStates', 'roadCameraState', 'modelV2', 'liveCalibration', 'driverMonitoringState', 'longitudinalPlan', 'lateralPlan', 'liveLocationKalman', 'managerState'] + cameras) diff --git a/selfdrive/debug/fingerprint_from_route.py b/selfdrive/debug/fingerprint_from_route.py index 326e68f8e7..b3598b105c 100755 --- a/selfdrive/debug/fingerprint_from_route.py +++ b/selfdrive/debug/fingerprint_from_route.py @@ -17,7 +17,7 @@ def get_fingerprint(lr): for c in msg.can: # read also msgs sent by EON on CAN bus 0x80 and filter out the # addr with more than 11 bits - if c.src % 0x80 == 0 and c.address < 0x800: + if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8): msgs[c.address] = len(c.dat) # show CAN fingerprint diff --git a/selfdrive/debug/get_fingerprint.py b/selfdrive/debug/get_fingerprint.py index e678db4f17..f7f7a1604f 100755 --- a/selfdrive/debug/get_fingerprint.py +++ b/selfdrive/debug/get_fingerprint.py @@ -22,7 +22,7 @@ while True: for c in lc.can: # read also msgs sent by EON on CAN bus 0x80 and filter out the # addr with more than 11 bits - if c.src in [0, 2] and c.address < 0x800: + if c.src % 0x80 == 0 and c.address < 0x800 and c.address not in (0x7df, 0x7e0, 0x7e8): msgs[c.address] = len(c.dat) fingerprint = ', '.join("%d: %d" % v for v in sorted(msgs.items())) diff --git a/selfdrive/debug/hyundai_enable_radar_points.py b/selfdrive/debug/hyundai_enable_radar_points.py index ac7e7102d0..3a0ff33cb6 100755 --- a/selfdrive/debug/hyundai_enable_radar_points.py +++ b/selfdrive/debug/hyundai_enable_radar_points.py @@ -32,6 +32,9 @@ SUPPORTED_FW_VERSIONS = { b"DN8_ SCC FHCUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + b"DN8_ SCC F-CUP 1.00 1.00 99110-L0000\x19\x08)\x15T ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), # 2021 SONATA HYBRID b"DNhe SCC FHCUP 1.00 1.02 99110-L5000 \x01#\x15# ": ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", @@ -52,6 +55,10 @@ SUPPORTED_FW_VERSIONS = { b'IK__ SCC F-CUP 1.00 1.02 96400-G9100\x18\x07\x06\x17\x12 ': ConfigValues( default_config=b"\x00\x00\x00\x01\x00\x00", tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), + # 2019 SANTA FE + b"TM__ SCC F-CUP 1.00 1.00 99110-S1210\x19\x01%\x168 ": ConfigValues( + default_config=b"\x00\x00\x00\x01\x00\x00", + tracks_enabled=b"\x00\x00\x00\x01\x00\x01"), } if __name__ == "__main__": diff --git a/selfdrive/debug/internal/check_frame_frequencies.py b/selfdrive/debug/internal/check_frame_frequencies.py new file mode 100755 index 0000000000..9ac8bfc80e --- /dev/null +++ b/selfdrive/debug/internal/check_frame_frequencies.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 + +import time +import statistics +import cereal.messaging as messaging + +from typing import Dict + +camera_states = [ + 'roadCameraState', + 'wideRoadCameraState', + 'driverCameraState' +] + +def fmt(val): + ref = 0.05 + return f"{val:.6f} ({100 * val / ref:.2f}%)" + +if __name__ == "__main__": + sm = messaging.SubMaster(camera_states) + + prev_sof = {state: None for state in camera_states} + diffs: Dict[str, list] = {state: [] for state in camera_states} + + st = time.monotonic() + while True: + sm.update() + + for state in camera_states: + if sm.updated[state]: + if prev_sof[state] is not None: + diffs[state].append((sm[state].timestampSof - prev_sof[state]) / 1e9) + prev_sof[state] = sm[state].timestampSof + + if time.monotonic() - st > 10: + for state in camera_states: + values = diffs[state] + ref = 0.05 + print(f"{state} \tMean: {fmt(statistics.mean(values))} \t Min: {fmt(min(values))} \t Max: {fmt(max(values))} \t Std: {statistics.stdev(values):.6f} \t Num frames: {len(values)}") + diffs[state] = [] + + print() + st = time.monotonic() diff --git a/selfdrive/debug/internal/fuzz_fw_fingerprint.py b/selfdrive/debug/internal/fuzz_fw_fingerprint.py index 1ea133cc19..a18390fef3 100755 --- a/selfdrive/debug/internal/fuzz_fw_fingerprint.py +++ b/selfdrive/debug/internal/fuzz_fw_fingerprint.py @@ -28,7 +28,7 @@ if __name__ == "__main__": for candidate, fws in FWS.items(): fw_dict = {} for (tp, addr, subaddr), fw_list in fws.items(): - fw_dict[(addr, subaddr)] = random.choice(fw_list) + fw_dict[(addr, subaddr)] = [random.choice(fw_list)] matches = match_fw_to_car_fuzzy(fw_dict, log=False, exclude=candidate) diff --git a/selfdrive/debug/internal/test_paramsd.py b/selfdrive/debug/internal/test_paramsd.py index 3d8e422c35..81524496f7 100755 --- a/selfdrive/debug/internal/test_paramsd.py +++ b/selfdrive/debug/internal/test_paramsd.py @@ -20,7 +20,7 @@ T_SIM = 5 * 60 # s DT = 0.01 -CP = CarInterface.get_params(CAR.CIVIC) +CP = CarInterface.get_non_essential_params(CAR.CIVIC) VM = VehicleModel(CP) x, y = 0, 0 # m, m diff --git a/selfdrive/debug/print_docs_diff.py b/selfdrive/debug/print_docs_diff.py index b804286458..103dd52dd8 100755 --- a/selfdrive/debug/print_docs_diff.py +++ b/selfdrive/debug/print_docs_diff.py @@ -9,6 +9,7 @@ from selfdrive.car.docs_definitions import Column FOOTNOTE_TAG = "{}" STAR_ICON = '' +VIDEO_ICON = '' COLUMNS = "|" + "|".join([column.value for column in Column]) + "|" COLUMN_HEADER = "|---|---|---|{}|".format("|".join([":---:"] * (len(Column) - 3))) ARROW_SYMBOL = "➡️" @@ -39,8 +40,8 @@ def match_cars(base_cars, new_cars): def build_column_diff(base_car, new_car): row_builder = [] for column in Column: - base_column = base_car.get_column(column, STAR_ICON, FOOTNOTE_TAG) - new_column = new_car.get_column(column, STAR_ICON, FOOTNOTE_TAG) + base_column = base_car.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) + new_column = new_car.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) if base_column != new_column: row_builder.append(f"{base_column} {ARROW_SYMBOL} {new_column}") @@ -74,11 +75,11 @@ def print_car_info_diff(path): # Removals for car_info in car_removals: - changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column])) + changes["removals"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) # Additions for car_info in car_additions: - changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, FOOTNOTE_TAG) for column in Column])) + changes["additions"].append(format_row([car_info.get_column(column, STAR_ICON, VIDEO_ICON, FOOTNOTE_TAG) for column in Column])) for new_car, base_car in car_changes: # Column changes diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index 595e25e8c3..dc6688324f 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -68,13 +68,13 @@ if __name__ == "__main__": CP = None for msg in lr: if msg.which() == "pandaStates": - if msg.pandaStates[0].pandaType not in ('uno', 'blackPanda', 'dos'): + if msg.pandaStates[0].pandaType in ('unknown', 'whitePanda', 'greyPanda', 'pedal'): print("wrong panda type") break elif msg.which() == "carParams": CP = msg.carParams - car_fw = CP.carFw + car_fw = [fw for fw in CP.carFw if not fw.logging] if len(car_fw) == 0: print("no fw") break diff --git a/selfdrive/debug/vw_mqb_config.py b/selfdrive/debug/vw_mqb_config.py index 8c4dbc55ee..8952405b8e 100755 --- a/selfdrive/debug/vw_mqb_config.py +++ b/selfdrive/debug/vw_mqb_config.py @@ -126,6 +126,7 @@ if __name__ == "__main__": uds_client.security_access(ACCESS_TYPE_LEVEL_1.SEND_KEY, struct.pack("!I", key)) # type: ignore except (NegativeResponseError, MessageTimeoutError): print("Security access failed!") + print("Open the hood and retry (disables the \"diagnostic firewall\" on newer vehicles)") quit() try: diff --git a/selfdrive/locationd/.gitignore b/selfdrive/locationd/.gitignore index 5268902785..11b9f127b2 100644 --- a/selfdrive/locationd/.gitignore +++ b/selfdrive/locationd/.gitignore @@ -1,5 +1,3 @@ -ubloxd -ubloxd_test params_learner paramsd locationd diff --git a/selfdrive/locationd/SConscript b/selfdrive/locationd/SConscript index 4b7fba19b6..740f827a49 100644 --- a/selfdrive/locationd/SConscript +++ b/selfdrive/locationd/SConscript @@ -1,14 +1,6 @@ Import('env', 'common', 'cereal', 'messaging', 'libkf', 'transformations') -loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'kaitai', 'pthread'] - -if GetOption('kaitai'): - generated = Dir('generated').srcnode().abspath - cmd = f"kaitai-struct-compiler --target cpp_stl --outdir {generated} $SOURCES" - env.Command(['generated/ubx.cpp', 'generated/ubx.h'], 'ubx.ksy', cmd) - env.Command(['generated/gps.cpp', 'generated/gps.h'], 'gps.ksy', cmd) - -env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp"], LIBS=loc_libs) +loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'pthread'] ekf_sym_cc = env.SharedObject("#rednose/helpers/ekf_sym.cc") locationd_sources = ["locationd.cc", "models/live_kf.cc", ekf_sym_cc] @@ -19,4 +11,4 @@ lenv.Depends(locationd, libkf) if File("liblocationd.cc").exists(): liblocationd = lenv.SharedLibrary("liblocationd", ["liblocationd.cc"] + locationd_sources, LIBS=loc_libs + transformations) - lenv.Depends(liblocationd, libkf) + lenv.Depends(liblocationd, libkf) \ No newline at end of file diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 9e6536f9b5..1c68eb67bd 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -31,6 +31,7 @@ INPUTS_NEEDED = 5 # Minimum blocks needed for valid calibration INPUTS_WANTED = 50 # We want a little bit more than we need for stability MAX_ALLOWED_SPREAD = np.radians(2) RPY_INIT = np.array([0.0,0.0,0.0]) +WIDE_FROM_DEVICE_EULER_INIT = np.array([0.0, 0.0, 0.0]) # These values are needed to accommodate biggest modelframe PITCH_LIMITS = np.array([-0.09074112085129739, 0.14907572052989657]) @@ -65,8 +66,8 @@ class Calibrator: # Read saved calibration params = Params() calibration_params = params.get("CalibrationParams") - self.wide_camera = params.get_bool('WideCameraOnly') rpy_init = RPY_INIT + wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT valid_blocks = 0 if param_put and calibration_params: @@ -74,24 +75,34 @@ class Calibrator: msg = log.Event.from_bytes(calibration_params) rpy_init = np.array(msg.liveCalibration.rpyCalib) valid_blocks = msg.liveCalibration.validBlocks + wide_from_device_euler = np.array(msg.liveCalibration.wideFromDeviceEuler) except Exception: cloudlog.exception("Error reading cached CalibrationParams") - self.reset(rpy_init, valid_blocks) + self.reset(rpy_init, valid_blocks, wide_from_device_euler) self.update_status() - def reset(self, rpy_init: np.ndarray = RPY_INIT, valid_blocks: int = 0, smooth_from: Optional[np.ndarray] = None) -> None: + def reset(self, rpy_init: np.ndarray = RPY_INIT, + valid_blocks: int = 0, + wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT, + smooth_from: Optional[np.ndarray] = None) -> None: if not np.isfinite(rpy_init).all(): self.rpy = RPY_INIT.copy() else: self.rpy = rpy_init.copy() + if not np.isfinite(wide_from_device_euler_init).all() or len(wide_from_device_euler_init) != 3: + self.wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT.copy() + else: + self.wide_from_device_euler = wide_from_device_euler_init.copy() + if not np.isfinite(valid_blocks) or valid_blocks < 0: self.valid_blocks = 0 else: self.valid_blocks = valid_blocks self.rpys = np.tile(self.rpy, (INPUTS_WANTED, 1)) + self.wide_from_device_eulers = np.tile(self.wide_from_device_euler, (INPUTS_WANTED, 1)) self.idx = 0 self.block_idx = 0 @@ -113,6 +124,7 @@ class Calibrator: def update_status(self) -> None: valid_idxs = self.get_valid_idxs() if valid_idxs: + self.wide_from_device_euler = np.mean(self.wide_from_device_eulers[valid_idxs], axis=0) rpys = self.rpys[valid_idxs] self.rpy = np.mean(rpys, axis=0) max_rpy_calib = np.array(np.max(rpys, axis=0)) @@ -146,14 +158,14 @@ class Calibrator: else: return self.rpy - def handle_cam_odom(self, trans: List[float], rot: List[float], trans_std: List[float]) -> Optional[np.ndarray]: + def handle_cam_odom(self, trans: List[float], + rot: List[float], + wide_from_device_euler: List[float], + trans_std: List[float]) -> Optional[np.ndarray]: self.old_rpy_weight = min(0.0, self.old_rpy_weight - 1/SMOOTH_CYCLES) straight_and_fast = ((self.v_ego > MIN_SPEED_FILTER) and (trans[0] > MIN_SPEED_FILTER) and (abs(rot[2]) < MAX_YAW_RATE_FILTER)) - if self.wide_camera: - angle_std_threshold = 4*MAX_VEL_ANGLE_STD - else: - angle_std_threshold = MAX_VEL_ANGLE_STD + angle_std_threshold = MAX_VEL_ANGLE_STD certain_if_calib = ((np.arctan2(trans_std[1], trans[0]) < angle_std_threshold) or (self.valid_blocks < INPUTS_NEEDED)) if not (straight_and_fast and certain_if_calib): @@ -165,7 +177,14 @@ class Calibrator: new_rpy = euler_from_rot(rot_from_euler(self.get_smooth_rpy()).dot(rot_from_euler(observed_rpy))) new_rpy = sanity_clip(new_rpy) - self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] + (BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE) + if len(wide_from_device_euler) == 3: + new_wide_from_device_euler = np.array(wide_from_device_euler) + else: + new_wide_from_device_euler = WIDE_FROM_DEVICE_EULER_INIT + self.rpys[self.block_idx] = (self.idx*self.rpys[self.block_idx] + + (BLOCK_SIZE - self.idx) * new_rpy) / float(BLOCK_SIZE) + self.wide_from_device_eulers[self.block_idx] = (self.idx*self.wide_from_device_eulers[self.block_idx] + + (BLOCK_SIZE - self.idx) * new_wide_from_device_euler) / float(BLOCK_SIZE) self.idx = (self.idx + 1) % BLOCK_SIZE if self.idx == 0: self.block_idx += 1 @@ -187,6 +206,7 @@ class Calibrator: liveCalibration.calPerc = min(100 * (self.valid_blocks * BLOCK_SIZE + self.idx) // (INPUTS_NEEDED * BLOCK_SIZE), 100) liveCalibration.rpyCalib = smooth_rpy.tolist() liveCalibration.rpyCalibSpread = self.calib_spread.tolist() + liveCalibration.wideFromDeviceEuler = self.wide_from_device_euler.tolist() if self.not_car: liveCalibration.validBlocks = INPUTS_NEEDED @@ -223,6 +243,7 @@ def calibrationd_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[m calibrator.handle_v_ego(sm['carState'].vEgo) new_rpy = calibrator.handle_cam_odom(sm['cameraOdometry'].trans, sm['cameraOdometry'].rot, + sm['cameraOdometry'].wideFromDeviceEuler, sm['cameraOdometry'].transStd) if DEBUG and new_rpy is not None: diff --git a/selfdrive/locationd/laikad.py b/selfdrive/locationd/laikad.py index 2769f394c5..59176c586b 100755 --- a/selfdrive/locationd/laikad.py +++ b/selfdrive/locationd/laikad.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 -import json import math import os import time +import shutil from collections import defaultdict from concurrent.futures import Future, ProcessPoolExecutor from datetime import datetime from enum import IntEnum -from typing import List, Optional +from typing import List, Optional, Dict, Any import numpy as np @@ -16,30 +16,68 @@ from common.params import Params, put_nonblocking from laika import AstroDog from laika.constants import SECS_IN_HR, SECS_IN_MIN from laika.downloader import DownloadFailed -from laika.ephemeris import Ephemeris, EphemerisType, convert_ublox_ephem, parse_qcom_ephem +from laika.ephemeris import EphemerisType, GPSEphemeris, GLONASSEphemeris, ephemeris_structs, parse_qcom_ephem from laika.gps_time import GPSTime -from laika.helpers import ConstellationId +from laika.helpers import ConstellationId, get_sv_id from laika.raw_gnss import GNSSMeasurement, correct_measurements, process_measurements, read_raw_ublox, read_raw_qcom -from selfdrive.locationd.laikad_helpers import calc_pos_fix_gauss_newton, get_posfix_sympy_fun +from laika.opt import calc_pos_fix, get_posfix_sympy_fun, calc_vel_fix, get_velfix_sympy_func from selfdrive.locationd.models.constants import GENERATED_DIR, ObservationKind from selfdrive.locationd.models.gnss_kf import GNSSKalman from selfdrive.locationd.models.gnss_kf import States as GStates from system.swaglog import cloudlog MAX_TIME_GAP = 10 -EPHEMERIS_CACHE = 'LaikadEphemeris' +EPHEMERIS_CACHE = 'LaikadEphemerisV3' DOWNLOADS_CACHE_FOLDER = "/tmp/comma_download_cache/" -CACHE_VERSION = 0.1 +CACHE_VERSION = 0.2 POS_FIX_RESIDUAL_THRESHOLD = 100.0 +class LogEphemerisType(IntEnum): + nav = 0 + nasaUltraRapid = 1 + glonassIacUltraRapid = 2 + qcom = 3 + +class EphemerisSource(IntEnum): + gnssChip = 0 + internet = 1 + cache = 2 + unknown = 3 + +def get_log_eph_type(ephem): + if ephem.eph_type == EphemerisType.NAV: + source_type = LogEphemerisType.nav + elif ephem.eph_type == EphemerisType.QCOM_POLY: + source_type = LogEphemerisType.qcom + else: + assert ephem.file_epoch is not None + file_src = ephem.file_source + if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z' + source_type = LogEphemerisType.nasaUltraRapid + elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3' + source_type = LogEphemerisType.glonassIacUltraRapid + else: + raise Exception(f"Didn't expect file source {file_src}") + return source_type + +def get_log_eph_source(ephem): + if ephem.file_name == 'qcom' or ephem.file_name == 'ublox': + source = EphemerisSource.gnssChip + elif ephem.file_name == EPHEMERIS_CACHE: + source = EphemerisSource.cache + else: + source = EphemerisSource.internet + return source + + class Laikad: - def __init__(self, valid_const=("GPS", "GLONASS"), auto_fetch_orbits=True, auto_update=False, - valid_ephem_types=(EphemerisType.ULTRA_RAPID_ORBIT, EphemerisType.NAV), + def __init__(self, valid_const=(ConstellationId.GPS, ConstellationId.GLONASS), auto_fetch_navs=True, auto_update=False, + valid_ephem_types=(EphemerisType.NAV, EphemerisType.QCOM_POLY), save_ephemeris=False, use_qcom=False): """ valid_const: GNSS constellation which can be used - auto_fetch_orbits: If true fetch orbits from internet when needed + auto_fetch_navs: If true fetch navs from internet when needed auto_update: If true download AstroDog will download all files needed. This can be ephemeris or correction data like ionosphere. valid_ephem_types: Valid ephemeris types to be used by AstroDog save_ephemeris: If true saves and loads nav and orbit ephemeris to cache. @@ -47,71 +85,111 @@ class Laikad: self.astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, clear_old_ephemeris=True, cache_dir=DOWNLOADS_CACHE_FOLDER) self.gnss_kf = GNSSKalman(GENERATED_DIR, cython=True, erratic_clock=use_qcom) - self.auto_fetch_orbits = auto_fetch_orbits + self.auto_fetch_navs = auto_fetch_navs self.orbit_fetch_executor: Optional[ProcessPoolExecutor] = None self.orbit_fetch_future: Optional[Future] = None - - self.last_fetch_orbits_t = None self.got_first_gnss_msg = False - self.last_cached_t = None + + self.last_report_time = GPSTime(0, 0) + self.last_fetch_navs_t = GPSTime(0, 0) + self.last_cached_t = GPSTime(0, 0) self.save_ephemeris = save_ephemeris self.load_cache() self.posfix_functions = {constellation: get_posfix_sympy_fun(constellation) for constellation in (ConstellationId.GPS, ConstellationId.GLONASS)} - self.last_pos_fix = [] - self.last_pos_residual = [] - self.last_pos_fix_t = None + self.velfix_function = get_velfix_sympy_func() + self.last_fix_pos = None + self.last_fix_t = None self.gps_week = None self.use_qcom = use_qcom + self.first_log_time = None + self.ttff = -1 def load_cache(self): if not self.save_ephemeris: return - cache = Params().get(EPHEMERIS_CACHE) - if not cache: + cache_bytes = Params().get(EPHEMERIS_CACHE) + if not cache_bytes: return + nav_dict = {} try: - cache = json.loads(cache, object_hook=deserialize_hook) - self.astro_dog.add_orbits(cache['orbits']) - self.astro_dog.add_navs(cache['nav']) - self.last_fetch_orbits_t = cache['last_fetch_orbits_t'] - except json.decoder.JSONDecodeError: + ephem_cache = ephemeris_structs.EphemerisCache.from_bytes(cache_bytes) + glonass_navs = [GLONASSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.glonassEphemerides] + gps_navs = [GPSEphemeris(data_struct, file_name=EPHEMERIS_CACHE) for data_struct in ephem_cache.gpsEphemerides] + for e in sum([glonass_navs, gps_navs], []): + if e.prn not in nav_dict: + nav_dict[e.prn] = [] + nav_dict[e.prn].append(e) + self.astro_dog.add_navs(nav_dict) + except Exception: cloudlog.exception("Error parsing cache") - timestamp = self.last_fetch_orbits_t.as_datetime() if self.last_fetch_orbits_t is not None else 'Nan' cloudlog.debug( - f"Loaded nav ({sum([len(v) for v in cache['nav']])}) and orbits ({sum([len(v) for v in cache['orbits']])}) cache with timestamp: {timestamp}. Unique orbit and nav sats: {list(cache['orbits'].keys())} {list(cache['nav'].keys())} " + - f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in self.astro_dog.orbit_fetched_times._ranges]}") + f"Loaded navs ({sum([len(nav_dict[prn]) for prn in nav_dict.keys()])}). Unique orbit and nav sats: {list(nav_dict.keys())} ") - def cache_ephemeris(self, t: GPSTime): - if self.save_ephemeris and (self.last_cached_t is None or t - self.last_cached_t > SECS_IN_MIN): - put_nonblocking(EPHEMERIS_CACHE, json.dumps( - {'version': CACHE_VERSION, 'last_fetch_orbits_t': self.last_fetch_orbits_t, 'orbits': self.astro_dog.orbits, 'nav': self.astro_dog.nav}, - cls=CacheSerializer)) + def cache_ephemeris(self): + + if self.save_ephemeris and (self.last_report_time - self.last_cached_t > SECS_IN_MIN): + nav_list: List = sum([v for k,v in self.astro_dog.navs.items()], []) + ephem_cache = ephemeris_structs.EphemerisCache(**{'glonassEphemerides': [e.data for e in nav_list if e.prn[0]=='R'], + 'gpsEphemerides': [e.data for e in nav_list if e.prn[0]=='G']}) + + put_nonblocking(EPHEMERIS_CACHE, ephem_cache.to_bytes()) cloudlog.debug("Cache saved") - self.last_cached_t = t + self.last_cached_t = self.last_report_time - def get_est_pos(self, t, processed_measurements): - if self.last_pos_fix_t is None or abs(self.last_pos_fix_t - t) >= 2: - min_measurements = 6 if any(p.constellation_id == ConstellationId.GLONASS for p in processed_measurements) else 5 - pos_fix, pos_fix_residual = calc_pos_fix_gauss_newton(processed_measurements, self.posfix_functions, min_measurements=min_measurements) - if len(pos_fix) > 0: - self.last_pos_fix_t = t - residual_median = np.median(np.abs(pos_fix_residual)) - if np.median(np.abs(pos_fix_residual)) < POS_FIX_RESIDUAL_THRESHOLD: - cloudlog.debug(f"Pos fix is within threshold with median: {residual_median.round()}") - self.last_pos_fix = pos_fix[:3] - self.last_pos_residual = pos_fix_residual - else: - cloudlog.debug(f"Pos fix failed with median: {residual_median.round()}. All residuals: {np.round(pos_fix_residual)}") - return self.last_pos_fix + def create_ephem_statuses(self): + ephemeris_statuses = [] + prns_to_check = list(self.astro_dog.get_all_ephem_prns()) + prns_to_check.sort() + for prn in prns_to_check: + eph = self.astro_dog.get_eph(prn, self.last_report_time) + if eph is not None: + status = log.GnssMeasurements.EphemerisStatus.new_message() + status.constellationId = ConstellationId.from_rinex_char(prn[0]).value + status.svId = get_sv_id(prn) + status.type = get_log_eph_type(eph).value + status.source = get_log_eph_source(eph).value + ephemeris_statuses.append(status) + return ephemeris_statuses + + + def get_lsq_fix(self, t, measurements): + if self.last_fix_t is None or abs(self.last_fix_t - t) > 0: + min_measurements = 5 if any(p.constellation_id == ConstellationId.GLONASS for p in measurements) else 4 + position_solution, pr_residuals, pos_std = calc_pos_fix(measurements, self.posfix_functions, min_measurements=min_measurements) + if len(position_solution) < 3: + return None + position_estimate = position_solution[:3] + + position_std_residual = np.median(np.abs(pr_residuals)) + position_std = np.median(np.abs(pos_std))/10 + position_std = max(position_std_residual, position_std) * np.ones(3) + + velocity_solution, prr_residuals, vel_std = calc_vel_fix(measurements, position_estimate, self.velfix_function, min_measurements=min_measurements) + if len(velocity_solution) < 3: + return None + velocity_estimate = velocity_solution[:3] + + velocity_std_residual = np.median(np.abs(prr_residuals)) + velocity_std = np.median(np.abs(vel_std))/10 + velocity_std = max(velocity_std, velocity_std_residual) * np.ones(3) + + return position_estimate, position_std, velocity_estimate, velocity_std def is_good_report(self, gnss_msg): if gnss_msg.which() == 'drMeasurementReport' and self.use_qcom: constellation_id = ConstellationId.from_qcom_source(gnss_msg.drMeasurementReport.source) - # TODO support GLONASS - return constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] + # TODO: Understand and use remaining unknown constellations + try: + good_constellation = constellation_id in [ConstellationId.GPS, ConstellationId.SBAS] + except NotImplementedError: + good_constellation = False + # gpsWeek 65535 is received rarely from quectel, this cannot be + # passed to GnssMeasurements's gpsWeek (Int16) + good_week = not getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max + return good_constellation and good_week elif gnss_msg.which() == 'measurementReport' and not self.use_qcom: return True else: @@ -128,75 +206,103 @@ class Laikad: week = report.gpsWeek tow = report.rcvTow new_meas = read_raw_ublox(report) + self.last_report_time = GPSTime(week, tow) return week, tow, new_meas def is_ephemeris(self, gnss_msg): if self.use_qcom: return gnss_msg.which() == 'drSvPoly' else: - return gnss_msg.which() == 'ephemeris' + return gnss_msg.which() in ('ephemeris', 'glonassEphemeris') def read_ephemeris(self, gnss_msg): - # TODO this only works on GLONASS if self.use_qcom: # TODO this is not robust to gps week rollover if self.gps_week is None: return ephem = parse_qcom_ephem(gnss_msg.drSvPoly, self.gps_week) + self.astro_dog.add_qcom_polys({ephem.prn: [ephem]}) + else: - ephem = convert_ublox_ephem(gnss_msg.ephemeris) - self.astro_dog.add_navs({ephem.prn: [ephem]}) - self.cache_ephemeris(t=ephem.epoch) + if gnss_msg.which() == 'ephemeris': + data_struct = ephemeris_structs.Ephemeris.new_message(**gnss_msg.ephemeris.to_dict()) + ephem = GPSEphemeris(data_struct, file_name='ublox') + elif gnss_msg.which() == 'glonassEphemeris': + data_struct = ephemeris_structs.GlonassEphemeris.new_message(**gnss_msg.glonassEphemeris.to_dict()) + ephem = GLONASSEphemeris(data_struct, file_name='ublox') + else: + cloudlog.error(f"Unsupported ephemeris type: {gnss_msg.which()}") + return + self.astro_dog.add_navs({ephem.prn: [ephem]}) + self.cache_ephemeris() + + def process_report(self, new_meas, t): + # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites + new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] + processed_measurements = process_measurements(new_meas, self.astro_dog) + if self.last_fix_pos is not None: + est_pos = self.last_fix_pos + else: + est_pos = self.gnss_kf.x[GStates.ECEF_POS].tolist() + corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) + return corrected_measurements + + def calc_fix(self, t, measurements): + instant_fix = self.get_lsq_fix(t, measurements) + if instant_fix is None: + return None + else: + position_estimate, position_std, velocity_estimate, velocity_std = instant_fix + self.last_fix_t = t + self.last_fix_pos = position_estimate + self.lat_fix_pos_std = position_std + return position_estimate, position_std, velocity_estimate, velocity_std def process_gnss_msg(self, gnss_msg, gnss_mono_time: int, block=False): - if self.is_good_report(gnss_msg): + out_msg = messaging.new_message("gnssMeasurements") + t = gnss_mono_time * 1e-9 + msg_dict: Dict[str, Any] = {"measTime": gnss_mono_time} + if self.first_log_time is None: + self.first_log_time = 1e-9 * gnss_mono_time + if self.is_ephemeris(gnss_msg): + self.read_ephemeris(gnss_msg) + elif self.is_good_report(gnss_msg): week, tow, new_meas = self.read_report(gnss_msg) self.gps_week = week - - t = gnss_mono_time * 1e-9 if week > 0: self.got_first_gnss_msg = True latest_msg_t = GPSTime(week, tow) - if self.auto_fetch_orbits: - self.fetch_orbits(latest_msg_t, block) + if self.auto_fetch_navs: + self.fetch_navs(latest_msg_t, block) - # Filter measurements with unexpected pseudoranges for GPS and GLONASS satellites - new_meas = [m for m in new_meas if 1e7 < m.observables['C1C'] < 3e7] + corrected_measurements = self.process_report(new_meas, t) + msg_dict['correctedMeasurements'] = [create_measurement_msg(m) for m in corrected_measurements] - processed_measurements = process_measurements(new_meas, self.astro_dog) - est_pos = self.get_est_pos(t, processed_measurements) - - corrected_measurements = correct_measurements(processed_measurements, est_pos, self.astro_dog) if len(est_pos) > 0 else [] - if gnss_mono_time % 10 == 0: - cloudlog.debug(f"Measurements Incoming/Processed/Corrected: {len(new_meas), len(processed_measurements), len(corrected_measurements)}") - - self.update_localizer(est_pos, t, corrected_measurements) - kf_valid = all(self.kf_valid(t)) - ecef_pos = self.gnss_kf.x[GStates.ECEF_POS] - ecef_vel = self.gnss_kf.x[GStates.ECEF_VELOCITY] - - p = self.gnss_kf.P.diagonal() - pos_std = np.sqrt(p[GStates.ECEF_POS]) - vel_std = np.sqrt(p[GStates.ECEF_VELOCITY]) - - meas_msgs = [create_measurement_msg(m) for m in corrected_measurements] - dat = messaging.new_message("gnssMeasurements") + fix = self.calc_fix(t, corrected_measurements) measurement_msg = log.LiveLocationKalman.Measurement.new_message - dat.gnssMeasurements = { - "gpsWeek": week, - "gpsTimeOfWeek": tow, - "positionECEF": measurement_msg(value=ecef_pos.tolist(), std=pos_std.tolist(), valid=kf_valid), - "velocityECEF": measurement_msg(value=ecef_vel.tolist(), std=vel_std.tolist(), valid=kf_valid), - "positionFixECEF": measurement_msg(value=self.last_pos_fix, std=self.last_pos_residual, valid=self.last_pos_fix_t == t), - "ubloxMonoTime": gnss_mono_time, - "correctedMeasurements": meas_msgs - } - return dat - elif self.is_ephemeris(gnss_msg): - self.read_ephemeris(gnss_msg) + if fix is not None: + position_estimate, position_std, velocity_estimate, velocity_std = fix + if self.ttff <= 0: + self.ttff = max(1e-3, t - self.first_log_time) + msg_dict["positionECEF"] = measurement_msg(value=position_estimate, std=position_std.tolist(), valid=bool(self.last_fix_t == t)) + msg_dict["velocityECEF"] = measurement_msg(value=velocity_estimate, std=velocity_std.tolist(), valid=bool(self.last_fix_t == t)) - #elif gnss_msg.which() == 'ionoData': - # todo add this. Needed to better correct messages offline. First fix ublox_msg.cc to sent them. + self.update_localizer(self.last_fix_pos, t, corrected_measurements) + P_diag = self.gnss_kf.P.diagonal() + kf_valid = all(self.kf_valid(t)) + msg_dict["kalmanPositionECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_POS].tolist(), + std=np.sqrt(P_diag[GStates.ECEF_POS]).tolist(), + valid=kf_valid) + msg_dict["kalmanVelocityECEF"] = measurement_msg(value=self.gnss_kf.x[GStates.ECEF_VELOCITY].tolist(), + std=np.sqrt(P_diag[GStates.ECEF_VELOCITY]).tolist(), + valid=kf_valid) + + msg_dict['gpsWeek'] = self.last_report_time.week + msg_dict['gpsTimeOfWeek'] = self.last_report_time.tow + msg_dict['timeToFirstFix'] = self.ttff + msg_dict['ephemerisStatuses'] = self.create_ephem_statuses() + out_msg.gnssMeasurements = msg_dict + return out_msg def update_localizer(self, est_pos, t: float, measurements: List[GNSSMeasurement]): # Check time and outputs are valid @@ -208,7 +314,7 @@ class Laikad: cloudlog.error("Time gap of over 10s detected, gnss kalman reset") elif not valid[2]: cloudlog.error("Gnss kalman filter state is nan") - if len(est_pos) > 0: + if est_pos is not None and len(est_pos) > 0: cloudlog.info(f"Reset kalman filter with {est_pos}") self.init_gnss_localizer(est_pos) else: @@ -231,9 +337,9 @@ class Laikad: p_initial_diag[GStates.ECEF_POS] = 1000 ** 2 self.gnss_kf.init_state(x_initial, covs_diag=p_initial_diag) - def fetch_orbits(self, t: GPSTime, block): - # Download new orbits if 1 hour of orbits data left - if t + SECS_IN_HR not in self.astro_dog.orbit_fetched_times and (self.last_fetch_orbits_t is None or abs(t - self.last_fetch_orbits_t) > SECS_IN_MIN): + def fetch_navs(self, t: GPSTime, block): + # Download new navs if 1 hour of navs data left + if t + SECS_IN_HR not in self.astro_dog.navs_fetched_times and (abs(t - self.last_fetch_navs_t) > SECS_IN_MIN): astro_dog_vars = self.astro_dog.valid_const, self.astro_dog.auto_update, self.astro_dog.valid_ephem_types, self.astro_dog.cache_dir ret = None @@ -248,22 +354,22 @@ class Laikad: if ret is not None: if ret[0] is None: - self.last_fetch_orbits_t = ret[2] + self.last_fetch_navs_t = ret[2] else: - self.astro_dog.orbits, self.astro_dog.orbit_fetched_times, self.last_fetch_orbits_t = ret - self.cache_ephemeris(t=t) + self.astro_dog.navs, self.astro_dog.navs_fetched_times, self.last_fetch_navs_t = ret + self.cache_ephemeris() def get_orbit_data(t: GPSTime, valid_const, auto_update, valid_ephem_types, cache_dir): astro_dog = AstroDog(valid_const=valid_const, auto_update=auto_update, valid_ephem_types=valid_ephem_types, cache_dir=cache_dir) - cloudlog.info(f"Start to download/parse orbits for time {t.as_datetime()}") + cloudlog.info(f"Start to download/parse navs for time {t.as_datetime()}") start_time = time.monotonic() try: - astro_dog.get_orbit_data(t, only_predictions=True) - cloudlog.info(f"Done parsing orbits. Took {time.monotonic() - start_time:.1f}s") - cloudlog.debug(f"Downloaded orbits ({sum([len(v) for v in astro_dog.orbits])}): {list(astro_dog.orbits.keys())}" + + astro_dog.get_navs(t) + cloudlog.info(f"Done parsing navs. Took {time.monotonic() - start_time:.1f}s") + cloudlog.debug(f"Downloaded navs ({sum([len(v) for v in astro_dog.navs])}): {list(astro_dog.navs.keys())}" + f"With time range: {[f'{start.as_datetime()}, {end.as_datetime()}' for (start,end) in astro_dog.orbit_fetched_times._ranges]}") - return astro_dog.orbits, astro_dog.orbit_fetched_times, t + return astro_dog.navs, astro_dog.navs_fetched_times, t except (DownloadFailed, RuntimeError, ValueError, IOError) as e: cloudlog.warning(f"No orbit data found or parsing failure: {e}") return None, None, t @@ -281,31 +387,8 @@ def create_measurement_msg(meas: GNSSMeasurement): c.satPos = meas.sat_pos_final.tolist() c.satVel = meas.sat_vel.tolist() c.satVel = meas.sat_vel.tolist() - ephem = meas.sat_ephemeris - assert ephem is not None - week, time_of_week = -1, -1 - if ephem.eph_type == EphemerisType.NAV: - source_type = EphemerisSourceType.nav - elif ephem.eph_type == EphemerisType.QCOM_POLY: - source_type = EphemerisSourceType.qcom - else: - assert ephem.file_epoch is not None - week = ephem.file_epoch.week - time_of_week = ephem.file_epoch.tow - file_src = ephem.file_source - if file_src == 'igu': # example nasa: '2214/igu22144_00.sp3.Z' - source_type = EphemerisSourceType.nasaUltraRapid - elif file_src == 'Sta': # example nasa: '22166/ultra/Stark_1D_22061518.sp3' - source_type = EphemerisSourceType.glonassIacUltraRapid - else: - raise Exception(f"Didn't expect file source {file_src}") - - c.ephemerisSource.type = source_type.value - c.ephemerisSource.gpsWeek = week - c.ephemerisSource.gpsTimeOfWeek = int(time_of_week) return c - def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMeasurement]): ekf_data = defaultdict(list) for m in measurements: @@ -321,73 +404,47 @@ def kf_add_observations(gnss_kf: GNSSKalman, t: float, measurements: List[GNSSMe gnss_kf.predict_and_observe(t, kind, data) -class CacheSerializer(json.JSONEncoder): - - def default(self, o): - if isinstance(o, Ephemeris): - return o.to_json() - if isinstance(o, GPSTime): - return o.__dict__ - if isinstance(o, np.ndarray): - return o.tolist() - return json.JSONEncoder.default(self, o) - - -def deserialize_hook(dct): - if 'ephemeris' in dct: - return Ephemeris.from_json(dct) - if 'week' in dct: - return GPSTime(dct['week'], dct['tow']) - return dct - - -class EphemerisSourceType(IntEnum): - nav = 0 - nasaUltraRapid = 1 - glonassIacUltraRapid = 2 - qcom = 3 +def clear_tmp_cache(): + if os.path.exists(DOWNLOADS_CACHE_FOLDER): + shutil.rmtree(DOWNLOADS_CACHE_FOLDER) + os.mkdir(DOWNLOADS_CACHE_FOLDER) def main(sm=None, pm=None): + #clear_tmp_cache() + use_qcom = not Params().get_bool("UbloxAvailable", block=True) if use_qcom: - raw_gnss_socket = "qcomGnss" + raw_name = "qcomGnss" else: - raw_gnss_socket = "ubloxGnss" + raw_name = "ubloxGnss" + raw_gnss_sock = messaging.sub_sock(raw_name, conflate=False, timeout=1000) if sm is None: - sm = messaging.SubMaster([raw_gnss_socket, 'clocks']) + sm = messaging.SubMaster(['clocks',]) if pm is None: pm = messaging.PubMaster(['gnssMeasurements']) + # disable until set as main gps source, to better analyze startup time + use_internet = False # "LAIKAD_NO_INTERNET" not in os.environ + replay = "REPLAY" in os.environ - use_internet = "LAIKAD_NO_INTERNET" not in os.environ - laikad = Laikad(save_ephemeris=not replay, auto_fetch_orbits=use_internet, use_qcom=use_qcom) + if replay or "CI" in os.environ: + use_internet = True + + laikad = Laikad(save_ephemeris=not replay, auto_fetch_navs=use_internet, use_qcom=use_qcom) while True: - sm.update() + for in_msg in messaging.drain_sock(raw_gnss_sock): + out_msg = laikad.process_gnss_msg(getattr(in_msg, raw_name), in_msg.logMonoTime, replay) + pm.send('gnssMeasurements', out_msg) - if sm.updated[raw_gnss_socket]: - gnss_msg = sm[raw_gnss_socket] - - # TODO: Understand and use remaining unknown constellations - if gnss_msg.which() == "drMeasurementReport": - if getattr(gnss_msg, gnss_msg.which()).source not in ['glonass', 'gps', 'beidou', 'sbas']: - continue - - if getattr(gnss_msg, gnss_msg.which()).gpsWeek > np.iinfo(np.int16).max: - # gpsWeek 65535 is received rarely from quectel, this cannot be - # passed to GnssMeasurements's gpsWeek (Int16) - continue - - msg = laikad.process_gnss_msg(gnss_msg, sm.logMonoTime[raw_gnss_socket], block=replay) - if msg is not None: - pm.send('gnssMeasurements', msg) + sm.update(0) if not laikad.got_first_gnss_msg and sm.updated['clocks']: clocks_msg = sm['clocks'] t = GPSTime.from_datetime(datetime.utcfromtimestamp(clocks_msg.wallTimeNanos * 1E-9)) - if laikad.auto_fetch_orbits: - laikad.fetch_orbits(t, block=replay) + if laikad.auto_fetch_navs: + laikad.fetch_navs(t, block=replay) if __name__ == "__main__": diff --git a/selfdrive/locationd/laikad_helpers.py b/selfdrive/locationd/laikad_helpers.py deleted file mode 100644 index f13e8e73bb..0000000000 --- a/selfdrive/locationd/laikad_helpers.py +++ /dev/null @@ -1,89 +0,0 @@ -import numpy as np -import sympy - -from laika.constants import EARTH_ROTATION_RATE, SPEED_OF_LIGHT -from laika.helpers import ConstellationId - - -def calc_pos_fix_gauss_newton(measurements, posfix_functions, x0=None, signal='C1C', min_measurements=6): - ''' - Calculates gps fix using gauss newton method - To solve the problem a minimal of 4 measurements are required. - If Glonass is included 5 are required to solve for the additional free variable. - returns: - 0 -> list with positions - ''' - if x0 is None: - x0 = [0, 0, 0, 0, 0] - n = len(measurements) - if n < min_measurements: - return [], [] - - Fx_pos = pr_residual(measurements, posfix_functions, signal=signal) - x = gauss_newton(Fx_pos, x0) - residual, _ = Fx_pos(x, weight=1.0) - return x.tolist(), residual.tolist() - - -def pr_residual(measurements, posfix_functions, signal='C1C'): - def Fx_pos(inp, weight=None): - vals, gradients = [], [] - - for meas in measurements: - pr = meas.observables[signal] - pr += meas.sat_clock_err * SPEED_OF_LIGHT - - w = (1 / meas.observables_std[signal]) if weight is None else weight - - val, *gradient = posfix_functions[meas.constellation_id](*inp, pr, *meas.sat_pos, w) - vals.append(val) - gradients.append(gradient) - return np.asarray(vals), np.asarray(gradients) - - return Fx_pos - - -def gauss_newton(fun, b, xtol=1e-8, max_n=25): - for _ in range(max_n): - # Compute function and jacobian on current estimate - r, J = fun(b) - - # Update estimate - delta = np.linalg.pinv(J) @ r - b -= delta - - # Check step size for stopping condition - if np.linalg.norm(delta) < xtol: - break - return b - - -def get_posfix_sympy_fun(constellation): - # Unknowns - x, y, z = sympy.Symbol('x'), sympy.Symbol('y'), sympy.Symbol('z') - bc = sympy.Symbol('bc') - bg = sympy.Symbol('bg') - var = [x, y, z, bc, bg] - - # Knowns - pr = sympy.Symbol('pr') - sat_x, sat_y, sat_z = sympy.Symbol('sat_x'), sympy.Symbol('sat_y'), sympy.Symbol('sat_z') - weight = sympy.Symbol('weight') - - theta = EARTH_ROTATION_RATE * (pr - bc) / SPEED_OF_LIGHT - val = sympy.sqrt( - (sat_x * sympy.cos(theta) + sat_y * sympy.sin(theta) - x) ** 2 + - (sat_y * sympy.cos(theta) - sat_x * sympy.sin(theta) - y) ** 2 + - (sat_z - z) ** 2 - ) - - if constellation == ConstellationId.GLONASS: - res = weight * (val - (pr - bc - bg)) - elif constellation == ConstellationId.GPS: - res = weight * (val - (pr - bc)) - else: - raise NotImplementedError(f"Constellation {constellation} not supported") - - res = [res] + [sympy.diff(res, v) for v in var] - - return sympy.lambdify([x, y, z, bc, bg, pr, sat_x, sat_y, sat_z, weight], res, modules=["numpy"]) diff --git a/selfdrive/locationd/liblocationd.cc b/selfdrive/locationd/liblocationd.cc index 49404668a4..32fec7724a 100755 --- a/selfdrive/locationd/liblocationd.cc +++ b/selfdrive/locationd/liblocationd.cc @@ -3,8 +3,8 @@ extern "C" { typedef Localizer* Localizer_t; - Localizer *localizer_init() { - return new Localizer(); + Localizer *localizer_init(bool has_ublox) { + return new Localizer(has_ublox); } void localizer_get_message_bytes(Localizer *localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, @@ -26,4 +26,16 @@ extern "C" { memcpy(std_buff, stdev.data(), sizeof(double) * stdev.size()); } + bool is_gps_ok(Localizer *localizer){ + return localizer->is_gps_ok(); + } + + bool are_inputs_ok(Localizer *localizer){ + return localizer->are_inputs_ok(); + } + + void observation_timings_invalid_reset(Localizer *localizer){ + localizer->observation_timings_invalid_reset(); + } + } diff --git a/selfdrive/locationd/locationd.cc b/selfdrive/locationd/locationd.cc index e156af5d64..307626506a 100755 --- a/selfdrive/locationd/locationd.cc +++ b/selfdrive/locationd/locationd.cc @@ -19,11 +19,22 @@ const double VALID_TIME_SINCE_RESET = 1.0; // s const double VALID_POS_STD = 50.0; // m const double MAX_RESET_TRACKER = 5.0; const double SANE_GPS_UNCERTAINTY = 1500.0; // m +const double INPUT_INVALID_THRESHOLD = 5.0; // same as reset tracker +const double DECAY = 0.99995; // same as reset tracker +const double MAX_FILTER_REWIND_TIME = 0.8; // s // TODO: GPS sensor time offsets are empirically calculated // They should be replaced with synced time from a real clock -const double GPS_LOCATION_SENSOR_TIME_OFFSET = 0.630; // s -const double GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET = 0.095; // s +const double GPS_QUECTEL_SENSOR_TIME_OFFSET = 0.630; // s +const double GPS_UBLOX_SENSOR_TIME_OFFSET = 0.095; // s +const float GPS_MUL_FACTOR = 10.0; +const float GPS_POS_STD_THRESHOLD = 50.0; +const float GPS_VEL_STD_THRESHOLD = 5.0; +const float GPS_POS_ERROR_RESET_THRESHOLD = 300.0; +const float GPS_POS_STD_RESET_THRESHOLD = 2.0; +const float GPS_VEL_STD_RESET_THRESHOLD = 0.5; +const float GPS_ORIENTATION_ERROR_RESET_THRESHOLD = 1.0; +const int GPS_ORIENTATION_ERROR_RESET_CNT = 3; static VectorXd floatlist2vector(const capnp::List::Reader& floatlist) { VectorXd res(floatlist.size()); @@ -74,6 +85,8 @@ Localizer::Localizer() { this->converter = std::make_unique((ECEF) { .x = ecef_pos[0], .y = ecef_pos[1], .z = ecef_pos[2] }); } +Localizer::Localizer(bool has_ublox) : Localizer() { ublox_available = has_ublox; } + void Localizer::build_live_location(cereal::LiveLocationKalman::Builder& fix) { VectorXd predicted_state = this->kf->get_x(); MatrixXdr predicted_cov = this->kf->get_P(); @@ -200,6 +213,14 @@ VectorXd Localizer::get_stdev() { return this->kf->get_P().diagonal().array().sqrt(); } +bool Localizer::are_inputs_ok() { + return this->critical_services_valid(this->observation_values_invalid) && !this->observation_timings_invalid; +} + +void Localizer::observation_timings_invalid_reset(){ + this->observation_timings_invalid = false; +} + void Localizer::handle_sensor(double current_time, const cereal::SensorEventData::Reader& log) { // TODO does not yet account for double sensor readings in the log @@ -213,6 +234,11 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData // sensor time and log time should be close if (std::abs(current_time - sensor_time) > 0.1) { LOGE("Sensor reading ignored, sensor timestamp more than 100ms off from log time"); + this->observation_timings_invalid = true; + return; + } + else if (!this->is_timestamp_valid(sensor_time)) { + this->observation_timings_invalid = true; return; } @@ -227,6 +253,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ROTATION_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_GYRO, { meas }); + this->observation_values_invalid["gyroscope"] *= DECAY; + } + else{ + this->observation_values_invalid["gyroscope"] += 1.0; } } @@ -242,6 +272,10 @@ void Localizer::handle_sensor(double current_time, const cereal::SensorEventData auto meas = Vector3d(-v[2], -v[1], -v[0]); if (meas.norm() < ACCEL_SANITY_CHECK) { this->kf->predict_and_observe(sensor_time, OBSERVATION_PHONE_ACCEL, { meas }); + this->observation_values_invalid["accelerometer"] *= DECAY; + } + else{ + this->observation_values_invalid["accelerometer"] += 1.0; } } } @@ -270,23 +304,30 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R bool gps_lat_lng_alt_insane = ((std::abs(log.getLatitude()) > 90) || (std::abs(log.getLongitude()) > 180) || (std::abs(log.getAltitude()) > ALTITUDE_SANITY_CHECK)); bool gps_vel_insane = (floatlist2vector(log.getVNED()).norm() > TRANS_SANITY_CHECK); - if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane) { + // quectel gps verticalAccuracy is clipped to 500 + bool gps_accuracy_insane_quectel = false; + if (!ublox_available) { + gps_accuracy_insane_quectel = log.getVerticalAccuracy() == 500; + } + + if (gps_invalid_flag || gps_unreasonable || gps_accuracy_insane || gps_lat_lng_alt_insane || gps_vel_insane || gps_accuracy_insane_quectel) { + //this->gps_valid = false; this->determine_gps_mode(current_time); return; } - + double sensor_time = current_time - sensor_time_offset; // Process message - this->last_gps_fix = sensor_time; + //this->gps_valid = true; this->gps_mode = true; Geodetic geodetic = { log.getLatitude(), log.getLongitude(), log.getAltitude() }; this->converter = std::make_unique(geodetic); VectorXd ecef_pos = this->converter->ned2ecef({ 0.0, 0.0, 0.0 }).to_vector(); VectorXd ecef_vel = this->converter->ned2ecef({ log.getVNED()[0], log.getVNED()[1], log.getVNED()[2] }).to_vector() - ecef_pos; - MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(10.0 * log.getAccuracy(),2) + std::pow(10.0 * log.getVerticalAccuracy(),2)).asDiagonal(); - MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(log.getSpeedAccuracy() * 10.0, 2)).asDiagonal(); + MatrixXdr ecef_pos_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getAccuracy(),2) + std::pow(GPS_MUL_FACTOR * log.getVerticalAccuracy(),2)).asDiagonal(); + MatrixXdr ecef_vel_R = Vector3d::Constant(std::pow(GPS_MUL_FACTOR * log.getSpeedAccuracy(), 2)).asDiagonal(); this->unix_timestamp_millis = log.getUnixTimestampMillis(); double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); @@ -313,13 +354,96 @@ void Localizer::handle_gps(double current_time, const cereal::GpsLocationData::R this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); } + this->last_gps_msg = sensor_time; + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); +} + +void Localizer::handle_gnss(double current_time, const cereal::GnssMeasurements::Reader& log) { + + if(!log.getPositionECEF().getValid() || !log.getVelocityECEF().getValid()) { + this->determine_gps_mode(current_time); + return; + } + + double sensor_time = log.getMeasTime() * 1e-9; + if (ublox_available) + sensor_time -= GPS_UBLOX_SENSOR_TIME_OFFSET; + else + sensor_time -= GPS_QUECTEL_SENSOR_TIME_OFFSET; + + auto ecef_pos_v = log.getPositionECEF().getValue(); + VectorXd ecef_pos = Vector3d(ecef_pos_v[0], ecef_pos_v[1], ecef_pos_v[2]); + + // indexed at 0 cause all std values are the same MAE + auto ecef_pos_std = log.getPositionECEF().getStd()[0]; + MatrixXdr ecef_pos_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_pos_std, 2)).asDiagonal(); + + auto ecef_vel_v = log.getVelocityECEF().getValue(); + VectorXd ecef_vel = Vector3d(ecef_vel_v[0], ecef_vel_v[1], ecef_vel_v[2]); + + // indexed at 0 cause all std values are the same MAE + auto ecef_vel_std = log.getVelocityECEF().getStd()[0]; + MatrixXdr ecef_vel_R = Vector3d::Constant(pow(GPS_MUL_FACTOR*ecef_vel_std, 2)).asDiagonal(); + + double gps_est_error = (this->kf->get_x().segment(STATE_ECEF_POS_START) - ecef_pos).norm(); + + VectorXd orientation_ecef = quat2euler(vector2quat(this->kf->get_x().segment(STATE_ECEF_ORIENTATION_START))); + VectorXd orientation_ned = ned_euler_from_ecef({ ecef_pos[0], ecef_pos[1], ecef_pos[2] }, orientation_ecef); + + LocalCoord convs((ECEF){ .x = ecef_pos[0], .y = ecef_pos[1], .z = ecef_pos[2] }); + ECEF next_ecef = {.x = ecef_pos[0] + ecef_vel[0], .y = ecef_pos[1] + ecef_vel[1], .z = ecef_pos[2] + ecef_vel[2]}; + VectorXd ned_vel = convs.ecef2ned(next_ecef).to_vector(); + double bearing_rad = atan2(ned_vel[1], ned_vel[0]); + + VectorXd orientation_ned_gps = Vector3d(0.0, 0.0, bearing_rad); + VectorXd orientation_error = (orientation_ned - orientation_ned_gps).array() - M_PI; + for (int i = 0; i < orientation_error.size(); i++) { + orientation_error(i) = std::fmod(orientation_error(i), 2.0 * M_PI); + if (orientation_error(i) < 0.0) { + orientation_error(i) += 2.0 * M_PI; + } + orientation_error(i) -= M_PI; + } + VectorXd initial_pose_ecef_quat = quat2vector(euler2quat(ecef_euler_from_ned({ ecef_pos(0), ecef_pos(1), ecef_pos(2) }, orientation_ned_gps))); + + if (ecef_pos_std > GPS_POS_STD_THRESHOLD || ecef_vel_std > GPS_VEL_STD_THRESHOLD) { + this->determine_gps_mode(current_time); + return; + } + + // prevent jumping gnss measurements (covered lots, standstill...) + bool orientation_reset = ecef_vel_std < GPS_VEL_STD_RESET_THRESHOLD; + orientation_reset &= orientation_error.norm() > GPS_ORIENTATION_ERROR_RESET_THRESHOLD; + orientation_reset &= !this->standstill; + if (orientation_reset) { + this->orientation_reset_count++; + } + else { + this->orientation_reset_count = 0; + } + + if ((gps_est_error > GPS_POS_ERROR_RESET_THRESHOLD && ecef_pos_std < GPS_POS_STD_RESET_THRESHOLD) || this->last_gps_msg == 0) { + // always reset on first gps message and if the location is off but the accuracy is high + LOGE("Locationd vs gnssMeasurement position difference too large, kalman reset"); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); + } else if (orientation_reset_count > GPS_ORIENTATION_ERROR_RESET_CNT) { + LOGE("Locationd vs gnssMeasurement orientation difference too large, kalman reset"); + this->reset_kalman(NAN, initial_pose_ecef_quat, ecef_pos, ecef_vel, ecef_pos_R, ecef_vel_R); + this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_ORIENTATION_FROM_GPS, { initial_pose_ecef_quat }); + this->orientation_reset_count = 0; + } + + this->gps_mode = true; + this->last_gps_msg = sensor_time; this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_POS, { ecef_pos }, { ecef_pos_R }); this->kf->predict_and_observe(sensor_time, OBSERVATION_ECEF_VEL, { ecef_vel }, { ecef_vel_R }); } void Localizer::handle_car_state(double current_time, const cereal::CarState::Reader& log) { this->car_speed = std::abs(log.getVEgo()); - if (log.getStandstill()) { + this->standstill = log.getStandstill(); + if (this->standstill) { this->kf->predict_and_observe(current_time, OBSERVATION_NO_ROT, { Vector3d(0.0, 0.0, 0.0) }); this->kf->predict_and_observe(current_time, OBSERVATION_NO_ACCEL, { Vector3d(0.0, 0.0, 0.0) }); } @@ -329,7 +453,13 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry VectorXd rot_device = this->device_from_calib * floatlist2vector(log.getRot()); VectorXd trans_device = this->device_from_calib * floatlist2vector(log.getTrans()); + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } + if ((rot_device.norm() > ROTATION_SANITY_CHECK) || (trans_device.norm() > TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -337,10 +467,12 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry VectorXd trans_calib_std = floatlist2vector(log.getTransStd()); if ((rot_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK) || (trans_calib_std.minCoeff() <= MIN_STD_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } if ((rot_calib_std.norm() > 10 * ROTATION_SANITY_CHECK) || (trans_calib_std.norm() > 10 * TRANS_SANITY_CHECK)) { + this->observation_values_invalid["cameraOdometry"] += 1.0; return; } @@ -356,12 +488,19 @@ void Localizer::handle_cam_odo(double current_time, const cereal::CameraOdometry { rot_device }, { rot_device_cov }); this->kf->predict_and_observe(current_time, OBSERVATION_CAMERA_ODO_TRANSLATION, { trans_device }, { trans_device_cov }); + this->observation_values_invalid["cameraOdometry"] *= DECAY; } void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log) { + if (!this->is_timestamp_valid(current_time)) { + this->observation_timings_invalid = true; + return; + } + if (log.getRpyCalib().size() > 0) { auto live_calib = floatlist2vector(log.getRpyCalib()); if ((live_calib.minCoeff() < -CALIB_RPY_SANITY_CHECK) || (live_calib.maxCoeff() > CALIB_RPY_SANITY_CHECK)) { + this->observation_values_invalid["liveCalibration"] += 1.0; return; } @@ -369,6 +508,7 @@ void Localizer::handle_live_calib(double current_time, const cereal::LiveCalibra this->device_from_calib = euler2rot(this->calib); this->calib_from_device = this->device_from_calib.transpose(); this->calibrated = log.getCalStatus() == 1; + this->observation_values_invalid["liveCalibration"] *= DECAY; } } @@ -400,8 +540,8 @@ void Localizer::time_check(double current_time) { void Localizer::update_reset_tracker() { // reset tracker is tuned to trigger when over 1reset/10s over 2min period - if (this->isGpsOK()) { - this->reset_tracker *= .99995; + if (this->is_gps_ok()) { + this->reset_tracker *= DECAY; } else { this->reset_tracker = 0.0; } @@ -450,9 +590,11 @@ void Localizer::handle_msg(const cereal::Event::Reader& log) { } else if (log.isGyroscope()) { this->handle_sensor(t, log.getGyroscope()); } else if (log.isGpsLocation()) { - this->handle_gps(t, log.getGpsLocation(), GPS_LOCATION_SENSOR_TIME_OFFSET); + this->handle_gps(t, log.getGpsLocation(), GPS_QUECTEL_SENSOR_TIME_OFFSET); } else if (log.isGpsLocationExternal()) { - this->handle_gps(t, log.getGpsLocationExternal(), GPS_LOCATION_EXTERNAL_SENSOR_TIME_OFFSET); + this->handle_gps(t, log.getGpsLocationExternal(), GPS_UBLOX_SENSOR_TIME_OFFSET); + //} else if (log.isGnssMeasurements()) { + // this->handle_gnss(t, log.getGnssMeasurements()); } else if (log.isCarState()) { this->handle_car_state(t, log.getCarState()); } else if (log.isCameraOdometry()) { @@ -476,9 +618,26 @@ kj::ArrayPtr Localizer::get_message_bytes(MessageBuilder& msg_build return msg_builder.toBytes(); } +bool Localizer::is_gps_ok() { + return (this->kf->get_filter_time() - this->last_gps_msg) < 2.0; +} -bool Localizer::isGpsOK() { - return this->kf->get_filter_time() - this->last_gps_fix < 1.0; +bool Localizer::critical_services_valid(std::map critical_services) { + for (auto &kv : critical_services){ + if (kv.second >= INPUT_INVALID_THRESHOLD){ + return false; + } + } + return true; +} + +bool Localizer::is_timestamp_valid(double current_time) { + double filter_time = this->kf->get_filter_time(); + if (!std::isnan(filter_time) && ((filter_time - current_time) > MAX_FILTER_REWIND_TIME)) { + LOGE("Observation timestamp is older than the max rewind threshold of the filter"); + return false; + } + return true; } void Localizer::determine_gps_mode(double current_time) { @@ -498,25 +657,31 @@ void Localizer::determine_gps_mode(double current_time) { } int Localizer::locationd_thread() { + ublox_available = Params().getBool("UbloxAvailable", true); const char* gps_location_socket; - if (Params().getBool("UbloxAvailable", true)) { + if (ublox_available) { gps_location_socket = "gpsLocationExternal"; } else { gps_location_socket = "gpsLocation"; } - const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", + const std::initializer_list service_list = {gps_location_socket, "cameraOdometry", "liveCalibration", "carState", "carParams", "accelerometer", "gyroscope"}; - PubMaster pm({"liveLocationKalman"}); // TODO: remove carParams once we're always sending at 100Hz SubMaster sm(service_list, {}, nullptr, {gps_location_socket, "carParams"}); + PubMaster pm({"liveLocationKalman"}); uint64_t cnt = 0; bool filterInitialized = false; + const std::vector critical_input_services = {"cameraOdometry", "liveCalibration", "accelerometer", "gyroscope"}; + for (std::string service : critical_input_services) { + this->observation_values_invalid.insert({service, 0.0}); + } while (!do_exit) { sm.update(); if (filterInitialized){ + this->observation_timings_invalid_reset(); for (const char* service : service_list) { if (sm.updated(service) && sm.valid(service)){ const cereal::Event::Reader log = sm[service]; @@ -530,8 +695,8 @@ int Localizer::locationd_thread() { // 100Hz publish for notcars, 20Hz for cars const char* trigger_msg = sm["carParams"].getCarParams().getNotCar() ? "accelerometer" : "cameraOdometry"; if (sm.updated(trigger_msg)) { - bool inputsOK = sm.allAliveAndValid(); - bool gpsOK = this->isGpsOK(); + bool inputsOK = sm.allAliveAndValid() && this->are_inputs_ok(); + bool gpsOK = this->is_gps_ok(); bool sensorsOK = sm.allAliveAndValid({"accelerometer", "gyroscope"}); MessageBuilder msg_builder; diff --git a/selfdrive/locationd/locationd.h b/selfdrive/locationd/locationd.h index 280296b06c..b7e42a9df8 100755 --- a/selfdrive/locationd/locationd.h +++ b/selfdrive/locationd/locationd.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "cereal/messaging/messaging.h" @@ -13,7 +14,7 @@ #include "common/timing.h" #include "common/util.h" -#include "selfdrive/sensord/sensors/constants.h" +#include "system/sensord/sensors/constants.h" #define VISION_DECIMATION 2 #define SENSOR_DECIMATION 10 #include "selfdrive/locationd/models/live_kf.h" @@ -23,6 +24,7 @@ class Localizer { public: Localizer(); + Localizer(bool has_ublox); int locationd_thread(); @@ -32,8 +34,12 @@ public: void finite_check(double current_time = NAN); void time_check(double current_time = NAN); void update_reset_tracker(); - bool isGpsOK(); + bool is_gps_ok(); + bool critical_services_valid(std::map critical_services); + bool is_timestamp_valid(double current_time); void determine_gps_mode(double current_time); + bool are_inputs_ok(); + void observation_timings_invalid_reset(); kj::ArrayPtr get_message_bytes(MessageBuilder& msg_builder, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid); @@ -47,6 +53,7 @@ public: void handle_msg(const cereal::Event::Reader& log); void handle_sensor(double current_time, const cereal::SensorEventData::Reader& log); void handle_gps(double current_time, const cereal::GpsLocationData::Reader& log, const double sensor_time_offset); + void handle_gnss(double current_time, const cereal::GnssMeasurements::Reader& log); void handle_car_state(double current_time, const cereal::CarState::Reader& log); void handle_cam_odo(double current_time, const cereal::CameraOdometry::Reader& log); void handle_live_calib(double current_time, const cereal::LiveCalibrationData::Reader& log); @@ -68,8 +75,13 @@ private: std::unique_ptr converter; int64_t unix_timestamp_millis = 0; - double last_gps_fix = 0; double reset_tracker = 0.0; bool device_fell = false; bool gps_mode = false; + double last_gps_msg = 0; + bool ublox_available = true; + bool observation_timings_invalid = false; + std::map observation_values_invalid; + bool standstill = true; + int32_t orientation_reset_count = 0; }; diff --git a/selfdrive/locationd/models/lane_kf.py b/selfdrive/locationd/models/lane_kf.py new file mode 100755 index 0000000000..4d38fa8e09 --- /dev/null +++ b/selfdrive/locationd/models/lane_kf.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import sys +import numpy as np +import sympy as sp + +from selfdrive.locationd.models.constants import ObservationKind +from rednose.helpers.ekf_sym import gen_code, EKF_sym + + +class LaneKalman(): + name = 'lane' + + @staticmethod + def generate_code(generated_dir): + # make functions and jacobians with sympy + # state variables + dim = 6 + state = sp.MatrixSymbol('state', dim, 1) + + dd = sp.Symbol('dd') # WARNING: NOT TIME + + # Time derivative of the state as a function of state + state_dot = sp.Matrix(np.zeros((dim, 1))) + state_dot[:3,0] = sp.Matrix(state[3:6,0]) + + # Basic descretization, 1st order intergrator + # Can be pretty bad if dt is big + f_sym = sp.Matrix(state) + dd*state_dot + + # + # Observation functions + # + h_lane_sym = sp.Matrix(state[:3,0]) + obs_eqs = [[h_lane_sym, ObservationKind.LANE_PT, None]] + gen_code(generated_dir, LaneKalman.name, f_sym, dd, state, obs_eqs, dim, dim) + + def __init__(self, generated_dir, pt_std=5): + # state + # left and right lane centers in ecef + # WARNING: this is not a temporal model + # the 'time' in this kalman filter is + # the distance traveled by the vehicle, + # which should approximately be the + # distance along the lane path + # a more logical parametrization + # states 0-2 are ecef coordinates distance d + # states 3-5 is the 3d "velocity" of the + # lane in ecef (m/m). + x_initial = np.array([0,0,0, + 0,0,0]) + + # state covariance + P_initial = np.diag([1e16, 1e16, 1e16, + 1**2, 1**2, 1**2]) + + # process noise + Q = np.diag([0.1**2, 0.1**2, 0.1**2, + 0.1**2, 0.1**2, 0.1*2]) + + self.dim_state = len(x_initial) + + # init filter + self.filter = EKF_sym(generated_dir, self.name, Q, x_initial, P_initial, x_initial.shape[0], P_initial.shape[0]) + self.obs_noise = {ObservationKind.LANE_PT: np.diag([pt_std**2]*3)} + + @property + def x(self): + return self.filter.state() + + @property + def P(self): + return self.filter.covs() + + def predict(self, t): + return self.filter.predict(t) + + def rts_smooth(self, estimates): + return self.filter.rts_smooth(estimates, norm_quats=False) + + + def init_state(self, state, covs_diag=None, covs=None, filter_time=None): + if covs_diag is not None: + P = np.diag(covs_diag) + elif covs is not None: + P = covs + else: + P = self.filter.covs() + self.filter.init_state(state, P, filter_time) + + def predict_and_observe(self, t, kind, data): + data = np.atleast_2d(data) + return self.filter.predict_and_update_batch(t, kind, data, self.get_R(kind, len(data))) + + def get_R(self, kind, n): + obs_noise = self.obs_noise[kind] + dim = obs_noise.shape[0] + R = np.zeros((n, dim, dim)) + for i in range(n): + R[i,:,:] = obs_noise + return R + + +if __name__ == "__main__": + generated_dir = sys.argv[2] + LaneKalman.generate_code(generated_dir) diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 86672b0460..7e30b1e3a7 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -14,8 +14,9 @@ from system.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_MAX_DELTA = math.radians(20.0) * DT_MDL # 20deg in 1 second is well within curvature limits ROLL_MIN, ROLL_MAX = math.radians(-10), math.radians(10) +ROLL_STD_MAX = math.radians(1.5) LATERAL_ACC_SENSOR_THRESHOLD = 4.0 @@ -36,10 +37,8 @@ class ParamsLearner: self.yaw_rate = 0.0 self.yaw_rate_std = 0.0 self.roll = 0.0 - self.steering_pressed = False self.steering_angle = 0.0 - - self.valid = True + self.roll_valid = False def handle_log(self, t, which, msg): if which == 'liveLocationKalman': @@ -48,8 +47,8 @@ class ParamsLearner: localizer_roll = msg.orientationNED.value[0] localizer_roll_std = np.radians(1) if np.isnan(msg.orientationNED.std[0]) else msg.orientationNED.std[0] - roll_valid = msg.orientationNED.valid and ROLL_MIN < localizer_roll < ROLL_MAX - if roll_valid: + self.roll_valid = (localizer_roll_std < ROLL_STD_MAX) and (ROLL_MIN < localizer_roll < ROLL_MAX) and msg.sensorsOK + if self.roll_valid: roll = localizer_roll # Experimentally found multiplier of 2 to be best trade-off between stability and accuracy or similar? roll_std = 2 * localizer_roll_std @@ -88,11 +87,10 @@ class ParamsLearner: elif which == 'carState': self.steering_angle = msg.steeringAngleDeg - self.steering_pressed = msg.steeringPressed self.speed = msg.vEgo - in_linear_region = abs(self.steering_angle) < 45 or not self.steering_pressed - self.active = self.speed > 5 and in_linear_region + in_linear_region = abs(self.steering_angle) < 45 + self.active = self.speed > 1 and in_linear_region if self.active: self.kf.predict_and_observe(t, ObservationKind.STEER_ANGLE, np.array([[math.radians(msg.steeringAngleDeg)]])) @@ -158,6 +156,7 @@ def main(sm=None, pm=None): learner = ParamsLearner(CP, params['steerRatio'], params['stiffnessFactor'], math.radians(params['angleOffsetAverageDeg'])) angle_offset_average = params['angleOffsetAverageDeg'] angle_offset = angle_offset_average + roll = 0.0 while True: sm.update() @@ -177,6 +176,8 @@ def main(sm=None, pm=None): angle_offset_average = clip(math.degrees(x[States.ANGLE_OFFSET]), angle_offset_average - MAX_ANGLE_OFFSET_DELTA, angle_offset_average + MAX_ANGLE_OFFSET_DELTA) angle_offset = clip(math.degrees(x[States.ANGLE_OFFSET] + x[States.ANGLE_OFFSET_FAST]), angle_offset - MAX_ANGLE_OFFSET_DELTA, angle_offset + MAX_ANGLE_OFFSET_DELTA) + roll = clip(float(x[States.ROAD_ROLL]), roll - ROLL_MAX_DELTA, roll + ROLL_MAX_DELTA) + roll_std = float(P[States.ROAD_ROLL]) # Account for the opposite signs of the yaw rates sensors_valid = bool(abs(learner.speed * (x[States.YAW_RATE] + learner.yaw_rate)) < LATERAL_ACC_SENSOR_THRESHOLD) @@ -187,12 +188,14 @@ def main(sm=None, pm=None): liveParameters.sensorValid = sensors_valid liveParameters.steerRatio = float(x[States.STEER_RATIO]) liveParameters.stiffnessFactor = float(x[States.STIFFNESS]) - liveParameters.roll = float(x[States.ROAD_ROLL]) + liveParameters.roll = roll liveParameters.angleOffsetAverageDeg = angle_offset_average liveParameters.angleOffsetDeg = angle_offset liveParameters.valid = all(( abs(liveParameters.angleOffsetAverageDeg) < 10.0, abs(liveParameters.angleOffsetDeg) < 10.0, + abs(liveParameters.roll) < ROLL_MAX, + roll_std < ROLL_STD_MAX, 0.2 <= liveParameters.stiffnessFactor <= 5.0, min_sr <= liveParameters.steerRatio <= max_sr, )) diff --git a/selfdrive/locationd/test/_test_locationd_lib.py b/selfdrive/locationd/test/_test_locationd_lib.py index c4a053bbc6..819bb1570e 100755 --- a/selfdrive/locationd/test/_test_locationd_lib.py +++ b/selfdrive/locationd/test/_test_locationd_lib.py @@ -19,7 +19,7 @@ LIBLOCATIOND_PATH = os.path.abspath(os.path.join(os.path.dirname(__file__), '../ class TestLocationdLib(unittest.TestCase): def setUp(self): header = '''typedef ...* Localizer_t; -Localizer_t localizer_init(); +Localizer_t localizer_init(bool has_ublox); void localizer_get_message_bytes(Localizer_t localizer, bool inputsOK, bool sensorsOK, bool gpsOK, bool msgValid, char *buff, size_t buff_size); void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t size);''' @@ -27,7 +27,7 @@ void localizer_handle_msg_bytes(Localizer_t localizer, const char *data, size_t self.ffi.cdef(header) self.lib = self.ffi.dlopen(LIBLOCATIOND_PATH) - self.localizer = self.lib.localizer_init() + self.localizer = self.lib.localizer_init(True) # default to ublox self.buff_size = 2048 self.msg_buff = self.ffi.new(f'char[{self.buff_size}]') diff --git a/selfdrive/locationd/test/test_laikad.py b/selfdrive/locationd/test/test_laikad.py index 198ecfe935..e184dc3ec4 100755 --- a/selfdrive/locationd/test/test_laikad.py +++ b/selfdrive/locationd/test/test_laikad.py @@ -1,34 +1,92 @@ #!/usr/bin/env python3 import time import unittest -from collections import defaultdict +from cereal import log +import cereal.messaging as messaging +from common.params import Params from datetime import datetime from unittest import mock -from unittest.mock import Mock, patch +#from unittest.mock import patch +from tqdm import tqdm + -from common.params import Params from laika.constants import SECS_IN_DAY from laika.downloader import DownloadFailed -from laika.ephemeris import EphemerisType, GPSEphemeris +from laika.ephemeris import EphemerisType from laika.gps_time import GPSTime -from laika.helpers import ConstellationId, TimeRangeHolder -from laika.raw_gnss import GNSSMeasurement, read_raw_ublox -from selfdrive.locationd.laikad import EPHEMERIS_CACHE, EphemerisSourceType, Laikad, create_measurement_msg +from laika.helpers import ConstellationId +from laika.raw_gnss import GNSSMeasurement, read_raw_ublox, read_raw_qcom +from selfdrive.locationd.laikad import EPHEMERIS_CACHE, Laikad from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader +from selfdrive.manager.process_config import managed_processes + +from selfdrive.test.process_replay.helpers import OpenpilotPrefix + + +def get_ublox_gnss(ubloxraw): + with OpenpilotPrefix(): + managed_processes['ubloxd'].start() + timeout_ms = 30 + pm = messaging.PubMaster(['ubloxRaw']) + sock = messaging.sub_sock('ubloxGnss', timeout=timeout_ms) + + log_msgs = [] + log_t = [] + for x in tqdm(ubloxraw): + pm.send(x.which(), x.as_builder()) + ret = messaging.recv_one(sock) + if ret is not None: + msg = log.Event.new_message(ubloxGnss=ret.ubloxGnss.to_dict()) + msg.logMonoTime = x.logMonoTime + log_msgs.append(msg) + log_t.append(1e-9 * x.logMonoTime) + assert managed_processes['ubloxd'].get_process_state_msg().running + assert len(log_msgs) > 1 or len(ubloxraw) == 0 + managed_processes['ubloxd'].stop() + return log_t, log_msgs + def get_log(segs=range(0)): logs = [] for i in segs: logs.extend(LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", i))) - return [m for m in logs if m.which() == 'ubloxGnss'] + raw_logs = [m for m in logs if m.which() == 'ubloxRaw'] + all_logs = get_ublox_gnss(raw_logs)[1] + low_gnss = [] + for m in all_logs: + if m.ubloxGnss.which() != 'measurementReport': + continue + + MAX_MEAS = 7 + if m.ubloxGnss.measurementReport.numMeas > MAX_MEAS: + mb = log.Event.new_message(ubloxGnss=m.ubloxGnss.to_dict()) + mb.logMonoTime = m.logMonoTime + mb.ubloxGnss.measurementReport.numMeas = MAX_MEAS + mb.ubloxGnss.measurementReport.measurements = list(m.ubloxGnss.measurementReport.measurements)[:MAX_MEAS] + mb.ubloxGnss.measurementReport.measurements[0].pseudorange += 1000 + low_gnss.append(mb.as_reader()) + else: + low_gnss.append(m) + return all_logs, low_gnss + +def get_log_qcom(segs=range(0)): + logs = [] + for i in segs: + logs.extend(LogReader(get_url("b0b3cba7abf862d1|2023-03-11--09-40-33", i))) + all_logs = [m for m in logs if m.which() == 'qcomGnss'] + return all_logs def verify_messages(lr, laikad, return_one_success=False): good_msgs = [] for m in lr: - msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) + if m.which() == 'ubloxGnss': + gnss_msg = m.ubloxGnss + else: + gnss_msg = m.qcomGnss + msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=True) if msg is not None and len(msg.gnssMeasurements.correctedMeasurements) > 0: good_msgs.append(msg) if return_one_success: @@ -38,10 +96,16 @@ def verify_messages(lr, laikad, return_one_success=False): def get_first_gps_time(logs): for m in logs: - if m.ubloxGnss.which == 'measurementReport': - new_meas = read_raw_ublox(m.ubloxGnss.measurementReport) - if len(new_meas) > 0: - return new_meas[0].recv_time + if m.which() == 'ubloxGnss': + if m.ubloxGnss.which == 'measurementReport': + new_meas = read_raw_ublox(m.ubloxGnss.measurementReport) + if len(new_meas) > 0: + return new_meas[0].recv_time + else: + if m.qcomGnss.which == 'measurementReport': + new_meas = read_raw_qcom(m.qcomGnss.measurementReport) + if len(new_meas) > 0: + return new_meas[0].recv_time def get_measurement_mock(gpstime, sat_ephemeris): @@ -59,90 +123,64 @@ class TestLaikad(unittest.TestCase): @classmethod def setUpClass(cls): - logs = get_log(range(1)) + logs, low_gnss = get_log(range(1)) cls.logs = logs + cls.low_gnss = low_gnss + cls.logs_qcom = get_log_qcom(range(1)) first_gps_time = get_first_gps_time(logs) cls.first_gps_time = first_gps_time def setUp(self): Params().remove(EPHEMERIS_CACHE) - def test_fetch_orbits_non_blocking(self): + def test_fetch_navs_non_blocking(self): gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1)) laikad = Laikad() - laikad.fetch_orbits(gpstime, block=False) + laikad.fetch_navs(gpstime, block=False) laikad.orbit_fetch_future.result(30) + # Get results and save orbits to laikad: - laikad.fetch_orbits(gpstime, block=False) - - ephem = laikad.astro_dog.orbits['G01'][0] + laikad.fetch_navs(gpstime, block=False) + ephem = laikad.astro_dog.navs['G01'][0] self.assertIsNotNone(ephem) - laikad.fetch_orbits(gpstime+2*SECS_IN_DAY, block=False) + laikad.fetch_navs(gpstime+2*SECS_IN_DAY, block=False) laikad.orbit_fetch_future.result(30) # Get results and save orbits to laikad: - laikad.fetch_orbits(gpstime + 2 * SECS_IN_DAY, block=False) + laikad.fetch_navs(gpstime + 2 * SECS_IN_DAY, block=False) - ephem2 = laikad.astro_dog.orbits['G01'][0] + ephem2 = laikad.astro_dog.navs['G01'][0] self.assertIsNotNone(ephem) self.assertNotEqual(ephem, ephem2) - def test_fetch_orbits_with_wrong_clocks(self): + + def test_fetch_navs_with_wrong_clocks(self): laikad = Laikad() - def check_has_orbits(): - self.assertGreater(len(laikad.astro_dog.orbits), 0) - ephem = laikad.astro_dog.orbits['G01'][0] + def check_has_navs(): + self.assertGreater(len(laikad.astro_dog.navs), 0) + ephem = laikad.astro_dog.navs['G01'][0] self.assertIsNotNone(ephem) real_current_time = GPSTime.from_datetime(datetime(2021, month=3, day=1)) wrong_future_clock_time = real_current_time + SECS_IN_DAY - laikad.fetch_orbits(wrong_future_clock_time, block=True) - check_has_orbits() - self.assertEqual(laikad.last_fetch_orbits_t, wrong_future_clock_time) + laikad.fetch_navs(wrong_future_clock_time, block=True) + check_has_navs() + self.assertEqual(laikad.last_fetch_navs_t, wrong_future_clock_time) # Test fetching orbits with earlier time - assert real_current_time < laikad.last_fetch_orbits_t + assert real_current_time < laikad.last_fetch_navs_t laikad.astro_dog.orbits = {} - laikad.fetch_orbits(real_current_time, block=True) - check_has_orbits() - self.assertEqual(laikad.last_fetch_orbits_t, real_current_time) - - def test_ephemeris_source_in_msg(self): - data_mock = defaultdict(str) - data_mock['sv_id'] = 1 - - gpstime = GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC - laikad = Laikad() - laikad.fetch_orbits(gpstime, block=True) - meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['R01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.glonassIacUltraRapid) - # Verify gps satellite returns same source - meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['R01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.glonassIacUltraRapid) - - # Test nasa source by using older date - gpstime = GPSTime.from_datetime(datetime(2021, month=3, day=1)) - laikad = Laikad() - laikad.fetch_orbits(gpstime, block=True) - meas = get_measurement_mock(gpstime, laikad.astro_dog.orbits['G01'][0]) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nasaUltraRapid) - - # Test nav source type - ephem = GPSEphemeris(data_mock, gpstime) - meas = get_measurement_mock(gpstime, ephem) - msg = create_measurement_msg(meas) - self.assertEqual(msg.ephemerisSource.type.raw, EphemerisSourceType.nav) + laikad.fetch_navs(real_current_time, block=True) + check_has_navs() + self.assertEqual(laikad.last_fetch_navs_t, real_current_time) def test_laika_online(self): laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT) correct_msgs = verify_messages(self.logs, laikad) - correct_msgs_expected = 555 + correct_msgs_expected = 560 self.assertEqual(correct_msgs_expected, len(correct_msgs)) self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) @@ -159,107 +197,141 @@ class TestLaikad(unittest.TestCase): self.assertTrue(kf_valid) def test_laika_online_nav_only(self): - laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV) - # Disable fetch_orbits to test NAV only - laikad.fetch_orbits = Mock() - correct_msgs = verify_messages(self.logs, laikad) - correct_msgs_expected = 559 - self.assertEqual(correct_msgs_expected, len(correct_msgs)) - self.assertEqual(correct_msgs_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + laikad = Laikad(auto_update=True, valid_ephem_types=EphemerisType.NAV, use_qcom=use_qcom) + # Disable fetch_orbits to test NAV only + correct_msgs = verify_messages(logs, laikad) + correct_msgs_expected = 44 if use_qcom else 560 + valid_fix_expected = 43 if use_qcom else 560 + + self.assertEqual(correct_msgs_expected, len(correct_msgs)) + self.assertEqual(valid_fix_expected, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) @mock.patch('laika.downloader.download_and_cache_file') def test_laika_offline(self, downloader_mock): downloader_mock.side_effect = DownloadFailed("Mock download failed") laikad = Laikad(auto_update=False) - laikad.fetch_orbits(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True) + laikad.fetch_navs(GPS_TIME_PREDICTION_ORBITS_RUSSIAN_SRC, block=True) @mock.patch('laika.downloader.download_and_cache_file') def test_download_failed_russian_source(self, downloader_mock): downloader_mock.side_effect = DownloadFailed laikad = Laikad(auto_update=False) correct_msgs = verify_messages(self.logs, laikad) - self.assertEqual(16, len(correct_msgs)) - self.assertEqual(16, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) + expected_msgs = 376 + self.assertEqual(expected_msgs, len(correct_msgs)) + self.assertEqual(expected_msgs, len([m for m in correct_msgs if m.gnssMeasurements.positionECEF.valid])) def test_laika_get_orbits(self): laikad = Laikad(auto_update=False) # Pretend process has loaded the orbits on startup by using the time of the first gps message. - laikad.fetch_orbits(self.first_gps_time, block=True) - self.dict_has_values(laikad.astro_dog.orbits) + laikad.fetch_navs(self.first_gps_time, block=True) + self.dict_has_values(laikad.astro_dog.navs) @unittest.skip("Use to debug live data") - def test_laika_get_orbits_now(self): + def test_laika_get_navs_now(self): laikad = Laikad(auto_update=False) - laikad.fetch_orbits(GPSTime.from_datetime(datetime.utcnow()), block=True) + laikad.fetch_navs(GPSTime.from_datetime(datetime.utcnow()), block=True) prn = "G01" - self.assertGreater(len(laikad.astro_dog.orbits[prn]), 0) + self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) prn = "R01" - self.assertGreater(len(laikad.astro_dog.orbits[prn]), 0) - print(min(laikad.astro_dog.orbits[prn], key=lambda e: e.epoch).epoch.as_datetime()) + self.assertGreater(len(laikad.astro_dog.navs[prn]), 0) - def test_get_orbits_in_process(self): - laikad = Laikad(auto_update=False) - has_orbits = False - for m in self.logs: - laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=False) - if laikad.orbit_fetch_future is not None: - laikad.orbit_fetch_future.result() - vals = laikad.astro_dog.orbits.values() - has_orbits = len(vals) > 0 and max([len(v) for v in vals]) > 0 - if has_orbits: - break - self.assertTrue(has_orbits) - self.assertGreater(len(laikad.astro_dog.orbit_fetched_times._ranges), 0) - self.assertEqual(None, laikad.orbit_fetch_future) + def test_get_navs_in_process(self): + for auto_fetch_navs in [True, False]: + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + laikad = Laikad(auto_update=False, use_qcom=use_qcom, auto_fetch_navs=auto_fetch_navs) + has_navs = False + has_fix = False + seen_chip_eph = False + seen_internet_eph = False + + for m in logs: + gnss_msg = m.qcomGnss if use_qcom else m.ubloxGnss + out_msg = laikad.process_gnss_msg(gnss_msg, m.logMonoTime, block=False) + if laikad.orbit_fetch_future is not None: + laikad.orbit_fetch_future.result() + vals = laikad.astro_dog.navs.values() + has_navs = len(vals) > 0 and max([len(v) for v in vals]) > 0 + vals = laikad.astro_dog.qcom_polys.values() + has_polys = len(vals) > 0 and max([len(v) for v in vals]) > 0 + has_fix = has_fix or out_msg.gnssMeasurements.positionECEF.valid + if len(out_msg.gnssMeasurements.ephemerisStatuses): + seen_chip_eph = seen_chip_eph or any([x.source == 'gnssChip' for x in out_msg.gnssMeasurements.ephemerisStatuses]) + seen_internet_eph = seen_internet_eph or any([x.source == 'internet' for x in out_msg.gnssMeasurements.ephemerisStatuses]) + + self.assertTrue(has_navs or has_polys) + self.assertTrue(has_fix) + self.assertTrue(seen_chip_eph or auto_fetch_navs) + self.assertTrue(seen_internet_eph or not auto_fetch_navs) + self.assertEqual(len(laikad.astro_dog.navs_fetched_times._ranges), 0) + self.assertEqual(None, laikad.orbit_fetch_future) def test_cache(self): - laikad = Laikad(auto_update=True, save_ephemeris=True) + use_qcom = True + for use_qcom, logs in zip([True, False], [self.logs_qcom, self.logs]): + laikad = Laikad(auto_update=True, save_ephemeris=True, use_qcom=use_qcom) + def wait_for_cache(): + max_time = 2 + while Params().get(EPHEMERIS_CACHE) is None: + time.sleep(0.1) + max_time -= 0.1 + if max_time < 0: + self.fail("Cache has not been written after 2 seconds") - def wait_for_cache(): - max_time = 2 - while Params().get(EPHEMERIS_CACHE) is None: - time.sleep(0.1) - max_time -= 0.1 - if max_time < 0: - self.fail("Cache has not been written after 2 seconds") + # Test cache with no ephemeris + laikad.last_report_time = GPSTime(1,0) + laikad.cache_ephemeris() + wait_for_cache() + Params().remove(EPHEMERIS_CACHE) - # Test cache with no ephemeris - laikad.cache_ephemeris(t=GPSTime(0, 0)) - wait_for_cache() - Params().remove(EPHEMERIS_CACHE) + #laikad.astro_dog.get_navs(self.first_gps_time) + msg = verify_messages(logs, laikad, return_one_success=True) + laikad.cache_ephemeris() + wait_for_cache() - laikad.astro_dog.get_navs(self.first_gps_time) - laikad.fetch_orbits(self.first_gps_time, block=True) - - # Wait for cache to save - wait_for_cache() - - # Check both nav and orbits separate - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, save_ephemeris=True) - # Verify orbits and nav are loaded from cache - self.dict_has_values(laikad.astro_dog.orbits) - self.dict_has_values(laikad.astro_dog.nav) - # Verify cache is working for only nav by running a segment - msg = verify_messages(self.logs, laikad, return_one_success=True) - self.assertIsNotNone(msg) - - with patch('selfdrive.locationd.laikad.get_orbit_data', return_value=None) as mock_method: - # Verify no orbit downloads even if orbit fetch times is reset since the cache has recently been saved and we don't want to download high frequently - laikad.astro_dog.orbit_fetched_times = TimeRangeHolder() - laikad.fetch_orbits(self.first_gps_time, block=False) - mock_method.assert_not_called() - - # Verify cache is working for only orbits by running a segment - laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True) - msg = verify_messages(self.logs, laikad, return_one_success=True) + # Check both nav and orbits separate + laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.NAV, + save_ephemeris=True, use_qcom=use_qcom, auto_fetch_navs=False) + # Verify navs are loaded from cache + self.dict_has_values(laikad.astro_dog.navs) + # Verify cache is working for only nav by running a segment + msg = verify_messages(logs, laikad, return_one_success=True) + self.assertTrue(len(msg.gnssMeasurements.ephemerisStatuses)) + self.assertTrue(any([x.source=='cache' for x in msg.gnssMeasurements.ephemerisStatuses])) self.assertIsNotNone(msg) - # Verify orbit data is not downloaded - mock_method.assert_not_called() + + + + #TODO test cache with only orbits + #with patch('selfdrive.locationd.laikad.get_orbit_data', return_value=None) as mock_method: + # # Verify no orbit downloads even if orbit fetch times is reset since the cache has recently been saved and we don't want to download high frequently + # laikad.astro_dog.orbit_fetched_times = TimeRangeHolder() + # laikad.fetch_navs(self.first_gps_time, block=False) + # mock_method.assert_not_called() + + # # Verify cache is working for only orbits by running a segment + # laikad = Laikad(auto_update=False, valid_ephem_types=EphemerisType.ULTRA_RAPID_ORBIT, save_ephemeris=True) + # msg = verify_messages(self.logs, laikad, return_one_success=True) + # self.assertIsNotNone(msg) + # # Verify orbit data is not downloaded + # mock_method.assert_not_called() + #break + + def test_low_gnss_meas(self): + cnt = 0 + laikad = Laikad() + for m in self.low_gnss: + msg = laikad.process_gnss_msg(m.ubloxGnss, m.logMonoTime, block=True) + if msg is None: + continue + gm = msg.gnssMeasurements + if len(gm.correctedMeasurements) != 0 and gm.positionECEF.valid: + cnt += 1 + self.assertEqual(cnt, 560) def dict_has_values(self, dct): self.assertGreater(len(dct), 0) self.assertGreater(min([len(v) for v in dct.values()]), 0) - - if __name__ == "__main__": unittest.main() diff --git a/selfdrive/locationd/test/test_locationd.py b/selfdrive/locationd/test/test_locationd.py index 7569530211..9f643e2b8f 100755 --- a/selfdrive/locationd/test/test_locationd.py +++ b/selfdrive/locationd/test/test_locationd.py @@ -8,6 +8,7 @@ import capnp import cereal.messaging as messaging from cereal.services import service_list from common.params import Params +from common.transformations.coordinates import ecef2geodetic from selfdrive.manager.process_config import managed_processes @@ -45,16 +46,25 @@ class TestLocationdProc(unittest.TestCase): except capnp.lib.capnp.KjException: msg = messaging.new_message(name, 0) + if name == "gpsLocationExternal": msg.gpsLocationExternal.flags = 1 msg.gpsLocationExternal.verticalAccuracy = 1.0 msg.gpsLocationExternal.speedAccuracy = 1.0 msg.gpsLocationExternal.bearingAccuracyDeg = 1.0 msg.gpsLocationExternal.vNED = [0.0, 0.0, 0.0] - msg.gpsLocationExternal.latitude = self.lat - msg.gpsLocationExternal.longitude = self.lon + msg.gpsLocationExternal.latitude = float(self.lat) + msg.gpsLocationExternal.longitude = float(self.lon) msg.gpsLocationExternal.unixTimestampMillis = t * 1e6 - msg.gpsLocationExternal.altitude = self.alt + msg.gpsLocationExternal.altitude = float(self.alt) + #if name == "gnssMeasurements": + # msg.gnssMeasurements.measTime = t + # msg.gnssMeasurements.positionECEF.value = [self.x , self.y, self.z] + # msg.gnssMeasurements.positionECEF.std = [0,0,0] + # msg.gnssMeasurements.positionECEF.valid = True + # msg.gnssMeasurements.velocityECEF.value = [] + # msg.gnssMeasurements.velocityECEF.std = [0,0,0] + # msg.gnssMeasurements.velocityECEF.valid = True elif name == 'cameraOdometry': msg.cameraOdometry.rot = [0.0, 0.0, 0.0] msg.cameraOdometry.rotStd = [0.0, 0.0, 0.0] @@ -67,9 +77,11 @@ class TestLocationdProc(unittest.TestCase): # first reset params Params().remove('LastGPSPosition') - self.lat = 30 + (random.random() * 10.0) - self.lon = -70 + (random.random() * 10.0) - self.alt = 5 + (random.random() * 10.0) + self.x = -2710700 + (random.random() * 1e5) + self.y = -4280600 + (random.random() * 1e5) + self.z = 3850300 + (random.random() * 1e5) + self.lat, self.lon, self.alt = ecef2geodetic([self.x, self.y, self.z]) + self.fake_duration = 90 # secs # get fake messages at the correct frequency, listed in services.py fake_msgs = [] @@ -83,7 +95,6 @@ class TestLocationdProc(unittest.TestCase): time.sleep(1) # wait for async params write lastGPS = json.loads(Params().get('LastGPSPosition')) - self.assertAlmostEqual(lastGPS['latitude'], self.lat, places=3) self.assertAlmostEqual(lastGPS['longitude'], self.lon, places=3) self.assertAlmostEqual(lastGPS['altitude'], self.alt, places=3) diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index 66af234590..fcc068d34e 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -16,19 +16,21 @@ from selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY HISTORY = 5 # secs POINTS_PER_BUCKET = 1500 MIN_POINTS_TOTAL = 4000 +MIN_POINTS_TOTAL_QLOG = 600 FIT_POINTS_TOTAL = 2000 +FIT_POINTS_TOTAL_QLOG = 600 MIN_VEL = 15 # m/s FRICTION_FACTOR = 1.5 # ~85% of data coverage FACTOR_SANITY = 0.3 +FACTOR_SANITY_QLOG = 0.5 FRICTION_SANITY = 0.5 +FRICTION_SANITY_QLOG = 0.8 STEER_MIN_THRESHOLD = 0.02 MIN_FILTER_DECAY = 50 MAX_FILTER_DECAY = 250 LAT_ACC_THRESHOLD = 1 STEER_BUCKET_BOUNDS = [(-0.5, -0.3), (-0.3, -0.2), (-0.2, -0.1), (-0.1, 0), (0, 0.1), (0.1, 0.2), (0.2, 0.3), (0.3, 0.5)] -MIN_BUCKET_POINTS = [100, 300, 500, 500, 500, 500, 300, 100] -MAX_RESETS = 5.0 -MAX_INVALID_THRESHOLD = 10 +MIN_BUCKET_POINTS = np.array([100, 300, 500, 500, 500, 500, 300, 100]) MIN_ENGAGE_BUFFER = 2 # secs VERSION = 1 # bump this to invalidate old parameter caches @@ -58,10 +60,11 @@ class NPQueue: class PointBuckets: - def __init__(self, x_bounds, min_points): + def __init__(self, x_bounds, min_points, min_points_total): self.x_bounds = x_bounds self.buckets = {bounds: NPQueue(maxlen=POINTS_PER_BUCKET, rowsize=3) for bounds in x_bounds} self.buckets_min_points = {bounds: min_point for bounds, min_point in zip(x_bounds, min_points)} + self.min_points_total = min_points_total def bucket_lengths(self): return [len(v) for v in self.buckets.values()] @@ -70,7 +73,7 @@ class PointBuckets: return sum(self.bucket_lengths()) def is_valid(self): - return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= MIN_POINTS_TOTAL) + return all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values())) and (self.__len__() >= self.min_points_total) def add_point(self, x, y): for bound_min, bound_max in self.x_bounds: @@ -90,9 +93,22 @@ class PointBuckets: class TorqueEstimator: - def __init__(self, CP): + def __init__(self, CP, decimated=False): self.hist_len = int(HISTORY / DT_MDL) self.lag = CP.steerActuatorDelay + .2 # from controlsd + if decimated: + self.min_bucket_points = MIN_BUCKET_POINTS / 10 + self.min_points_total = MIN_POINTS_TOTAL_QLOG + self.fit_points = FIT_POINTS_TOTAL_QLOG + self.factor_sanity = FACTOR_SANITY_QLOG + self.friction_sanity = FRICTION_SANITY_QLOG + + else: + self.min_bucket_points = MIN_BUCKET_POINTS + self.min_points_total = MIN_POINTS_TOTAL + self.fit_points = FIT_POINTS_TOTAL + self.factor_sanity = FACTOR_SANITY + self.friction_sanity = FRICTION_SANITY self.offline_friction = 0.0 self.offline_latAccelFactor = 0.0 @@ -112,10 +128,10 @@ class TorqueEstimator: 'points': [] } self.decay = MIN_FILTER_DECAY - self.min_lataccel_factor = (1.0 - FACTOR_SANITY) * self.offline_latAccelFactor - self.max_lataccel_factor = (1.0 + FACTOR_SANITY) * self.offline_latAccelFactor - self.min_friction = (1.0 - FRICTION_SANITY) * self.offline_friction - self.max_friction = (1.0 + FRICTION_SANITY) * self.offline_friction + self.min_lataccel_factor = (1.0 - self.factor_sanity) * self.offline_latAccelFactor + self.max_lataccel_factor = (1.0 + self.factor_sanity) * self.offline_latAccelFactor + self.min_friction = (1.0 - self.friction_sanity) * self.offline_friction + self.max_friction = (1.0 + self.friction_sanity) * self.offline_friction # try to restore cached params params = Params() @@ -154,13 +170,12 @@ class TorqueEstimator: def reset(self): self.resets += 1.0 - self.invalid_values_tracker = 0.0 self.decay = MIN_FILTER_DECAY self.raw_points = defaultdict(lambda: deque(maxlen=self.hist_len)) - self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=MIN_BUCKET_POINTS) + self.filtered_points = PointBuckets(x_bounds=STEER_BUCKET_BOUNDS, min_points=self.min_bucket_points, min_points_total=self.min_points_total) def estimate_params(self): - points = self.filtered_points.get_points(FIT_POINTS_TOTAL) + points = self.filtered_points.get_points(self.fit_points) # total least square solution as both x and y are noisy observations # this is empirically the slope of the hysteresis parallelogram as opposed to the line through the diagonals try: @@ -179,12 +194,6 @@ class TorqueEstimator: self.filtered_params[param].update(value) self.filtered_params[param].update_alpha(self.decay) - def is_sane(self, latAccelFactor, latAccelOffset, friction): - if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, friction]]): - return False - return (self.max_friction >= friction >= self.min_friction) and\ - (self.max_lataccel_factor >= latAccelFactor >= self.min_lataccel_factor) - def handle_log(self, t, which, msg): if which == "carControl": self.raw_points["carControl_t"].append(t + self.lag) @@ -214,23 +223,20 @@ class TorqueEstimator: liveTorqueParameters.useParams = self.use_params if self.filtered_points.is_valid(): - latAccelFactor, latAccelOffset, friction_coeff = self.estimate_params() + latAccelFactor, latAccelOffset, frictionCoeff = self.estimate_params() liveTorqueParameters.latAccelFactorRaw = float(latAccelFactor) liveTorqueParameters.latAccelOffsetRaw = float(latAccelOffset) - liveTorqueParameters.frictionCoefficientRaw = float(friction_coeff) + liveTorqueParameters.frictionCoefficientRaw = float(frictionCoeff) - if self.is_sane(latAccelFactor, latAccelOffset, friction_coeff): - liveTorqueParameters.liveValid = True - self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': friction_coeff}) - self.invalid_values_tracker = max(0.0, self.invalid_values_tracker - 0.5) - else: - cloudlog.exception("Live torque parameters are outside acceptable bounds.") + if any([val is None or np.isnan(val) for val in [latAccelFactor, latAccelOffset, frictionCoeff]]): + cloudlog.exception("Live torque parameters are invalid.") liveTorqueParameters.liveValid = False - self.invalid_values_tracker += 1.0 - # Reset when ~10 invalid over 5 secs - if self.invalid_values_tracker > MAX_INVALID_THRESHOLD: - # Do not reset the filter as it may cause a drastic jump, just reset points - self.reset() + self.reset() + else: + liveTorqueParameters.liveValid = True + latAccelFactor = np.clip(latAccelFactor, self.min_lataccel_factor, self.max_lataccel_factor) + frictionCoeff = np.clip(frictionCoeff, self.min_friction, self.max_friction) + self.update_params({'latAccelFactor': latAccelFactor, 'latAccelOffset': latAccelOffset, 'frictionCoefficient': frictionCoeff}) else: liveTorqueParameters.liveValid = False diff --git a/selfdrive/locationd/ublox_msg.cc b/selfdrive/locationd/ublox_msg.cc deleted file mode 100644 index b45dffae33..0000000000 --- a/selfdrive/locationd/ublox_msg.cc +++ /dev/null @@ -1,339 +0,0 @@ -#include "ublox_msg.h" - -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "common/swaglog.h" - -const double gpsPi = 3.1415926535898; -#define UBLOX_MSG_SIZE(hdr) (*(uint16_t *)&hdr[4]) - -inline static bool bit_to_bool(uint8_t val, int shifts) { - return (bool)(val & (1 << shifts)); -} - -inline int UbloxMsgParser::needed_bytes() { - // Msg header incomplete? - if(bytes_in_parse_buf < ublox::UBLOX_HEADER_SIZE) - return ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE - bytes_in_parse_buf; - uint16_t needed = UBLOX_MSG_SIZE(msg_parse_buf) + ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE; - // too much data - if(needed < (uint16_t)bytes_in_parse_buf) - return -1; - return needed - (uint16_t)bytes_in_parse_buf; -} - -inline bool UbloxMsgParser::valid_cheksum() { - uint8_t ck_a = 0, ck_b = 0; - for(int i = 2; i < bytes_in_parse_buf - ublox::UBLOX_CHECKSUM_SIZE;i++) { - ck_a = (ck_a + msg_parse_buf[i]) & 0xFF; - ck_b = (ck_b + ck_a) & 0xFF; - } - if(ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { - LOGD("Checksum a mismatch: %02X, %02X", ck_a, msg_parse_buf[6]); - return false; - } - if(ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { - LOGD("Checksum b mismatch: %02X, %02X", ck_b, msg_parse_buf[7]); - return false; - } - return true; -} - -inline bool UbloxMsgParser::valid() { - return bytes_in_parse_buf >= ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE && - needed_bytes() == 0 && valid_cheksum(); -} - -inline bool UbloxMsgParser::valid_so_far() { - if(bytes_in_parse_buf > 0 && msg_parse_buf[0] != ublox::PREAMBLE1) { - return false; - } - if(bytes_in_parse_buf > 1 && msg_parse_buf[1] != ublox::PREAMBLE2) { - return false; - } - if(needed_bytes() == 0 && !valid()) { - return false; - } - return true; -} - - -bool UbloxMsgParser::add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { - int needed = needed_bytes(); - if(needed > 0) { - bytes_consumed = std::min((uint32_t)needed, incoming_data_len ); - // Add data to buffer - memcpy(msg_parse_buf + bytes_in_parse_buf, incoming_data, bytes_consumed); - bytes_in_parse_buf += bytes_consumed; - } else { - bytes_consumed = incoming_data_len; - } - - // Validate msg format, detect invalid header and invalid checksum. - while(!valid_so_far() && bytes_in_parse_buf != 0) { - // Corrupted msg, drop a byte. - bytes_in_parse_buf -= 1; - if(bytes_in_parse_buf > 0) - memmove(&msg_parse_buf[0], &msg_parse_buf[1], bytes_in_parse_buf); - } - - // There is redundant data at the end of buffer, reset the buffer. - if(needed_bytes() == -1) { - bytes_in_parse_buf = 0; - } - return valid(); -} - - -std::pair> UbloxMsgParser::gen_msg() { - std::string dat = data(); - kaitai::kstream stream(dat); - - ubx_t ubx_message(&stream); - auto body = ubx_message.body(); - - switch (ubx_message.msg_type()) { - case 0x0107: - return {"gpsLocationExternal", gen_nav_pvt(static_cast(body))}; - case 0x0213: - return {"ubloxGnss", gen_rxm_sfrbx(static_cast(body))}; - case 0x0215: - return {"ubloxGnss", gen_rxm_rawx(static_cast(body))}; - case 0x0a09: - return {"ubloxGnss", gen_mon_hw(static_cast(body))}; - case 0x0a0b: - return {"ubloxGnss", gen_mon_hw2(static_cast(body))}; - default: - LOGE("Unknown message type %x", ubx_message.msg_type()); - return {"ubloxGnss", kj::Array()}; - } -} - - -kj::Array UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) { - MessageBuilder msg_builder; - auto gpsLoc = msg_builder.initEvent().initGpsLocationExternal(); - gpsLoc.setSource(cereal::GpsLocationData::SensorSource::UBLOX); - gpsLoc.setFlags(msg->flags()); - gpsLoc.setLatitude(msg->lat() * 1e-07); - gpsLoc.setLongitude(msg->lon() * 1e-07); - gpsLoc.setAltitude(msg->height() * 1e-03); - gpsLoc.setSpeed(msg->g_speed() * 1e-03); - gpsLoc.setBearingDeg(msg->head_mot() * 1e-5); - gpsLoc.setAccuracy(msg->h_acc() * 1e-03); - std::tm timeinfo = std::tm(); - timeinfo.tm_year = msg->year() - 1900; - timeinfo.tm_mon = msg->month() - 1; - timeinfo.tm_mday = msg->day(); - timeinfo.tm_hour = msg->hour(); - timeinfo.tm_min = msg->min(); - timeinfo.tm_sec = msg->sec(); - - std::time_t utc_tt = timegm(&timeinfo); - gpsLoc.setUnixTimestampMillis(utc_tt * 1e+03 + msg->nano() * 1e-06); - float f[] = { msg->vel_n() * 1e-03f, msg->vel_e() * 1e-03f, msg->vel_d() * 1e-03f }; - gpsLoc.setVNED(f); - gpsLoc.setVerticalAccuracy(msg->v_acc() * 1e-03); - gpsLoc.setSpeedAccuracy(msg->s_acc() * 1e-03); - gpsLoc.setBearingAccuracyDeg(msg->head_acc() * 1e-05); - return capnp::messageToFlatArray(msg_builder); -} - - -kj::Array UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) { - auto body = *msg->body(); - - if (msg->gnss_id() == ubx_t::gnss_type_t::GNSS_TYPE_GPS) { - // GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes - // We will first need to separate the data from the padding and parity - assert(body.size() == 10); - - std::string subframe_data; - subframe_data.reserve(30); - for (uint32_t word : body) { - word = word >> 6; // TODO: Verify parity - subframe_data.push_back(word >> 16); - subframe_data.push_back(word >> 8); - subframe_data.push_back(word >> 0); - } - - // Collect subframes in map and parse when we have all the parts - { - kaitai::kstream stream(subframe_data); - gps_t subframe(&stream); - int subframe_id = subframe.how()->subframe_id(); - - if (subframe_id == 1) gps_subframes[msg->sv_id()].clear(); - gps_subframes[msg->sv_id()][subframe_id] = subframe_data; - } - - if (gps_subframes[msg->sv_id()].size() == 5) { - MessageBuilder msg_builder; - auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris(); - eph.setSvId(msg->sv_id()); - - // Subframe 1 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][1]); - gps_t subframe(&stream); - gps_t::subframe_1_t* subframe_1 = static_cast(subframe.body()); - - eph.setGpsWeek(subframe_1->week_no()); - eph.setTgd(subframe_1->t_gd() * pow(2, -31)); - eph.setToc(subframe_1->t_oc() * pow(2, 4)); - eph.setAf2(subframe_1->af_2() * pow(2, -55)); - eph.setAf1(subframe_1->af_1() * pow(2, -43)); - eph.setAf0(subframe_1->af_0() * pow(2, -31)); - } - - // Subframe 2 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][2]); - gps_t subframe(&stream); - gps_t::subframe_2_t* subframe_2 = static_cast(subframe.body()); - - eph.setCrs(subframe_2->c_rs() * pow(2, -5)); - eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi); - eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi); - eph.setCuc(subframe_2->c_uc() * pow(2, -29)); - eph.setEcc(subframe_2->e() * pow(2, -33)); - eph.setCus(subframe_2->c_us() * pow(2, -29)); - eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0)); - eph.setToe(subframe_2->t_oe() * pow(2, 4)); - } - - // Subframe 3 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][3]); - gps_t subframe(&stream); - gps_t::subframe_3_t* subframe_3 = static_cast(subframe.body()); - - eph.setCic(subframe_3->c_ic() * pow(2, -29)); - eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi); - eph.setCis(subframe_3->c_is() * pow(2, -29)); - eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi); - eph.setCrc(subframe_3->c_rc() * pow(2, -5)); - eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi); - eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi); - eph.setIode(subframe_3->iode()); - eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi); - } - - // Subframe 4 - { - kaitai::kstream stream(gps_subframes[msg->sv_id()][4]); - gps_t subframe(&stream); - gps_t::subframe_4_t* subframe_4 = static_cast(subframe.body()); - - // This is page 18, why is the page id 56? - if (subframe_4->data_id() == 1 && subframe_4->page_id() == 56) { - auto iono = static_cast(subframe_4->body()); - double a0 = iono->a0() * pow(2, -30); - double a1 = iono->a1() * pow(2, -27); - double a2 = iono->a2() * pow(2, -24); - double a3 = iono->a3() * pow(2, -24); - eph.setIonoAlpha({a0, a1, a2, a3}); - - double b0 = iono->b0() * pow(2, 11); - double b1 = iono->b1() * pow(2, 14); - double b2 = iono->b2() * pow(2, 16); - double b3 = iono->b3() * pow(2, 16); - eph.setIonoBeta({b0, b1, b2, b3}); - } - } - - return capnp::messageToFlatArray(msg_builder); - } - } - return kj::Array(); -} - -kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { - MessageBuilder msg_builder; - auto mr = msg_builder.initEvent().initUbloxGnss().initMeasurementReport(); - mr.setRcvTow(msg->rcv_tow()); - mr.setGpsWeek(msg->week()); - mr.setLeapSeconds(msg->leap_s()); - mr.setGpsWeek(msg->week()); - - auto mb = mr.initMeasurements(msg->num_meas()); - auto measurements = *msg->measurements(); - for(int8_t i = 0; i < msg->num_meas(); i++) { - mb[i].setSvId(measurements[i]->sv_id()); - mb[i].setPseudorange(measurements[i]->pr_mes()); - mb[i].setCarrierCycles(measurements[i]->cp_mes()); - mb[i].setDoppler(measurements[i]->do_mes()); - mb[i].setGnssId(measurements[i]->gnss_id()); - mb[i].setGlonassFrequencyIndex(measurements[i]->freq_id()); - mb[i].setLocktime(measurements[i]->lock_time()); - mb[i].setCno(measurements[i]->cno()); - mb[i].setPseudorangeStdev(0.01 * (pow(2, (measurements[i]->pr_stdev() & 15)))); // weird scaling, might be wrong - mb[i].setCarrierPhaseStdev(0.004 * (measurements[i]->cp_stdev() & 15)); - mb[i].setDopplerStdev(0.002 * (pow(2, (measurements[i]->do_stdev() & 15)))); // weird scaling, might be wrong - - auto ts = mb[i].initTrackingStatus(); - auto trk_stat = measurements[i]->trk_stat(); - ts.setPseudorangeValid(bit_to_bool(trk_stat, 0)); - ts.setCarrierPhaseValid(bit_to_bool(trk_stat, 1)); - ts.setHalfCycleValid(bit_to_bool(trk_stat, 2)); - ts.setHalfCycleSubtracted(bit_to_bool(trk_stat, 3)); - } - - mr.setNumMeas(msg->num_meas()); - auto rs = mr.initReceiverStatus(); - rs.setLeapSecValid(bit_to_bool(msg->rec_stat(), 0)); - rs.setClkReset(bit_to_bool(msg->rec_stat(), 2)); - return capnp::messageToFlatArray(msg_builder); -} - -kj::Array UbloxMsgParser::gen_mon_hw(ubx_t::mon_hw_t *msg) { - MessageBuilder msg_builder; - auto hwStatus = msg_builder.initEvent().initUbloxGnss().initHwStatus(); - hwStatus.setNoisePerMS(msg->noise_per_ms()); - hwStatus.setFlags(msg->flags()); - hwStatus.setAgcCnt(msg->agc_cnt()); - hwStatus.setAStatus((cereal::UbloxGnss::HwStatus::AntennaSupervisorState) msg->a_status()); - hwStatus.setAPower((cereal::UbloxGnss::HwStatus::AntennaPowerStatus) msg->a_power()); - hwStatus.setJamInd(msg->jam_ind()); - return capnp::messageToFlatArray(msg_builder); -} - -kj::Array UbloxMsgParser::gen_mon_hw2(ubx_t::mon_hw2_t *msg) { - MessageBuilder msg_builder; - auto hwStatus = msg_builder.initEvent().initUbloxGnss().initHwStatus2(); - hwStatus.setOfsI(msg->ofs_i()); - hwStatus.setMagI(msg->mag_i()); - hwStatus.setOfsQ(msg->ofs_q()); - hwStatus.setMagQ(msg->mag_q()); - - switch (msg->cfg_source()) { - case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_ROM: - hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::ROM); - break; - case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_OTP: - hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::OTP); - break; - case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_CONFIG_PINS: - hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::CONFIGPINS); - break; - case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_FLASH: - hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::FLASH); - break; - default: - hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::UNDEFINED); - break; - } - - hwStatus.setLowLevCfg(msg->low_lev_cfg()); - hwStatus.setPostStatus(msg->post_status()); - - return capnp::messageToFlatArray(msg_builder); -} diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index c8a7d41539..5b69e3dca7 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -15,7 +15,7 @@ from system.version import is_dirty MAX_CACHE_SIZE = 4e9 if "CI" in os.environ else 2e9 CACHE_DIR = Path("/data/scons_cache" if AGNOS else "/tmp/scons_cache") -TOTAL_SCONS_NODES = 2395 +TOTAL_SCONS_NODES = 2460 MAX_BUILD_PROGRESS = 100 PREBUILT = os.path.exists(os.path.join(BASEDIR, 'prebuilt')) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 928507f65b..1b10cee806 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -20,25 +20,23 @@ from selfdrive.manager.process_config import managed_processes from selfdrive.athena.registration import register, UNREGISTERED_DONGLE_ID from system.swaglog import cloudlog, add_file_handler from system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ - terms_version, training_version, is_tested_branch + terms_version, training_version, is_tested_branch, is_release_branch -sys.path.append(os.path.join(BASEDIR, "pyextra")) - def manager_init() -> None: # update system time from panda set_time(cloudlog) # save boot log - subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd")) + subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd")) params = Params() params.clear_all(ParamKeyType.CLEAR_ON_MANAGER_START) default_params: List[Tuple[str, Union[str, bytes]]] = [ ("CompletedTrainingVersion", "0"), - ("DisengageOnAccelerator", "1"), + ("DisengageOnAccelerator", "0"), ("GsmMetered", "1"), ("HasAcceptedTerms", "0"), ("LanguageSetting", "main_en"), @@ -78,6 +76,7 @@ def manager_init() -> None: params.put("GitBranch", get_short_branch(default="")) params.put("GitRemote", get_origin(default="")) params.put_bool("IsTestedBranch", is_tested_branch()) + params.put_bool("IsReleaseBranch", is_release_branch()) # set dongle id reg_res = register(show_spinner=True) @@ -154,7 +153,7 @@ def manager_thread() -> None: for param in ("DoUninstall", "DoShutdown", "DoReboot"): if params.get_bool(param): shutdown = True - params.put("LastManagerExitReason", param) + params.put("LastManagerExitReason", f"{param} {datetime.datetime.now()}") cloudlog.warning(f"Shutting down manager - {param} set") if shutdown: diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index dabfbe4ee0..ce18073d9b 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -290,10 +290,11 @@ class DaemonProcess(ManagerProcess): def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None, - not_run: Optional[List[str]]=None) -> None: + not_run: Optional[List[str]]=None) -> List[ManagerProcess]: if not_run is None: not_run = [] + running = [] for p in procs: # Conditions that make a process run run = any(( @@ -311,7 +312,10 @@ def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None if run: p.start() + running.append(p) else: p.stop(block=False) p.check_watchdog(started) + + return running diff --git a/selfdrive/manager/process_config.py b/selfdrive/manager/process_config.py index 3f63fbb959..57c42aaaaa 100644 --- a/selfdrive/manager/process_config.py +++ b/selfdrive/manager/process_config.py @@ -17,6 +17,17 @@ def logging(started, params, CP: car.CarParams) -> bool: run = (not CP.notCar) or not params.get_bool("DisableLogging") return started and run +def ublox_available() -> bool: + return os.path.exists('/dev/ttyHS0') and not os.path.exists('/persist/comma/use-quectel-gps') + +def ublox(started, params, CP: car.CarParams) -> bool: + use_ublox = ublox_available() + params.put_bool("UbloxAvailable", use_ublox) + return started and use_ublox + +def qcomgps(started, params, CP: car.CarParams) -> bool: + return started and not ublox_available() + procs = [ # due to qualcomm kernel bugs SIGKILLing camerad sometimes causes page table corruption NativeProcess("camerad", "system/camerad", ["./camerad"], unkillable=True, callback=driverview), @@ -24,15 +35,17 @@ procs = [ NativeProcess("logcatd", "system/logcatd", ["./logcatd"]), NativeProcess("proclogd", "system/proclogd", ["./proclogd"]), PythonProcess("logmessaged", "system.logmessaged", offroad=True), + PythonProcess("micd", "system.micd"), PythonProcess("timezoned", "system.timezoned", enabled=not PC, offroad=True), DaemonProcess("manage_athenad", "selfdrive.athena.manage_athenad", "AthenadPid"), NativeProcess("dmonitoringmodeld", "selfdrive/modeld", ["./dmonitoringmodeld"], enabled=(not PC or WEBCAM), callback=driverview), - NativeProcess("encoderd", "selfdrive/loggerd", ["./encoderd"]), - NativeProcess("loggerd", "selfdrive/loggerd", ["./loggerd"], onroad=False, callback=logging), + NativeProcess("encoderd", "system/loggerd", ["./encoderd"]), + NativeProcess("loggerd", "system/loggerd", ["./loggerd"], onroad=False, callback=logging), NativeProcess("modeld", "selfdrive/modeld", ["./modeld"]), - NativeProcess("sensord", "selfdrive/sensord", ["./sensord"], enabled=not PC), - NativeProcess("ubloxd", "selfdrive/locationd", ["./ubloxd"], enabled=(not PC or WEBCAM)), + NativeProcess("mapsd", "selfdrive/navd", ["./map_renderer"], enabled=False), + NativeProcess("navmodeld", "selfdrive/modeld", ["./navmodeld"], enabled=False), + NativeProcess("sensord", "system/sensord", ["./sensord"], enabled=not PC), NativeProcess("ui", "selfdrive/ui", ["./ui"], offroad=True, watchdog_max_dt=(5 if not PC else None)), NativeProcess("soundd", "selfdrive/ui/soundd", ["./soundd"], offroad=True), NativeProcess("locationd", "selfdrive/locationd", ["./locationd"]), @@ -40,20 +53,21 @@ procs = [ PythonProcess("calibrationd", "selfdrive.locationd.calibrationd"), PythonProcess("torqued", "selfdrive.locationd.torqued"), PythonProcess("controlsd", "selfdrive.controls.controlsd"), - PythonProcess("deleter", "selfdrive.loggerd.deleter", offroad=True), + PythonProcess("deleter", "system.loggerd.deleter", offroad=True), PythonProcess("dmonitoringd", "selfdrive.monitoring.dmonitoringd", enabled=(not PC or WEBCAM), callback=driverview), PythonProcess("laikad", "selfdrive.locationd.laikad"), - PythonProcess("rawgpsd", "selfdrive.sensord.rawgps.rawgpsd", enabled=TICI), + PythonProcess("rawgpsd", "system.sensord.rawgps.rawgpsd", enabled=TICI, onroad=False, callback=qcomgps), PythonProcess("navd", "selfdrive.navd.navd"), PythonProcess("pandad", "selfdrive.boardd.pandad", offroad=True), PythonProcess("paramsd", "selfdrive.locationd.paramsd"), - PythonProcess("pigeond", "selfdrive.sensord.pigeond", enabled=TICI), + NativeProcess("ubloxd", "system/ubloxd", ["./ubloxd"], enabled=TICI, onroad=False, callback=ublox), + PythonProcess("pigeond", "system.sensord.pigeond", enabled=TICI, onroad=False, callback=ublox), PythonProcess("plannerd", "selfdrive.controls.plannerd"), PythonProcess("radard", "selfdrive.controls.radard"), PythonProcess("thermald", "selfdrive.thermald.thermald", offroad=True), PythonProcess("tombstoned", "selfdrive.tombstoned", enabled=not PC, offroad=True), PythonProcess("updated", "selfdrive.updated", enabled=not PC, onroad=False, offroad=True), - PythonProcess("uploader", "selfdrive.loggerd.uploader", offroad=True), + PythonProcess("uploader", "system.loggerd.uploader", offroad=True), PythonProcess("statsd", "selfdrive.statsd", offroad=True), # debug procs diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index 7ac2c5f506..889ab3d279 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -4,22 +4,27 @@ import signal import time import unittest +from cereal import car +from common.params import Params import selfdrive.manager.manager as manager -from selfdrive.manager.process import DaemonProcess +from selfdrive.manager.process import ensure_running from selfdrive.manager.process_config import managed_processes from system.hardware import HARDWARE os.environ['FAKEUPLOAD'] = "1" MAX_STARTUP_TIME = 3 -ALL_PROCESSES = [p.name for p in managed_processes.values() if (type(p) is not DaemonProcess) and p.enabled and (p.name not in ['pandad', ])] - +BLACKLIST_PROCS = ['manage_athenad', 'pandad', 'pigeond'] class TestManager(unittest.TestCase): def setUp(self): os.environ['PASSIVE'] = '0' HARDWARE.set_power_save(False) + # ensure clean CarParams + params = Params() + params.clear_all() + def tearDown(self): manager.manager_cleanup() @@ -40,25 +45,27 @@ class TestManager(unittest.TestCase): Ensure all processes exit cleanly when stopped. """ HARDWARE.set_power_save(False) + manager.manager_init() manager.manager_prepare() - for p in ALL_PROCESSES: - managed_processes[p].start() + + CP = car.CarParams.new_message() + procs = ensure_running(managed_processes.values(), True, Params(), CP, not_run=BLACKLIST_PROCS) time.sleep(10) - for p in reversed(ALL_PROCESSES): - with self.subTest(proc=p): - state = managed_processes[p].get_process_state_msg() - self.assertTrue(state.running, f"{p} not running") - exit_code = managed_processes[p].stop(retry=False) + for p in procs: + with self.subTest(proc=p.name): + state = p.get_process_state_msg() + self.assertTrue(state.running, f"{p.name} not running") + exit_code = p.stop(retry=False) - self.assertTrue(exit_code is not None, f"{p} failed to exit") + self.assertTrue(exit_code is not None, f"{p.name} failed to exit") # TODO: interrupted blocking read exits with 1 in cereal. use a more unique return code exit_codes = [0, 1] - if managed_processes[p].sigkill: + if p.sigkill: exit_codes = [-signal.SIGKILL] - self.assertIn(exit_code, exit_codes, f"{p} died with {exit_code}") + self.assertIn(exit_code, exit_codes, f"{p.name} died with {exit_code}") if __name__ == "__main__": diff --git a/selfdrive/modeld/SConscript b/selfdrive/modeld/SConscript index 5c02e2b15f..c7e0dc5d86 100644 --- a/selfdrive/modeld/SConscript +++ b/selfdrive/modeld/SConscript @@ -70,29 +70,14 @@ lenv.Program('_dmonitoringmodeld', [ if use_thneed and arch == "larch64" or GetOption('pc_thneed'): fn = File("models/supercombo").abspath - if GetOption('pc_thneed'): - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && GPU=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" - else: - cmd = f"cd {Dir('#').abspath}/tinygrad_repo && FLOAT16=1 MATMUL=1 PYOPENCL_NO_CACHE=1 NATIVE_EXPLOG=1 OPTWG=1 UNSAFE_FLOAT4=1 DEBUGCL=1 python3 openpilot/compile.py {fn}.onnx {fn}.thneed" + tinygrad_opts = ["NATIVE_EXPLOG=1", "VALIDHACKS=1", "OPTLOCAL=1", "IMAGE=2", "GPU=1", "ENABLE_METHOD_CACHE=1"] + if not GetOption('pc_thneed'): + # use FLOAT16 on device for speed + don't cache the CL kernels for space + tinygrad_opts += ["FLOAT16=1", "PYOPENCL_NO_CACHE=1"] + cmd = f"cd {Dir('#').abspath}/tinygrad_repo && " + ' '.join(tinygrad_opts) + f" python3 openpilot/compile.py {fn}.onnx {fn}.thneed" - # is there a better way then listing all of tinygrad? - lenv.Command(fn + ".thneed", [fn + ".onnx", - "#tinygrad_repo/openpilot/compile.py", - "#tinygrad_repo/accel/opencl/conv.cl", - "#tinygrad_repo/accel/opencl/matmul.cl", - "#tinygrad_repo/accel/opencl/ops_opencl.py", - "#tinygrad_repo/accel/opencl/preprocessing.py", - "#tinygrad_repo/extra/onnx.py", - "#tinygrad_repo/extra/utils.py", - "#tinygrad_repo/tinygrad/llops/ops_gpu.py", - "#tinygrad_repo/tinygrad/llops/ops_opencl.py", - "#tinygrad_repo/tinygrad/helpers.py", - "#tinygrad_repo/tinygrad/mlops.py", - "#tinygrad_repo/tinygrad/ops.py", - "#tinygrad_repo/tinygrad/shapetracker.py", - "#tinygrad_repo/tinygrad/tensor.py", - "#tinygrad_repo/tinygrad/nn/__init__.py" - ], cmd) + tinygrad_files = sum([lenv.Glob("#"+x) for x in open(File("#release/files_common").abspath).read().split("\n") if x.startswith("tinygrad_repo/")], []) + lenv.Command(fn + ".thneed", [fn + ".onnx"] + tinygrad_files, cmd) llenv = lenv.Clone() if GetOption('pc_thneed'): @@ -111,3 +96,8 @@ llenv.Program('_modeld', [ "modeld.cc", "models/driving.cc", ]+common_model, LIBS=libs + transformations) + +lenv.Program('_navmodeld', [ + "navmodeld.cc", + "models/nav.cc", + ]+common_model, LIBS=libs + transformations) diff --git a/selfdrive/modeld/modeld.cc b/selfdrive/modeld/modeld.cc index cfc71a0e27..0a8a8d6358 100644 --- a/selfdrive/modeld/modeld.cc +++ b/selfdrive/modeld/modeld.cc @@ -15,6 +15,7 @@ #include "common/util.h" #include "system/hardware/hw.h" #include "selfdrive/modeld/models/driving.h" +#include "selfdrive/modeld/models/nav.h" ExitHandler do_exit; @@ -72,6 +73,8 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl mat3 model_transform_main = {}; mat3 model_transform_extra = {}; bool live_calib_seen = false; + float driving_style[DRIVING_STYLE_LEN] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0}; + float nav_features[NAV_FEATURE_LEN] = {0}; VisionBuf *buf_main = nullptr; VisionBuf *buf_extra = nullptr; @@ -151,7 +154,7 @@ void run_model(ModelState &model, VisionIpcClient &vipc_client_main, VisionIpcCl } double mt1 = millis_since_boot(); - ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, prepare_only); + ModelOutput *model_output = model_eval_frame(&model, buf_main, buf_extra, model_transform_main, model_transform_extra, vec_desire, is_rhd, driving_style, nav_features, prepare_only); double mt2 = millis_since_boot(); float model_execution_time = (mt2 - mt1) / 1000.0; diff --git a/selfdrive/modeld/models/README.md b/selfdrive/modeld/models/README.md index 6b704cbfa8..1bfca8e95a 100644 --- a/selfdrive/modeld/models/README.md +++ b/selfdrive/modeld/models/README.md @@ -2,7 +2,7 @@ To view the architecture of the ONNX networks, you can use [netron](https://netron.app/) ## Supercombo -### Supercombo input format (Full size: 393738 x float32) +### Supercombo input format (Full size: 799906 x float32) * **image stream** * Two consecutive images (256 * 512 * 3 in RGB) recorded at 20 Hz : 393216 = 2 * 6 * 128 * 256 * Each 256 * 512 image is represented in YUV420 with 6 channels : 6 * 128 * 256 @@ -16,11 +16,11 @@ To view the architecture of the ONNX networks, you can use [netron](https://netr * Channel 4 represents the half-res U channel * Channel 5 represents the half-res V channel * **desire** - * one-hot encoded vector to command model to execute certain actions, bit only needs to be sent for 1 frame : 8 + * one-hot encoded buffer to command model to execute certain actions, bit needs to be sent for the past 5 seconds (at 20FPS) : 100 * 8 * **traffic convention** * one-hot encoded vector to tell model whether traffic is right-hand or left-hand traffic : 2 -* **recurrent state** - * The recurrent state vector that is fed back into the GRU for temporal context : 512 +* **feature buffer** + * A buffer of intermediate features that gets appended to the current feature to form a 5 seconds temporal context (at 20FPS) : 99 * 128 ### Supercombo output format (Full size: XXX x float32) diff --git a/selfdrive/modeld/models/commonmodel.h b/selfdrive/modeld/models/commonmodel.h index 40c82a8c21..1a079da055 100644 --- a/selfdrive/modeld/models/commonmodel.h +++ b/selfdrive/modeld/models/commonmodel.h @@ -13,6 +13,7 @@ #endif #include "common/mat.h" +#include "cereal/messaging/messaging.h" #include "selfdrive/modeld/transforms/loadyuv.h" #include "selfdrive/modeld/transforms/transform.h" @@ -21,6 +22,11 @@ const bool send_raw_pred = getenv("SEND_RAW_PRED") != NULL; void softmax(const float* input, float* output, size_t len); float sigmoid(float input); +template +constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { + return kj::ArrayPtr(arr.data(), arr.size()); +} + class ModelFrame { public: ModelFrame(cl_device_id device_id, cl_context context); diff --git a/selfdrive/modeld/models/driving.cc b/selfdrive/modeld/models/driving.cc index 3316e6d114..5538d6ff9b 100644 --- a/selfdrive/modeld/models/driving.cc +++ b/selfdrive/modeld/models/driving.cc @@ -22,11 +22,6 @@ std::array prev_brake_3ms2_probs = {0,0,0}; // #define DUMP_YUV -template -constexpr const kj::ArrayPtr to_kj_array_ptr(const std::array &arr) { - return kj::ArrayPtr(arr.data(), arr.size()); -} - void model_init(ModelState* s, cl_device_id device_id, cl_context context) { s->frame = new ModelFrame(device_id, context); s->wide_frame = new ModelFrame(device_id, context); @@ -51,10 +46,19 @@ void model_init(ModelState* s, cl_device_id device_id, cl_context context) { #ifdef TRAFFIC_CONVENTION s->m->addTrafficConvention(s->traffic_convention, TRAFFIC_CONVENTION_LEN); #endif + +#ifdef DRIVING_STYLE + s->m->addDrivingStyle(s->driving_style, DRIVING_STYLE_LEN); +#endif + +#ifdef NAV + s->m->addNavFeatures(s->nav_features, NAV_FEATURE_LEN); +#endif + } ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, - const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only) { + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, float *driving_style, float *nav_features, bool prepare_only) { #ifdef DESIRE std::memmove(&s->pulse_desire[0], &s->pulse_desire[DESIRE_LEN], sizeof(float) * DESIRE_LEN*HISTORY_BUFFER_LEN); if (desire_in != NULL) { @@ -62,9 +66,9 @@ ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, // Model decides when action is completed // so desire input is just a pulse triggered on rising edge if (desire_in[i] - s->prev_desire[i] > .99) { - s->pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN-1)+i] = desire_in[i]; + s->pulse_desire[DESIRE_LEN*HISTORY_BUFFER_LEN+i] = desire_in[i]; } else { - s->pulse_desire[DESIRE_LEN*(HISTORY_BUFFER_LEN-1)+i] = 0.0; + s->pulse_desire[DESIRE_LEN*HISTORY_BUFFER_LEN+i] = 0.0; } s->prev_desire[i] = desire_in[i]; } @@ -72,6 +76,14 @@ ModelOutput* model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* wbuf, LOGT("Desire enqueued"); #endif +#ifdef NAV + std::memcpy(s->nav_features, nav_features, sizeof(float)*NAV_FEATURE_LEN); +#endif + +#ifdef DRIVING_STYLE + std::memcpy(s->driving_style, driving_style, sizeof(float)*DRIVING_STYLE_LEN); +#endif + int rhd_idx = is_rhd; s->traffic_convention[rhd_idx] = 1.0; s->traffic_convention[1-rhd_idx] = 0.0; @@ -188,7 +200,7 @@ void fill_meta(cereal::ModelDataV2::MetaData::Builder meta, const ModelOutputMet } template -void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array &t, +void fill_xyzt(cereal::XYZTData::Builder xyzt, const std::array &t, const std::array &x, const std::array &y, const std::array &z) { xyzt.setT(to_kj_array_ptr(t)); xyzt.setX(to_kj_array_ptr(x)); @@ -197,7 +209,7 @@ void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array -void fill_xyzt(cereal::ModelDataV2::XYZTData::Builder xyzt, const std::array &t, +void fill_xyzt(cereal::XYZTData::Builder xyzt, const std::array &t, const std::array &x, const std::array &y, const std::array &z, const std::array &x_std, const std::array &y_std, const std::array &z_std) { fill_xyzt(xyzt, t, x, y, z); @@ -337,6 +349,17 @@ void fill_model(cereal::ModelDataV2::Builder &framed, const ModelOutput &net_out for (int i=0; i prediction; - float prob; - - constexpr const ModelOutputStopLinePrediction &get_best_prediction(int t_idx) const { - int max_idx = 0; - for (int i = 1; i < prediction.size(); i++) { - if (prediction[i].prob > prediction[max_idx].prob) { - max_idx = i; - } - } - return prediction[max_idx]; - } -}; -static_assert(sizeof(ModelOutputStopLines) == (sizeof(ModelOutputStopLinePrediction)*STOP_LINE_MHP_N) + sizeof(float)); struct ModelOutputPose { ModelOutputXYZ velocity_mean; @@ -190,6 +163,20 @@ struct ModelOutputPose { }; static_assert(sizeof(ModelOutputPose) == sizeof(ModelOutputXYZ)*4); +struct ModelOutputWideFromDeviceEuler { + ModelOutputXYZ mean; + ModelOutputXYZ std; +}; +static_assert(sizeof(ModelOutputWideFromDeviceEuler) == sizeof(ModelOutputXYZ)*2); + +struct ModelOutputTemporalPose { + ModelOutputXYZ velocity_mean; + ModelOutputXYZ rotation_mean; + ModelOutputXYZ velocity_std; + ModelOutputXYZ rotation_std; +}; +static_assert(sizeof(ModelOutputTemporalPose) == sizeof(ModelOutputXYZ)*4); + struct ModelOutputDisengageProb { float gas_disengage; float brake_disengage; @@ -245,9 +232,10 @@ struct ModelOutput { const ModelOutputLaneLines lane_lines; const ModelOutputRoadEdges road_edges; const ModelOutputLeads leads; - const ModelOutputStopLines stop_lines; const ModelOutputMeta meta; const ModelOutputPose pose; + const ModelOutputWideFromDeviceEuler wide_from_device_euler; + const ModelOutputTemporalPose temporal_pose; }; constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); @@ -257,7 +245,7 @@ constexpr int OUTPUT_SIZE = sizeof(ModelOutput) / sizeof(float); #else constexpr int TEMPORAL_SIZE = 0; #endif -constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + FEATURE_LEN; +constexpr int NET_OUTPUT_SIZE = OUTPUT_SIZE + FEATURE_LEN + PAD_SIZE; // TODO: convert remaining arrays to std::array and update model runners struct ModelState { @@ -273,11 +261,17 @@ struct ModelState { #ifdef TRAFFIC_CONVENTION float traffic_convention[TRAFFIC_CONVENTION_LEN] = {}; #endif +#ifdef DRIVING_STYLE + float driving_style[DRIVING_STYLE_LEN] = {}; +#endif +#ifdef NAV + float nav_features[NAV_FEATURE_LEN] = {}; +#endif }; void model_init(ModelState* s, cl_device_id device_id, cl_context context); ModelOutput *model_eval_frame(ModelState* s, VisionBuf* buf, VisionBuf* buf_wide, - const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, bool prepare_only); + const mat3 &transform, const mat3 &transform_wide, float *desire_in, bool is_rhd, float *driving_style, float *nav_features, bool prepare_only); void model_free(ModelState* s); void model_publish(PubMaster &pm, uint32_t vipc_frame_id, uint32_t vipc_frame_id_extra, uint32_t frame_id, float frame_drop, const ModelOutput &net_outputs, uint64_t timestamp_eof, diff --git a/selfdrive/modeld/models/nav.cc b/selfdrive/modeld/models/nav.cc new file mode 100644 index 0000000000..861795e172 --- /dev/null +++ b/selfdrive/modeld/models/nav.cc @@ -0,0 +1,66 @@ +#include "selfdrive/modeld/models/nav.h" + +#include +#include + +#include "common/mat.h" +#include "common/modeldata.h" +#include "common/timing.h" + + +void navmodel_init(NavModelState* s) { + #ifdef USE_ONNX_MODEL + s->m = new ONNXModel("models/navmodel.onnx", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); + #else + s->m = new SNPEModel("models/navmodel_q.dlc", &s->output[0], NAV_NET_OUTPUT_SIZE, USE_DSP_RUNTIME, false, true); + #endif +} + +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf) { + memcpy(s->net_input_buf, buf->addr, NAV_INPUT_SIZE); + + double t1 = millis_since_boot(); + s->m->addImage((float*)s->net_input_buf, NAV_INPUT_SIZE/sizeof(float)); + s->m->execute(); + double t2 = millis_since_boot(); + + NavModelResult *model_res = (NavModelResult*)&s->output; + model_res->dsp_execution_time = (t2 - t1) / 1000.; + return model_res; +} + +void fill_plan(cereal::NavModelData::Builder &framed, const NavModelOutputPlan &plan) { + std::array pos_x, pos_y; + std::array pos_x_std, pos_y_std; + + for (int i=0; im; +} diff --git a/selfdrive/modeld/models/nav.h b/selfdrive/modeld/models/nav.h new file mode 100644 index 0000000000..b2955ad2c5 --- /dev/null +++ b/selfdrive/modeld/models/nav.h @@ -0,0 +1,56 @@ +#pragma once + +#include "cereal/messaging/messaging.h" +#include "cereal/visionipc/visionipc_client.h" +#include "common/util.h" +#include "common/modeldata.h" +#include "selfdrive/modeld/models/commonmodel.h" +#include "selfdrive/modeld/runners/run.h" + +constexpr int NAV_INPUT_SIZE = 256*256; +constexpr int NAV_FEATURE_LEN = 64; +constexpr int NAV_DESIRE_LEN = 32; + +struct NavModelOutputXY { + float x; + float y; +}; +static_assert(sizeof(NavModelOutputXY) == sizeof(float)*2); + +struct NavModelOutputPlan { + std::array mean; + std::array std; +}; +static_assert(sizeof(NavModelOutputPlan) == sizeof(NavModelOutputXY)*TRAJECTORY_SIZE*2); + +struct NavModelOutputDesirePrediction { + std::array values; +}; +static_assert(sizeof(NavModelOutputDesirePrediction) == sizeof(float)*NAV_DESIRE_LEN); + +struct NavModelOutputFeatures { + std::array values; +}; +static_assert(sizeof(NavModelOutputFeatures) == sizeof(float)*NAV_FEATURE_LEN); + +struct NavModelResult { + const NavModelOutputPlan plan; + const NavModelOutputDesirePrediction desire_pred; + const NavModelOutputFeatures features; + float dsp_execution_time; +}; +static_assert(sizeof(NavModelResult) == sizeof(NavModelOutputPlan) + sizeof(NavModelOutputDesirePrediction) + sizeof(NavModelOutputFeatures) + sizeof(float)); + +constexpr int NAV_OUTPUT_SIZE = sizeof(NavModelResult) / sizeof(float); +constexpr int NAV_NET_OUTPUT_SIZE = NAV_OUTPUT_SIZE - 1; + +struct NavModelState { + RunModel *m; + uint8_t net_input_buf[NAV_INPUT_SIZE]; + float output[NAV_OUTPUT_SIZE]; +}; + +void navmodel_init(NavModelState* s); +NavModelResult* navmodel_eval_frame(NavModelState* s, VisionBuf* buf); +void navmodel_publish(PubMaster &pm, uint32_t frame_id, const NavModelResult &model_res, float execution_time); +void navmodel_free(NavModelState* s); diff --git a/selfdrive/modeld/models/navmodel.onnx b/selfdrive/modeld/models/navmodel.onnx new file mode 100644 index 0000000000..397b8d90e2 --- /dev/null +++ b/selfdrive/modeld/models/navmodel.onnx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9028b36a591763724e95205b48f37f09260b4434fb1f3c6f228db1710a401aa8 +size 12258591 diff --git a/selfdrive/modeld/models/navmodel_q.dlc b/selfdrive/modeld/models/navmodel_q.dlc new file mode 100644 index 0000000000..984cf0a553 --- /dev/null +++ b/selfdrive/modeld/models/navmodel_q.dlc @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc7ade56bb4f9525c84a46df22252ea1e321a0518cbcef8bdfc76ccd8ad10b41 +size 3154304 diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index 59b8883d2a..13759c62f8 100644 --- a/selfdrive/modeld/models/supercombo.onnx +++ b/selfdrive/modeld/models/supercombo.onnx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:022a830c39267f378f45204682060c93e3aa304bbd8cfa6b2dfe4fa8f419102d -size 56972617 +oid sha256:5121deb0d5c683b0fbee4c1cad7bc625953bf127b1383fb7599a6b644efd0aea +size 46011200 diff --git a/selfdrive/modeld/navmodeld b/selfdrive/modeld/navmodeld new file mode 100755 index 0000000000..079afd9677 --- /dev/null +++ b/selfdrive/modeld/navmodeld @@ -0,0 +1,12 @@ +#!/bin/sh + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" +cd $DIR + +if [ -f /TICI ]; then + export LD_LIBRARY_PATH="/usr/lib/aarch64-linux-gnu:/data/pythonpath/third_party/snpe/larch64:$LD_LIBRARY_PATH" + export ADSP_LIBRARY_PATH="/data/pythonpath/third_party/snpe/dsp/" +else + export LD_LIBRARY_PATH="$DIR/../../third_party/snpe/x86_64-linux-clang:$DIR/../../openpilot/third_party/snpe/x86_64:$LD_LIBRARY_PATH" +fi +exec ./_navmodeld diff --git a/selfdrive/modeld/navmodeld.cc b/selfdrive/modeld/navmodeld.cc new file mode 100644 index 0000000000..75f2c62ab3 --- /dev/null +++ b/selfdrive/modeld/navmodeld.cc @@ -0,0 +1,59 @@ +#include +#include + +#include +#include + +#include "cereal/visionipc/visionipc_client.h" +#include "common/swaglog.h" +#include "common/util.h" +#include "selfdrive/modeld/models/nav.h" + +ExitHandler do_exit; + +void run_model(NavModelState &model, VisionIpcClient &vipc_client) { + PubMaster pm({"navModel"}); + + double last_ts = 0; + uint32_t last_frame_id = 0; + VisionIpcBufExtra extra = {}; + + while (!do_exit) { + VisionBuf *buf = vipc_client.recv(&extra); + if (buf == nullptr) continue; + + double t1 = millis_since_boot(); + NavModelResult *model_res = navmodel_eval_frame(&model, buf); + double t2 = millis_since_boot(); + + // send navmodel packet + navmodel_publish(pm, extra.frame_id, *model_res, (t2 - t1) / 1000.0); + + //printf("navmodel process: %.2fms, from last %.2fms\n", t2 - t1, t1 - last_ts); + last_ts = t1; + last_frame_id = extra.frame_id; + } +} + +int main(int argc, char **argv) { + setpriority(PRIO_PROCESS, 0, -15); + + // init the models + NavModelState model; + navmodel_init(&model); + LOGW("models loaded, navmodeld starting"); + + VisionIpcClient vipc_client = VisionIpcClient("navd", VISION_STREAM_MAP, true); + while (!do_exit && !vipc_client.connect(false)) { + util::sleep_for(100); + } + + // run the models + if (vipc_client.connected) { + LOGW("connected with buffer size: %d", vipc_client.buffers[0].len); + run_model(model, vipc_client); + } + + navmodel_free(&model); + return 0; +} diff --git a/selfdrive/modeld/runners/onnxmodel.cc b/selfdrive/modeld/runners/onnxmodel.cc index 447d90fd7e..5616a6c9c7 100644 --- a/selfdrive/modeld/runners/onnxmodel.cc +++ b/selfdrive/modeld/runners/onnxmodel.cc @@ -97,6 +97,16 @@ void ONNXModel::addDesire(float *state, int state_size) { desire_state_size = state_size; } +void ONNXModel::addNavFeatures(float *state, int state_size) { + nav_features_input_buf = state; + nav_features_size = state_size; +} + +void ONNXModel::addDrivingStyle(float *state, int state_size) { + driving_style_input_buf = state; + driving_style_size = state_size; +} + void ONNXModel::addTrafficConvention(float *state, int state_size) { traffic_convention_input_buf = state; traffic_convention_size = state_size; @@ -128,7 +138,13 @@ void ONNXModel::execute() { if (desire_input_buf != NULL) { pwrite(desire_input_buf, desire_state_size); } - if (traffic_convention_input_buf != NULL) { + if (nav_features_input_buf != NULL) { + pwrite(nav_features_input_buf, nav_features_size); + } + if (driving_style_input_buf != NULL) { + pwrite(driving_style_input_buf, driving_style_size); + } + if (traffic_convention_input_buf != NULL) { pwrite(traffic_convention_input_buf, traffic_convention_size); } if (calib_input_buf != NULL) { diff --git a/selfdrive/modeld/runners/onnxmodel.h b/selfdrive/modeld/runners/onnxmodel.h index d5b7bfecf0..9990bf1b45 100644 --- a/selfdrive/modeld/runners/onnxmodel.h +++ b/selfdrive/modeld/runners/onnxmodel.h @@ -10,6 +10,8 @@ public: ~ONNXModel(); void addRecurrent(float *state, int state_size); void addDesire(float *state, int state_size); + void addNavFeatures(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); void addTrafficConvention(float *state, int state_size); void addCalib(float *state, int state_size); void addImage(float *image_buf, int buf_size); @@ -25,6 +27,10 @@ private: int rnn_state_size; float *desire_input_buf = NULL; int desire_state_size; + float *nav_features_input_buf = NULL; + int nav_features_size; + float *driving_style_input_buf = NULL; + int driving_style_size; float *traffic_convention_input_buf = NULL; int traffic_convention_size; float *calib_input_buf = NULL; diff --git a/selfdrive/modeld/runners/runmodel.h b/selfdrive/modeld/runners/runmodel.h index c607811401..673ddb50b5 100644 --- a/selfdrive/modeld/runners/runmodel.h +++ b/selfdrive/modeld/runners/runmodel.h @@ -5,6 +5,8 @@ public: virtual ~RunModel() {} virtual void addRecurrent(float *state, int state_size) {} virtual void addDesire(float *state, int state_size) {} + virtual void addNavFeatures(float *state, int state_size) {} + virtual void addDrivingStyle(float *state, int state_size) {} virtual void addTrafficConvention(float *state, int state_size) {} virtual void addCalib(float *state, int state_size) {} virtual void addImage(float *image_buf, int buf_size) {} diff --git a/selfdrive/modeld/runners/snpemodel.cc b/selfdrive/modeld/runners/snpemodel.cc index ff4adcd8d3..609a7a6657 100644 --- a/selfdrive/modeld/runners/snpemodel.cc +++ b/selfdrive/modeld/runners/snpemodel.cc @@ -153,6 +153,16 @@ void SNPEModel::addDesire(float *state, int state_size) { desireBuffer = this->addExtra(state, state_size, 1); } +void SNPEModel::addNavFeatures(float *state, int state_size) { + navFeatures = state; + navFeaturesBuffer = this->addExtra(state, state_size, 1); +} + +void SNPEModel::addDrivingStyle(float *state, int state_size) { + drivingStyle = state; + drivingStyleBuffer = this->addExtra(state, state_size, 2); +} + void SNPEModel::addCalib(float *state, int state_size) { calib = state; calibBuffer = this->addExtra(state, state_size, 1); diff --git a/selfdrive/modeld/runners/snpemodel.h b/selfdrive/modeld/runners/snpemodel.h index 08ae16c2b1..0d84d1d482 100644 --- a/selfdrive/modeld/runners/snpemodel.h +++ b/selfdrive/modeld/runners/snpemodel.h @@ -28,6 +28,8 @@ public: void addTrafficConvention(float *state, int state_size); void addCalib(float *state, int state_size); void addDesire(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); + void addNavFeatures(float *state, int state_size); void addImage(float *image_buf, int buf_size); void addExtra(float *image_buf, int buf_size); void execute(); @@ -75,6 +77,10 @@ private: std::unique_ptr trafficConventionBuffer; float *desire; std::unique_ptr desireBuffer; + float *navFeatures; + std::unique_ptr navFeaturesBuffer; + float *drivingStyle; + std::unique_ptr drivingStyleBuffer; float *calib; std::unique_ptr calibBuffer; }; diff --git a/selfdrive/modeld/runners/thneedmodel.cc b/selfdrive/modeld/runners/thneedmodel.cc index 67db01bb95..4fdd7ca466 100644 --- a/selfdrive/modeld/runners/thneedmodel.cc +++ b/selfdrive/modeld/runners/thneedmodel.cc @@ -24,6 +24,14 @@ void ThneedModel::addDesire(float *state, int state_size) { desire = state; } +void ThneedModel::addDrivingStyle(float *state, int state_size) { + drivingStyle = state; +} + +void ThneedModel::addNavFeatures(float *state, int state_size) { + navFeatures = state; +} + void ThneedModel::addImage(float *image_input_buf, int buf_size) { input = image_input_buf; } diff --git a/selfdrive/modeld/runners/thneedmodel.h b/selfdrive/modeld/runners/thneedmodel.h index f3f34dc7f4..63712f1d00 100644 --- a/selfdrive/modeld/runners/thneedmodel.h +++ b/selfdrive/modeld/runners/thneedmodel.h @@ -9,6 +9,8 @@ public: void addRecurrent(float *state, int state_size); void addTrafficConvention(float *state, int state_size); void addDesire(float *state, int state_size); + void addNavFeatures(float *state, int state_size); + void addDrivingStyle(float *state, int state_size); void addImage(float *image_buf, int buf_size); void addExtra(float *image_buf, int buf_size); void execute(); @@ -26,6 +28,8 @@ private: // recurrent and desire float *recurrent; float *trafficConvention; + float *drivingStyle; float *desire; + float *navFeatures; }; diff --git a/selfdrive/modeld/thneed/thneed.h b/selfdrive/modeld/thneed/thneed.h index 65475ccf7f..6475577734 100644 --- a/selfdrive/modeld/thneed/thneed.h +++ b/selfdrive/modeld/thneed/thneed.h @@ -12,7 +12,7 @@ #include -#include "selfdrive/modeld/thneed/include/msm_kgsl.h" +#include "msm_kgsl.h" using namespace std; diff --git a/selfdrive/monitoring/driver_monitor.py b/selfdrive/monitoring/driver_monitor.py index 30781f4d1b..a2cddc2462 100644 --- a/selfdrive/monitoring/driver_monitor.py +++ b/selfdrive/monitoring/driver_monitor.py @@ -32,7 +32,9 @@ class DRIVER_MONITOR_SETTINGS(): self._BLINK_THRESHOLD = 0.895 self._EE_THRESH11 = 0.275 - self._EE_THRESH12 = 3.0 + self._EE_THRESH12 = 5.5 + self._EE_MAX_OFFSET1 = 0.06 + self._EE_MIN_OFFSET1 = 0.025 self._EE_THRESH21 = 0.01 self._EE_THRESH22 = 0.35 @@ -43,6 +45,7 @@ class DRIVER_MONITOR_SETTINGS(): self._POSE_YAW_THRESHOLD_SLACK = 0.5042 self._POSE_YAW_THRESHOLD_STRICT = self._POSE_YAW_THRESHOLD self._PITCH_NATURAL_OFFSET = 0.029 # initial value before offset is learned + self._PITCH_NATURAL_THRESHOLD = 0.449 self._YAW_NATURAL_OFFSET = 0.097 # initial value before offset is learned self._PITCH_MAX_OFFSET = 0.124 self._PITCH_MIN_OFFSET = -0.0881 @@ -153,6 +156,11 @@ class DriverStatus(): self._set_timers(active_monitoring=True) + def _reset_awareness(self): + self.awareness = 1. + self.awareness_active = 1. + self.awareness_passive = 1. + def _set_timers(self, active_monitoring): if self.active_monitoring_mode and self.awareness <= self.threshold_prompt: if active_monitoring: @@ -196,7 +204,7 @@ class DriverStatus(): 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._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch or \ + if pitch_error > (self.settings._POSE_PITCH_THRESHOLD*self.pose.cfactor_pitch if self.pose_calibrated else self.settings._PITCH_NATURAL_THRESHOLD) or \ yaw_error > self.settings._POSE_YAW_THRESHOLD*self.pose.cfactor_yaw: distracted_types.append(DistractedType.DISTRACTED_POSE) @@ -204,7 +212,7 @@ class DriverStatus(): distracted_types.append(DistractedType.DISTRACTED_BLINK) if self.ee1_calibrated: - ee1_dist = self.eev1 > self.ee1_offseter.filtered_stat.M * self.settings._EE_THRESH12 + ee1_dist = self.eev1 > max(min(self.ee1_offseter.filtered_stat.M, self.settings._EE_MAX_OFFSET1), self.settings._EE_MIN_OFFSET1) * self.settings._EE_THRESH12 else: ee1_dist = self.eev1 > self.settings._EE_THRESH11 # if self.ee2_calibrated: @@ -286,17 +294,17 @@ class DriverStatus(): self.hi_stds = 0 def update_events(self, events, driver_engaged, ctrl_active, standstill): - if (driver_engaged and self.awareness > 0) or not ctrl_active: - # reset only when on disengagement if red reached - self.awareness = 1. - self.awareness_active = 1. - self.awareness_passive = 1. + if (driver_engaged and self.awareness > 0 and not self.active_monitoring_mode) or not ctrl_active: # reset only when on disengagement if red reached + self._reset_awareness() return driver_attentive = self.driver_distraction_filter.x < 0.37 awareness_prev = self.awareness if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): + if driver_engaged: + self._reset_awareness() + return # only restore awareness when paying attention and alert is not red self.awareness = min(self.awareness + ((self.settings._RECOVERY_FACTOR_MAX-self.settings._RECOVERY_FACTOR_MIN)*(1.-self.awareness)+self.settings._RECOVERY_FACTOR_MIN)*self.step_change, 1.) if self.awareness == 1.: diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index 43b5e7747e..f72b4a3aaa 100755 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -101,11 +101,12 @@ class TestMonitoring(unittest.TestCase): ((TEST_TIMESPAN-10-d_status.settings._AWARENESS_TIME)/2))/DT_DMON)].names[0], EventName.driverUnresponsive) # engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel - # - should have short orange recovery time and no green afterwards; should recover rightaway on wheel touch + # - should have short orange recovery time and no green afterwards; wheel touch only recovers when paying attention def test_normal_driver(self): ds_vector = [msg_DISTRACTED] * int(DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ [msg_ATTENTIVE] * int(DISTRACTED_SECONDS_TO_ORANGE/DT_DMON) + \ - [msg_DISTRACTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*2/DT_DMON)) + [msg_DISTRACTED] * int((DISTRACTED_SECONDS_TO_ORANGE+2)/DT_DMON) + \ + [msg_ATTENTIVE] * (int(TEST_TIMESPAN/DT_DMON)-int((DISTRACTED_SECONDS_TO_ORANGE*3+2)/DT_DMON)) interaction_vector = [car_interaction_NOT_DETECTED] * int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON) + \ [car_interaction_DETECTED] * (int(TEST_TIMESPAN/DT_DMON)-int(DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) events, _ = self._run_seq(ds_vector, interaction_vector, always_true, always_false) @@ -113,7 +114,8 @@ class TestMonitoring(unittest.TestCase): self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) self.assertEqual(len(events[int(DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)]), 0) self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) - self.assertEqual(len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)]), 0) + self.assertEqual(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) + self.assertEqual(len(events[int((DISTRACTED_SECONDS_TO_ORANGE*3+2.5)/DT_DMON)]), 0) # engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ # driver dodges, and then touches wheel to no avail, disengages and reengages diff --git a/selfdrive/navd/README.md b/selfdrive/navd/README.md new file mode 100644 index 0000000000..6c7f7eabe3 --- /dev/null +++ b/selfdrive/navd/README.md @@ -0,0 +1,24 @@ +# navigation + +This directory contains two daemons, `navd` and `map_renderer`, which support navigation in the openpilot stack. + +### navd + +`navd` takes in a route through the `NavDestination` param and sends out two packets: `navRoute` and `navInstruction`. These packets contain the coordinates of the planned route and turn-by-turn instructions. + +### map renderer + +The map renderer listens for the `navRoute` and publishes a rendered map view over VisionIPC for the navigation model, which lives in `selfdrive/modeld/`. The rendered maps look like this: + +![](https://i.imgur.com/oZLfmwq.png) + +## development + +Currently, [mapbox](https://www.mapbox.com/) is used for navigation. + +* get an API token: https://docs.mapbox.com/help/glossary/access-token/ +* set an API token using the `MAPBOX_TOKEN` environment variable +* routes/destinations are set through the `NavDestination` param + * use `set_destination.py` for debugging +* edit the map: https://www.mapbox.com/contribute +* mapbox API playground: https://docs.mapbox.com/playground/ diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index b10684eef3..8a2c2a8a91 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -18,5 +18,4 @@ if arch in ['larch64', 'x86_64']: nav_src = ["main.cc", "map_renderer.cc"] qt_env.Program("map_renderer", nav_src, LIBS=qt_libs + ['common', 'json11']) - if GetOption('extras'): - qt_env.SharedLibrary("map_renderer", ["map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) + qt_env.SharedLibrary("map_renderer", ["map_renderer.cc"], LIBS=qt_libs + ['common', 'messaging']) diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index f85916a4ca..51676bb3a3 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -1,20 +1,32 @@ #include "selfdrive/navd/map_renderer.h" +#include #include #include #include -#include #include "common/util.h" #include "common/timing.h" +#include "common/swaglog.h" #include "selfdrive/ui/qt/maps/map_helpers.h" const float DEFAULT_ZOOM = 13.5; // Don't go below 13 or features will start to disappear -const int WIDTH = 512; -const int HEIGHT = WIDTH; - +const int HEIGHT = 512, WIDTH = 512; const int NUM_VIPC_BUFFERS = 4; +const int EARTH_CIRCUMFERENCE_METERS = 40075000; +const int PIXELS_PER_TILE = 256; + +const bool TEST_MODE = getenv("MAP_RENDER_TEST_MODE"); +const int LLK_DECIMATION = TEST_MODE ? 1 : 10; + +float get_zoom_level_for_scale(float lat, float meters_per_pixel) { + float meters_per_tile = meters_per_pixel * PIXELS_PER_TILE; + float num_tiles = cos(DEG2RAD(lat)) * EARTH_CIRCUMFERENCE_METERS / meters_per_tile; + return log2(num_tiles) - 1; +} + + MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); @@ -47,22 +59,32 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set m_map->setFramebufferObject(fbo->handle(), fbo->size()); gl_functions->glViewport(0, 0, WIDTH, HEIGHT); + QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + // https://github.com/mapbox/mapbox-gl-native/blob/cf734a2fec960025350d8de0d01ad38aeae155a0/platform/qt/include/qmapboxgl.hpp#L116 + //LOGD("new state %d", change); + }); + + QObject::connect(m_map.data(), &QMapboxGL::mapLoadingFailed, [=](QMapboxGL::MapLoadingFailure err_code, const QString &reason) { + LOGE("Map loading failed with %d: '%s'\n", err_code, reason.toStdString().c_str()); + }); + if (online) { vipc_server.reset(new VisionIpcServer("navd")); - vipc_server->create_buffers(VisionStreamType::VISION_STREAM_MAP, NUM_VIPC_BUFFERS, false, WIDTH, HEIGHT); + vipc_server->create_buffers(VisionStreamType::VISION_STREAM_MAP, NUM_VIPC_BUFFERS, false, WIDTH/2, HEIGHT/2); vipc_server->start_listener(); - pm.reset(new PubMaster({"navThumbnail"})); - sm.reset(new SubMaster({"liveLocationKalman", "navRoute"})); + pm.reset(new PubMaster({"navThumbnail", "mapRenderState"})); + sm.reset(new SubMaster({"liveLocationKalman", "navRoute"}, {"liveLocationKalman"})); timer = new QTimer(this); + timer->setSingleShot(true); QObject::connect(timer, SIGNAL(timeout()), this, SLOT(msgUpdate())); - timer->start(50); + timer->start(0); } } void MapRenderer::msgUpdate() { - sm->update(0); + sm->update(1000); if (sm->updated("liveLocationKalman")) { auto location = (*sm)["liveLocationKalman"].getLiveLocationKalman(); @@ -70,8 +92,21 @@ void MapRenderer::msgUpdate() { auto orientation = location.getCalibratedOrientationNED(); bool localizer_valid = (location.getStatus() == cereal::LiveLocationKalman::Status::VALID) && pos.getValid(); - if (localizer_valid) { + if (localizer_valid && (sm->rcv_frame("liveLocationKalman") % LLK_DECIMATION) == 0) { updatePosition(QMapbox::Coordinate(pos.getValue()[0], pos.getValue()[1]), RAD2DEG(orientation.getValue()[2])); + + // TODO: use the static rendering mode + if (!loaded() && frame_id > 0) { + for (int i = 0; i < 5 && !loaded(); i++) { + LOGW("map render retry #%d, %d", i+1, m_map.isNull()); + QApplication::processEvents(QEventLoop::AllEvents, 100); + update(); + } + + if (!loaded()) { + LOGE("failed to render map after retry"); + } + } } } @@ -83,15 +118,9 @@ void MapRenderer::msgUpdate() { } updateRoute(route); } -} -void MapRenderer::updateZoom(float zoom) { - if (m_map.isNull()) { - return; - } - - m_map->setZoom(zoom); - update(); + // schedule next update + timer->start(0); } void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { @@ -99,8 +128,13 @@ void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { return; } + // Choose a scale that ensures above 13 zoom level up to and above 75deg of lat + float meters_per_pixel = 2; + float zoom = get_zoom_level_for_scale(position.first, meters_per_pixel); + m_map->setCoordinate(position); m_map->setBearing(bearing); + m_map->setZoom(zoom); update(); } @@ -109,24 +143,33 @@ bool MapRenderer::loaded() { } void MapRenderer::update() { + double start_t = millis_since_boot(); gl_functions->glClear(GL_COLOR_BUFFER_BIT); m_map->render(); gl_functions->glFlush(); + double end_t = millis_since_boot(); - sendVipc(); + if ((vipc_server != nullptr) && loaded()) { + publish((end_t - start_t) / 1000.0); + } } -void MapRenderer::sendVipc() { - if (!vipc_server || !loaded()) { - return; - } +void MapRenderer::sendThumbnail(const uint64_t ts, const kj::Array &buf) { + MessageBuilder msg; + auto thumbnaild = msg.initEvent().initNavThumbnail(); + thumbnaild.setFrameId(frame_id); + thumbnaild.setTimestampEof(ts); + thumbnaild.setThumbnail(buf); + pm->send("navThumbnail", msg); +} +void MapRenderer::publish(const double render_time) { QImage cap = fbo->toImage().convertToFormat(QImage::Format_RGB888, Qt::AutoColor); uint64_t ts = nanos_since_boot(); VisionBuf* buf = vipc_server->get_buffer(VisionStreamType::VISION_STREAM_MAP); VisionIpcBufExtra extra = { .frame_id = frame_id, - .timestamp_sof = ts, + .timestamp_sof = (*sm)["liveLocationKalman"].getLogMonoTime(), .timestamp_eof = ts, }; @@ -134,15 +177,22 @@ void MapRenderer::sendVipc() { uint8_t* dst = (uint8_t*)buf->addr; uint8_t* src = cap.bits(); - // RGB to greyscale + // RGB to greyscale and crop memset(dst, 128, buf->len); - for (int i = 0; i < WIDTH * HEIGHT; i++) { - dst[i] = src[i * 3]; + for (int r = 0; r < HEIGHT/2; r++) { + for (int c = 0; c < WIDTH/2; c++) { + dst[r*WIDTH/2 + c] = src[((HEIGHT/4 + r)*WIDTH + (c+WIDTH/4)) * 3]; + } } vipc_server->send(buf, &extra); - if (frame_id % 100 == 0) { + // Send thumbnail + if (TEST_MODE) { + // Full image in thumbnails in test mode + kj::Array buffer_kj = kj::heapArray((const capnp::byte*)cap.bits(), cap.sizeInBytes()); + sendThumbnail(ts, buffer_kj); + } else if (frame_id % 100 == 0) { // Write jpeg into buffer QByteArray buffer_bytes; QBuffer buffer(&buffer_bytes); @@ -150,16 +200,17 @@ void MapRenderer::sendVipc() { cap.save(&buffer, "JPG", 50); kj::Array buffer_kj = kj::heapArray((const capnp::byte*)buffer_bytes.constData(), buffer_bytes.size()); - - // Send thumbnail - MessageBuilder msg; - auto thumbnaild = msg.initEvent().initNavThumbnail(); - thumbnaild.setFrameId(frame_id); - thumbnaild.setTimestampEof(ts); - thumbnaild.setThumbnail(buffer_kj); - pm->send("navThumbnail", msg); + sendThumbnail(ts, buffer_kj); } + // Send state msg + MessageBuilder msg; + auto state = msg.initEvent().initMapRenderState(); + state.setLocationMonoTime((*sm)["liveLocationKalman"].getLogMonoTime()); + state.setRenderTime(render_time); + state.setFrameId(frame_id); + pm->send("mapRenderState", msg); + frame_id++; } @@ -169,6 +220,7 @@ uint8_t* MapRenderer::getImage() { uint8_t* src = cap.bits(); uint8_t* dst = new uint8_t[WIDTH * HEIGHT]; + // RGB to greyscale for (int i = 0; i < WIDTH * HEIGHT; i++) { dst[i] = src[i * 3]; } @@ -222,11 +274,6 @@ extern "C" { return new MapRenderer(settings, false); } - void map_renderer_update_zoom(MapRenderer *inst, float zoom) { - inst->updateZoom(zoom); - QApplication::processEvents(); - } - void map_renderer_update_position(MapRenderer *inst, float lat, float lon, float bearing) { inst->updatePosition({lat, lon}, bearing); QApplication::processEvents(); diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index 921d871632..0019030b6d 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -25,7 +25,6 @@ public: bool loaded(); ~MapRenderer(); - private: std::unique_ptr ctx; std::unique_ptr surface; @@ -35,7 +34,8 @@ private: std::unique_ptr vipc_server; std::unique_ptr pm; std::unique_ptr sm; - void sendVipc(); + void publish(const double render_time); + void sendThumbnail(const uint64_t ts, const kj::Array &buf); QMapboxGLSettings m_settings; QScopedPointer m_map; @@ -47,7 +47,6 @@ private: QTimer* timer; public slots: - void updateZoom(float zoom); void updatePosition(QMapbox::Coordinate position, float bearing); void updateRoute(QList coordinates); void msgUpdate(); diff --git a/selfdrive/navd/map_renderer.py b/selfdrive/navd/map_renderer.py index 079bb028ce..3239470b23 100755 --- a/selfdrive/navd/map_renderer.py +++ b/selfdrive/navd/map_renderer.py @@ -4,12 +4,14 @@ import os import time import numpy as np +import polyline from cffi import FFI from common.ffi_wrapper import suffix from common.basedir import BASEDIR -HEIGHT = WIDTH = 512 +HEIGHT = WIDTH = SIZE = 512 +METERS_PER_PIXEL = 2 def get_ffi(): @@ -18,7 +20,6 @@ def get_ffi(): ffi = FFI() ffi.cdef(""" void* map_renderer_init(char *maps_host, char *token); -void map_renderer_update_zoom(void *inst, float zoom); void map_renderer_update_position(void *inst, float lat, float lon, float bearing); void map_renderer_update_route(void *inst, char *polyline); void map_renderer_update(void *inst); @@ -50,6 +51,22 @@ def get_image(lib, renderer): return r.reshape((WIDTH, HEIGHT)) +def navRoute_to_polyline(nr): + coords = [(m.latitude, m.longitude) for m in nr.navRoute.coordinates] + return coords_to_polyline(coords) + + +def coords_to_polyline(coords): + # TODO: where does this factor of 10 come from? + return polyline.encode([(lat * 10., lon * 10.) for lat, lon in coords]) + + +def polyline_to_coords(p): + coords = polyline.decode(p) + return [(lat / 10., lon / 10.) for lat, lon in coords] + + + if __name__ == "__main__": import matplotlib.pyplot as plt diff --git a/selfdrive/navd/navd.py b/selfdrive/navd/navd.py index 4855b63594..7af911ab2a 100755 --- a/selfdrive/navd/navd.py +++ b/selfdrive/navd/navd.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +import json import math import os import threading @@ -21,6 +22,7 @@ from system.swaglog import cloudlog REROUTE_DISTANCE = 25 MANEUVER_TRANSITION_THRESHOLD = 10 VALID_POS_STD = 50.0 +REROUTE_COUNTER_MIN = 3 class RouteEngine: @@ -47,6 +49,8 @@ class RouteEngine: self.ui_pid = None + self.reroute_counter = 0 + if "MAPBOX_TOKEN" in os.environ: self.mapbox_token = os.environ["MAPBOX_TOKEN"] self.mapbox_host = "https://api.mapbox.com" @@ -113,6 +117,7 @@ class RouteEngine: self.recompute_countdown = 2**self.recompute_backoff self.recompute_backoff = min(6, self.recompute_backoff + 1) self.calculate_route(new_destination) + self.reroute_counter = 0 else: self.recompute_countdown = max(0, self.recompute_countdown - 1) @@ -135,12 +140,27 @@ class RouteEngine: 'language': lang, } - if self.last_bearing is not None: - params['bearings'] = f"{(self.last_bearing + 360) % 360:.0f},90;" + # TODO: move waypoints into NavDestination param? + waypoints = self.params.get('NavDestinationWaypoints', encoding='utf8') + waypoint_coords = [] + if waypoints is not None and len(waypoints) > 0: + waypoint_coords = json.loads(waypoints) - url = self.mapbox_host + f'/directions/v5/mapbox/driving-traffic/{self.last_position.longitude},{self.last_position.latitude};{destination.longitude},{destination.latitude}' + coords = [ + (self.last_position.longitude, self.last_position.latitude), + *waypoint_coords, + (destination.longitude, destination.latitude) + ] + params['waypoints'] = f'0;{len(coords)-1}' + if self.last_bearing is not None: + params['bearings'] = f"{(self.last_bearing + 360) % 360:.0f},90" + (';'*(len(coords)-1)) + + coords_str = ';'.join([f'{lon},{lat}' for lon, lat in coords]) + url = self.mapbox_host + '/directions/v5/mapbox/driving-traffic/' + coords_str try: resp = requests.get(url, params=params, timeout=10) + if resp.status_code != 200: + cloudlog.event("API request failed", status_code=resp.status_code, text=resp.text, error=True) resp.raise_for_status() r = resp.json() @@ -175,6 +195,10 @@ class RouteEngine: cloudlog.warning("Got empty route response") self.clear_route() + # clear waypoints to avoid a re-route including past waypoints + # TODO: only clear once we're past a waypoint + self.params.remove('NavDestinationWaypoints') + except requests.exceptions.RequestException: cloudlog.exception("failed to get route") self.clear_route() @@ -202,7 +226,11 @@ class RouteEngine: remaining = 1.0 - along_geometry / max(step['distance'], 1) total_distance = step['distance'] * remaining total_time = step['duration'] * remaining - total_time_typical = step['duration_typical'] * remaining + + if step['duration_typical'] is None: + total_time_typical = total_time + else: + total_time_typical = step['duration_typical'] * remaining # Add up totals for future steps for i in range(self.step_idx + 1, len(self.route)): @@ -285,8 +313,11 @@ class RouteEngine: min_d = min(min_d, minimum_distance(a, b, self.last_position)) - return min_d > REROUTE_DISTANCE - + if min_d > REROUTE_DISTANCE: + self.reroute_counter += 1 + else: + self.reroute_counter = 0 + return self.reroute_counter > REROUTE_COUNTER_MIN # TODO: Check for going wrong way in segment diff --git a/selfdrive/navd/set_destination.py b/selfdrive/navd/set_destination.py new file mode 100755 index 0000000000..e6158dbdee --- /dev/null +++ b/selfdrive/navd/set_destination.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python3 +import json +import sys + +from common.params import Params + +if __name__ == "__main__": + params = Params() + + # set from google maps url + if len(sys.argv) > 1: + coords = sys.argv[1].split("/@")[-1].split("/")[0].split(",") + dest = { + "latitude": float(coords[0]), + "longitude": float(coords[1]) + } + params.put("NavDestination", json.dumps(dest)) + params.remove("NavDestinationWaypoints") + else: + print("Setting to Taco Bell") + dest = { + "latitude": 32.71160109904473, + "longitude": -117.12556569985693, + } + params.put("NavDestination", json.dumps(dest)) + + waypoints = [ + (-117.16020713111648, 32.71997612490662), + ] + params.put("NavDestinationWaypoints", json.dumps(waypoints)) + + print(dest) + print(waypoints) diff --git a/selfdrive/sensord/libdiag.h b/selfdrive/sensord/libdiag.h deleted file mode 100644 index 03a59464ed..0000000000 --- a/selfdrive/sensord/libdiag.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -#define DIAG_MAX_RX_PKT_SIZ 4096 - -bool Diag_LSM_Init(uint8_t* pIEnv); -bool Diag_LSM_DeInit(void); - -// DCI - -#define DIAG_CON_APSS 0x001 -#define DIAG_CON_MPSS 0x002 -#define DIAG_CON_LPASS 0x004 -#define DIAG_CON_WCNSS 0x008 - -enum { - DIAG_DCI_NO_ERROR = 1001, -} diag_dci_error_type; - -int diag_register_dci_client(int*, uint16_t*, int, void*); -int diag_log_stream_config(int client_id, int set_mask, uint16_t log_codes_array[], int num_codes); -int diag_register_dci_stream(void (*func_ptr_logs)(unsigned char *ptr, int len), void (*func_ptr_events)(unsigned char *ptr, int len)); -int diag_release_dci_client(int*); - -int diag_send_dci_async_req(int client_id, unsigned char buf[], int bytes, unsigned char *rsp_ptr, int rsp_len, - void (*func_ptr)(unsigned char *ptr, int len, void *data_ptr), void *data_ptr); - - -#ifdef __cplusplus -} -#endif diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.cc b/selfdrive/sensord/sensors/lsm6ds3_accel.cc deleted file mode 100644 index 27cd4d0c70..0000000000 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.cc +++ /dev/null @@ -1,128 +0,0 @@ -#include "lsm6ds3_accel.h" - -#include - -#include "common/swaglog.h" -#include "common/timing.h" - -LSM6DS3_Accel::LSM6DS3_Accel(I2CBus *bus, int gpio_nr, bool shared_gpio) : - I2CSensor(bus, gpio_nr, shared_gpio) {} - -int LSM6DS3_Accel::init() { - int ret = 0; - uint8_t buffer[1]; - uint8_t value = 0; - - ret = read_register(LSM6DS3_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != LSM6DS3_ACCEL_CHIP_ID && buffer[0] != LSM6DS3TRC_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_ACCEL_CHIP_ID) { - source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; - } - - ret = init_gpio(); - if (ret < 0) { - goto fail; - } - - // TODO: set scale and bandwidth. Default is +- 2G, 50 Hz - ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, LSM6DS3_ACCEL_ODR_104HZ); - if (ret < 0) { - goto fail; - } - - ret = set_register(LSM6DS3_ACCEL_I2C_REG_DRDY_CFG, LSM6DS3_ACCEL_DRDY_PULSE_MODE); - if (ret < 0) { - goto fail; - } - - // enable data ready interrupt for accel on INT1 - // (without resetting existing interrupts) - ret = read_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, &value, 1); - if (ret < 0) { - goto fail; - } - - value |= LSM6DS3_ACCEL_INT1_DRDY_XL; - ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value); - -fail: - return ret; -} - -int LSM6DS3_Accel::shutdown() { - int ret = 0; - - // disable data ready interrupt for accel on INT1 - uint8_t value = 0; - ret = read_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, &value, 1); - if (ret < 0) { - goto fail; - } - - value &= ~(LSM6DS3_ACCEL_INT1_DRDY_XL); - ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value); - if (ret < 0) { - LOGE("Could not disable lsm6ds3 acceleration interrupt!") - goto fail; - } - - // enable power-down mode - value = 0; - ret = read_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, &value, 1); - if (ret < 0) { - goto fail; - } - - value &= 0x0F; - ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, value); - if (ret < 0) { - LOGE("Could not power-down lsm6ds3 accelerometer!") - goto fail; - } - -fail: - return ret; -} - -bool LSM6DS3_Accel::get_event(MessageBuilder &msg, uint64_t ts) { - - // INT1 shared with gyro, check STATUS_REG who triggered - uint8_t status_reg = 0; - read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); - if ((status_reg & LSM6DS3_ACCEL_DRDY_XLDA) == 0) { - return false; - } - - uint8_t buffer[6]; - int len = read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); - assert(len == sizeof(buffer)); - - float scale = 9.81 * 2.0f / (1 << 15); - float x = read_16_bit(buffer[0], buffer[1]) * scale; - float y = read_16_bit(buffer[2], buffer[3]) * scale; - float z = read_16_bit(buffer[4], buffer[5]) * scale; - - auto event = msg.initEvent().initAccelerometer(); - event.setSource(source); - event.setVersion(1); - event.setSensor(SENSOR_ACCELEROMETER); - event.setType(SENSOR_TYPE_ACCELEROMETER); - event.setTimestamp(ts); - - float xyz[] = {y, -x, z}; - auto svec = event.initAcceleration(); - svec.setV(xyz); - svec.setStatus(true); - - return true; -} diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc b/selfdrive/sensord/sensors/lsm6ds3_gyro.cc deleted file mode 100644 index 014a72bb73..0000000000 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.cc +++ /dev/null @@ -1,131 +0,0 @@ -#include "lsm6ds3_gyro.h" - -#include -#include - -#include "common/swaglog.h" -#include "common/timing.h" - -#define DEG2RAD(x) ((x) * M_PI / 180.0) - -LSM6DS3_Gyro::LSM6DS3_Gyro(I2CBus *bus, int gpio_nr, bool shared_gpio) : - I2CSensor(bus, gpio_nr, shared_gpio) {} - -int LSM6DS3_Gyro::init() { - int ret = 0; - uint8_t buffer[1]; - uint8_t value = 0; - - ret = read_register(LSM6DS3_GYRO_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != LSM6DS3_GYRO_CHIP_ID && buffer[0] != LSM6DS3TRC_GYRO_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_GYRO_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_GYRO_CHIP_ID) { - source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; - } - - ret = init_gpio(); - if (ret < 0) { - goto fail; - } - - // TODO: set scale. Default is +- 250 deg/s - ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, LSM6DS3_GYRO_ODR_104HZ); - if (ret < 0) { - goto fail; - } - - ret = set_register(LSM6DS3_GYRO_I2C_REG_DRDY_CFG, LSM6DS3_GYRO_DRDY_PULSE_MODE); - if (ret < 0) { - goto fail; - } - - // enable data ready interrupt for gyro on INT1 - // (without resetting existing interrupts) - ret = read_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, &value, 1); - if (ret < 0) { - goto fail; - } - - value |= LSM6DS3_GYRO_INT1_DRDY_G; - ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value); - -fail: - return ret; -} - -int LSM6DS3_Gyro::shutdown() { - int ret = 0; - - // disable data ready interrupt for accel on INT1 - uint8_t value = 0; - ret = read_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, &value, 1); - if (ret < 0) { - goto fail; - } - - value &= ~(LSM6DS3_GYRO_INT1_DRDY_G); - ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value); - if (ret < 0) { - LOGE("Could not disable lsm6ds3 gyroscope interrupt!") - goto fail; - } - - // enable power-down mode - value = 0; - ret = read_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, &value, 1); - if (ret < 0) { - goto fail; - } - - value &= 0x0F; - ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, value); - if (ret < 0) { - LOGE("Could not power-down lsm6ds3 gyroscope!") - goto fail; - } - -fail: - return ret; -} - -bool LSM6DS3_Gyro::get_event(MessageBuilder &msg, uint64_t ts) { - - // INT1 shared with accel, check STATUS_REG who triggered - uint8_t status_reg = 0; - read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); - if ((status_reg & LSM6DS3_GYRO_DRDY_GDA) == 0) { - return false; - } - - uint8_t buffer[6]; - int len = read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); - assert(len == sizeof(buffer)); - - float scale = 8.75 / 1000.0; - float x = DEG2RAD(read_16_bit(buffer[0], buffer[1]) * scale); - float y = DEG2RAD(read_16_bit(buffer[2], buffer[3]) * scale); - float z = DEG2RAD(read_16_bit(buffer[4], buffer[5]) * scale); - - auto event = msg.initEvent().initGyroscope(); - event.setSource(source); - event.setVersion(2); - event.setSensor(SENSOR_GYRO_UNCALIBRATED); - event.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED); - event.setTimestamp(ts); - - float xyz[] = {y, -x, z}; - auto svec = event.initGyroUncalibrated(); - svec.setV(xyz); - svec.setStatus(true); - - return true; -} diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index 7dc002727e..e64907149c 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -13,7 +13,7 @@ from system.swaglog import cloudlog from system.hardware import HARDWARE from common.file_helpers import atomic_write_in_dir from system.version import get_normalized_origin, get_short_branch, get_short_version, is_dirty -from selfdrive.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S +from system.loggerd.config import STATS_DIR, STATS_DIR_FILE_LIMIT, STATS_SOCKET, STATS_FLUSH_TIME_S class METRIC_TYPE: diff --git a/selfdrive/test/longitudinal_maneuvers/maneuver.py b/selfdrive/test/longitudinal_maneuvers/maneuver.py index dad5d89844..00ddfe627e 100644 --- a/selfdrive/test/longitudinal_maneuvers/maneuver.py +++ b/selfdrive/test/longitudinal_maneuvers/maneuver.py @@ -2,7 +2,7 @@ import numpy as np from selfdrive.test.longitudinal_maneuvers.plant import Plant -class Maneuver(): +class Maneuver: def __init__(self, title, duration, **kwargs): # Was tempted to make a builder class self.distance_lead = kwargs.get("initial_distance_lead", 200.0) @@ -17,6 +17,9 @@ class Maneuver(): self.only_lead2 = kwargs.get("only_lead2", False) self.only_radar = kwargs.get("only_radar", False) self.ensure_start = kwargs.get("ensure_start", False) + self.enabled = kwargs.get("enabled", True) + self.e2e = kwargs.get("e2e", False) + self.force_decel = kwargs.get("force_decel", False) self.duration = duration self.title = title @@ -26,23 +29,26 @@ class Maneuver(): lead_relevancy=self.lead_relevancy, speed=self.speed, distance_lead=self.distance_lead, + enabled=self.enabled, only_lead2=self.only_lead2, only_radar=self.only_radar, + e2e=self.e2e, + force_decel=self.force_decel, ) valid = True logs = [] - while plant.current_time() < self.duration: - speed_lead = np.interp(plant.current_time(), self.breakpoints, self.speed_lead_values) - prob = np.interp(plant.current_time(), self.breakpoints, self.prob_lead_values) - cruise = np.interp(plant.current_time(), self.breakpoints, self.cruise_values) + while plant.current_time < self.duration: + speed_lead = np.interp(plant.current_time, self.breakpoints, self.speed_lead_values) + prob = np.interp(plant.current_time, self.breakpoints, self.prob_lead_values) + cruise = np.interp(plant.current_time, self.breakpoints, self.cruise_values) log = plant.step(speed_lead, prob, cruise) d_rel = log['distance_lead'] - log['distance'] if self.lead_relevancy else 200. v_rel = speed_lead - log['speed'] if self.lead_relevancy else 0. log['d_rel'] = d_rel log['v_rel'] = v_rel - logs.append(np.array([plant.current_time(), + logs.append(np.array([plant.current_time, log['distance'], log['distance_lead'], log['speed'], @@ -56,6 +62,10 @@ class Maneuver(): if self.ensure_start and log['v_rel'] > 0 and log['speeds'][-1] <= 0.1: print('LongitudinalPlanner not starting!') valid = False + if self.force_decel and log['speed'] > 1e-1 and log['acceleration'] > -0.04: + print('Not stopping with force decel') + valid = False + print("maneuver end", valid) return valid, np.array(logs) diff --git a/selfdrive/test/longitudinal_maneuvers/plant.py b/selfdrive/test/longitudinal_maneuvers/plant.py index e81510e9ba..8febbf4022 100755 --- a/selfdrive/test/longitudinal_maneuvers/plant.py +++ b/selfdrive/test/longitudinal_maneuvers/plant.py @@ -10,11 +10,12 @@ from selfdrive.modeld.constants import T_IDXS from selfdrive.controls.lib.longitudinal_planner import LongitudinalPlanner from selfdrive.controls.lib.radar_helpers import _LEAD_ACCEL_TAU -class Plant(): + +class Plant: messaging_initialized = False def __init__(self, lead_relevancy=False, speed=0.0, distance_lead=2.0, - only_lead2=False, only_radar=False): + enabled=True, only_lead2=False, only_radar=False, e2e=False, force_decel=False): self.rate = 1. / DT_MDL if not Plant.messaging_initialized: @@ -32,10 +33,13 @@ class Plant(): self.speeds = [] # lead car - self.distance_lead = distance_lead self.lead_relevancy = lead_relevancy - self.only_lead2=only_lead2 - self.only_radar=only_radar + self.distance_lead = distance_lead + self.enabled = enabled + self.only_lead2 = only_lead2 + self.only_radar = only_radar + self.e2e = e2e + self.force_decel = force_decel self.rk = Ratekeeper(self.rate, print_delay_threshold=100.0) self.ts = 1. / self.rate @@ -45,8 +49,9 @@ class Plant(): from selfdrive.car.honda.values import CAR from selfdrive.car.honda.interface import CarInterface - self.planner = LongitudinalPlanner(CarInterface.get_params(CAR.CIVIC), init_v=self.speed) + self.planner = LongitudinalPlanner(CarInterface.get_non_essential_params(CAR.CIVIC), init_v=self.speed) + @property def current_time(self): return float(self.rk.frame) / self.rate @@ -94,20 +99,20 @@ class Plant(): # Simulate model predicting slightly faster speed # this is to ensure lead policy is effective when model # does not predict slowdown in e2e mode - position = log.ModelDataV2.XYZTData.new_message() + position = log.XYZTData.new_message() position.x = [float(x) for x in (self.speed + 0.5) * np.array(T_IDXS)] model.modelV2.position = position - velocity = log.ModelDataV2.XYZTData.new_message() + velocity = log.XYZTData.new_message() velocity.x = [float(x) for x in (self.speed + 0.5) * np.ones_like(T_IDXS)] model.modelV2.velocity = velocity - acceleration = log.ModelDataV2.XYZTData.new_message() + acceleration = log.XYZTData.new_message() acceleration.x = [float(x) for x in np.zeros_like(T_IDXS)] model.modelV2.acceleration = acceleration - - - control.controlsState.longControlState = LongCtrlState.pid + control.controlsState.longControlState = LongCtrlState.pid if self.enabled else LongCtrlState.off control.controlsState.vCruise = float(v_cruise * 3.6) + control.controlsState.experimentalMode = self.e2e + control.controlsState.forceDecel = self.force_decel car_state.carState.vEgo = float(self.speed) car_state.carState.standstill = self.speed < 0.01 @@ -141,7 +146,7 @@ class Plant(): # print at 5hz if (self.rk.frame % (self.rate // 5)) == 0: print("%2.2f sec %6.2f m %6.2f m/s %6.2f m/s2 lead_rel: %6.2f m %6.2f m/s" - % (self.current_time(), self.distance, self.speed, self.acceleration, d_rel, v_rel)) + % (self.current_time, self.distance, self.speed, self.acceleration, d_rel, v_rel)) # ******** update prevs ******** diff --git a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py index c7c2915878..bc477ca9fe 100755 --- a/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py +++ b/selfdrive/test/longitudinal_maneuvers/test_longitudinal.py @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +import itertools import os +from parameterized import parameterized_class import unittest from common.params import Params @@ -8,120 +10,146 @@ from selfdrive.test.longitudinal_maneuvers.maneuver import Maneuver # TODO: make new FCW tests -maneuvers = [ - Maneuver( - 'approach stopped car at 20m/s, initial distance: 120m', - duration=20., - initial_speed=25., - lead_relevancy=True, - initial_distance_lead=120., - speed_lead_values=[30., 0.], - breakpoints=[0., 1.], - ), - Maneuver( - 'approach stopped car at 20m/s, initial distance 90m', - duration=20., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=90., - speed_lead_values=[20., 0.], - breakpoints=[0., 1.], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 1m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 35.0], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 2m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 25.0], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 3m/s^2', - duration=50., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - breakpoints=[0., 15., 21.66], - ), - Maneuver( - 'steady state following a car at 20m/s, then lead decel to 0mph at 3+m/s^2', - duration=40., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=35., - speed_lead_values=[20., 20., 0.], - prob_lead_values=[0., 1., 1.], - cruise_values=[20., 20., 20.], - breakpoints=[2., 2.01, 8.8], - ), - Maneuver( - "approach stopped car at 20m/s, with prob_lead_values", - duration=30., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=120., - speed_lead_values=[0.0, 0., 0.], - prob_lead_values=[0.0, 0., 1.], - cruise_values=[20., 20., 20.], - breakpoints=[0.0, 2., 2.01], - ), - Maneuver( - "approach slower cut-in car at 20m/s", - duration=20., - initial_speed=20., - lead_relevancy=True, - initial_distance_lead=50., - speed_lead_values=[15., 15.], - breakpoints=[1., 11.], - only_lead2=True, - ), - Maneuver( - "stay stopped behind radar override lead", - duration=20., - initial_speed=0., - lead_relevancy=True, - initial_distance_lead=10., - speed_lead_values=[0., 0.], - prob_lead_values=[0., 0.], - breakpoints=[1., 11.], - only_radar=True, - ), - Maneuver( - "NaN recovery", - duration=30., - initial_speed=15., - lead_relevancy=True, - initial_distance_lead=60., - speed_lead_values=[0., 0., 0.0], - breakpoints=[1., 1.01, 11.], - cruise_values=[float("nan"), 15., 15.], - ), - # controls relies on planner commanding to move for stock-ACC resume spamming - Maneuver( - "resume from a stop", - duration=20., - initial_speed=0., - lead_relevancy=True, - initial_distance_lead=STOP_DISTANCE, - speed_lead_values=[0., 0., 2.], - breakpoints=[1., 10., 15.], - ensure_start=True, - ), -] +def create_maneuvers(kwargs): + maneuvers = [ + Maneuver( + 'approach stopped car at 25m/s, initial distance: 120m', + duration=20., + initial_speed=25., + lead_relevancy=True, + initial_distance_lead=120., + speed_lead_values=[30., 0.], + breakpoints=[0., 1.], + **kwargs, + ), + Maneuver( + 'approach stopped car at 20m/s, initial distance 90m', + duration=20., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=90., + speed_lead_values=[20., 0.], + breakpoints=[0., 1.], + **kwargs, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 1m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 35.0], + **kwargs, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 2m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 25.0], + **kwargs, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 3m/s^2', + duration=50., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + breakpoints=[0., 15., 21.66], + **kwargs, + ), + Maneuver( + 'steady state following a car at 20m/s, then lead decel to 0mph at 3+m/s^2', + duration=40., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=35., + speed_lead_values=[20., 20., 0.], + prob_lead_values=[0., 1., 1.], + cruise_values=[20., 20., 20.], + breakpoints=[2., 2.01, 8.8], + **kwargs, + ), + Maneuver( + "approach stopped car at 20m/s, with prob_lead_values", + duration=30., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=120., + speed_lead_values=[0.0, 0., 0.], + prob_lead_values=[0.0, 0., 1.], + cruise_values=[20., 20., 20.], + breakpoints=[0.0, 2., 2.01], + **kwargs, + ), + Maneuver( + "approach slower cut-in car at 20m/s", + duration=20., + initial_speed=20., + lead_relevancy=True, + initial_distance_lead=50., + speed_lead_values=[15., 15.], + breakpoints=[1., 11.], + only_lead2=True, + **kwargs, + ), + Maneuver( + "stay stopped behind radar override lead", + duration=20., + initial_speed=0., + lead_relevancy=True, + initial_distance_lead=10., + speed_lead_values=[0., 0.], + prob_lead_values=[0., 0.], + breakpoints=[1., 11.], + only_radar=True, + **kwargs, + ), + Maneuver( + "NaN recovery", + duration=30., + initial_speed=15., + lead_relevancy=True, + initial_distance_lead=60., + speed_lead_values=[0., 0., 0.0], + breakpoints=[1., 1.01, 11.], + cruise_values=[float("nan"), 15., 15.], + **kwargs, + ), + Maneuver( + 'cruising at 25 m/s while disabled', + duration=20., + initial_speed=25., + lead_relevancy=False, + enabled=False, + **kwargs, + ), + ] + if not kwargs['force_decel']: + # controls relies on planner commanding to move for stock-ACC resume spamming + maneuvers.append(Maneuver( + "resume from a stop", + duration=20., + initial_speed=0., + lead_relevancy=True, + initial_distance_lead=STOP_DISTANCE, + speed_lead_values=[0., 0., 2.], + breakpoints=[1., 10., 15.], + ensure_start=True, + **kwargs, + )) + return maneuvers +@parameterized_class(("e2e", "force_decel"), itertools.product([True, False], repeat=2)) class LongitudinalControl(unittest.TestCase): + e2e: bool + force_decel: bool + @classmethod def setUpClass(cls): os.environ['SIMULATION'] = "1" @@ -133,29 +161,12 @@ class LongitudinalControl(unittest.TestCase): params.put_bool("Passive", bool(os.getenv("PASSIVE"))) params.put_bool("OpenpilotEnabledToggle", True) - # hack - def test_longitudinal_setup(self): - pass - - -def run_maneuver_worker(k): - def run(self): - params = Params() - - man = maneuvers[k] - params.put_bool("EndToEndLong", True) - print(man.title, ' in e2e mode') - valid, _ = man.evaluate() - self.assertTrue(valid, msg=man.title) - params.put_bool("EndToEndLong", False) - print(man.title, ' in acc mode') - valid, _ = man.evaluate() - self.assertTrue(valid, msg=man.title) - return run - -for k in range(len(maneuvers)): - setattr(LongitudinalControl, f"test_longitudinal_maneuvers_{k+1}", - run_maneuver_worker(k)) + def test_maneuver(self): + for maneuver in create_maneuvers({"e2e": self.e2e, "force_decel": self.force_decel}): + with self.subTest(title=maneuver.title, e2e=maneuver.e2e, force_decel=maneuver.force_decel): + print(maneuver.title, f'in {"e2e" if maneuver.e2e else "acc"} mode') + valid, _ = maneuver.evaluate() + self.assertTrue(valid) if __name__ == "__main__": diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index c14956b1b2..c96e624421 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -2,6 +2,7 @@ import bz2 import sys import math +import capnp import numbers import dictdiffer from collections import Counter @@ -30,20 +31,23 @@ def remove_ignored_fields(msg, ignore): continue for k in keys[:-1]: - try: - attr = getattr(msg, k) - except AttributeError: - break - else: - v = getattr(attr, keys[-1]) - if isinstance(v, bool): - val = False - elif isinstance(v, numbers.Number): - val = 0 + # indexing into list + if k.isdigit(): + attr = attr[int(k)] else: - raise NotImplementedError('Error ignoring field') - setattr(attr, keys[-1], val) - return msg.as_reader() + attr = getattr(attr, k) + + v = getattr(attr, keys[-1]) + if isinstance(v, bool): + val = False + elif isinstance(v, numbers.Number): + val = 0 + elif isinstance(v, (list, capnp.lib.capnp._DynamicListBuilder)): + val = [] + else: + raise NotImplementedError(f"Unknown type: {type(v)}") + setattr(attr, keys[-1], val) + return msg def get_field_tolerance(diff_field, field_tolerances): @@ -76,15 +80,15 @@ def compare_logs(log1, log2, ignore_fields=None, ignore_msgs=None, tolerance=Non diff = [] for msg1, msg2 in zip(log1, log2): if msg1.which() != msg2.which(): - print(msg1, msg2) + print(msg1.which(), msg2.which()) raise Exception("msgs not aligned between logs") - msg1_bytes = remove_ignored_fields(msg1, ignore_fields).as_builder().to_bytes() - msg2_bytes = remove_ignored_fields(msg2, ignore_fields).as_builder().to_bytes() + msg1 = remove_ignored_fields(msg1, ignore_fields) + msg2 = remove_ignored_fields(msg2, ignore_fields) - if msg1_bytes != msg2_bytes: - msg1_dict = msg1.to_dict(verbose=True) - msg2_dict = msg2.to_dict(verbose=True) + if msg1.to_bytes() != msg2.to_bytes(): + msg1_dict = msg1.as_reader().to_dict(verbose=True) + msg2_dict = msg2.as_reader().to_dict(verbose=True) dd = dictdiffer.diff(msg1_dict, msg2_dict, ignore=ignore_fields) diff --git a/selfdrive/test/process_replay/model_replay.py b/selfdrive/test/process_replay/model_replay.py index ecfacbf5d2..68cbb28aaf 100755 --- a/selfdrive/test/process_replay/model_replay.py +++ b/selfdrive/test/process_replay/model_replay.py @@ -20,14 +20,17 @@ from system.version import get_commit from tools.lib.framereader import FrameReader from tools.lib.logreader import LogReader -TEST_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36" -SEGMENT = 0 -MAX_FRAMES = 100 if PC else 1300 +TEST_ROUTE = "2f4452b03ccb98f0|2022-12-03--13-45-30" +SEGMENT = 6 +MAX_FRAMES = 100 if PC else 600 +NAV_FRAMES = 50 +NO_NAV = "NO_NAV" in os.environ # TODO: make map renderer work in CI SEND_EXTRA_INPUTS = bool(os.getenv("SEND_EXTRA_INPUTS", "0")) VIPC_STREAM = {"roadCameraState": VisionStreamType.VISION_STREAM_ROAD, "driverCameraState": VisionStreamType.VISION_STREAM_DRIVER, "wideRoadCameraState": VisionStreamType.VISION_STREAM_WIDE_ROAD} + def get_log_fn(ref_commit, test_route): return f"{test_route}_model_tici_{ref_commit}.bz2" @@ -39,6 +42,58 @@ def replace_calib(msg, calib): return msg +def nav_model_replay(lr): + sm = messaging.SubMaster(['navModel', 'navThumbnail', 'mapRenderState']) + pm = messaging.PubMaster(['liveLocationKalman', 'navRoute']) + + nav = [m for m in lr if m.which() == 'navRoute'] + llk = [m for m in lr if m.which() == 'liveLocationKalman'] + assert len(nav) > 0 and len(llk) >= NAV_FRAMES and nav[0].logMonoTime < llk[-NAV_FRAMES].logMonoTime + + log_msgs = [] + try: + assert "MAPBOX_TOKEN" in os.environ + os.environ['MAP_RENDER_TEST_MODE'] = '1' + managed_processes['mapsd'].start() + managed_processes['navmodeld'].start() + + # setup position and route + for _ in range(10): + for s in (llk[-NAV_FRAMES], nav[0]): + pm.send(s.which(), s.as_builder().to_bytes()) + sm.update(1000) + if sm.updated['navModel']: + break + time.sleep(1) + + if not sm.updated['navModel']: + raise Exception("no navmodeld outputs, failed to initialize") + + # drain + time.sleep(2) + sm.update(0) + + # run replay + for n in range(len(llk) - NAV_FRAMES, len(llk)): + pm.send(llk[n].which(), llk[n].as_builder().to_bytes()) + m = messaging.recv_one(sm.sock['navThumbnail']) + assert m is not None, f"no navThumbnail, frame={n}" + log_msgs.append(m) + + m = messaging.recv_one(sm.sock['mapRenderState']) + assert m is not None, f"no mapRenderState, frame={n}" + log_msgs.append(m) + + m = messaging.recv_one(sm.sock['navModel']) + assert m is not None, f"no navModel response, frame={n}" + log_msgs.append(m) + finally: + managed_processes['mapsd'].stop() + managed_processes['navmodeld'].stop() + + return log_msgs + + def model_replay(lr, frs): if not PC: spinner = Spinner() @@ -149,13 +204,15 @@ if __name__ == "__main__": # 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")), - 'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="ecamera")) + 'roadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="fcamera"), readahead=True), + 'driverCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="dcamera"), readahead=True), + 'wideRoadCameraState': FrameReader(get_url(TEST_ROUTE, SEGMENT, log_type="ecamera"), readahead=True) } - # run replay + # run replays log_msgs = model_replay(lr, frs) + if not NO_NAV: + log_msgs += nav_model_replay(lr) # get diff failed = False @@ -164,15 +221,32 @@ if __name__ == "__main__": ref_commit = f.read().strip() log_fn = get_log_fn(ref_commit, TEST_ROUTE) try: - cmp_log = list(LogReader(BASE_URL + log_fn))[:2*MAX_FRAMES] + expected_msgs = 2*MAX_FRAMES + if not NO_NAV: + expected_msgs += NAV_FRAMES*3 + cmp_log = list(LogReader(BASE_URL + log_fn))[:expected_msgs] ignore = [ 'logMonoTime', 'modelV2.frameDropPerc', 'modelV2.modelExecutionTime', 'driverStateV2.modelExecutionTime', - 'driverStateV2.dspExecutionTime' + 'driverStateV2.dspExecutionTime', + 'navModel.dspExecutionTime', + 'navModel.modelExecutionTime', + 'navThumbnail.timestampEof', + 'mapRenderState.locationMonoTime', + 'mapRenderState.renderTime', ] + if PC: + ignore += [ + 'modelV2.laneLines.0.t', + 'modelV2.laneLines.1.t', + 'modelV2.laneLines.2.t', + 'modelV2.laneLines.3.t', + 'modelV2.roadEdges.0.t', + 'modelV2.roadEdges.1.t', + ] # TODO this tolerance is absurdly large tolerance = 2.0 if PC else None results: Any = {TEST_ROUTE: {}} diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 5bb045ec29..bd026f5710 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -865885fc49b2766326568e5cc7ec06be8a3f6fad +82db08d52b155336e9a1dadd11485d5acdf2eba0 diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index 9d37be4a56..2b9096b423 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -180,7 +180,7 @@ def fingerprint(msgs, fsm, can_sock, fingerprint): def get_car_params(msgs, fsm, can_sock, fingerprint): if fingerprint: CarInterface, _, _ = interfaces[fingerprint] - CP = CarInterface.get_params(fingerprint) + CP = CarInterface.get_non_essential_params(fingerprint) else: can = FakeSocket(wait=False) sendcan = FakeSocket(wait=False) @@ -188,7 +188,7 @@ def get_car_params(msgs, fsm, can_sock, fingerprint): canmsgs = [msg for msg in msgs if msg.which() == 'can'] for m in canmsgs[:300]: can.send(m.as_builder().to_bytes()) - _, CP = get_car(can, sendcan) + _, CP = get_car(can, sendcan, Params().get_bool("ExperimentalLongitudinalEnabled")) Params().put("CarParams", CP.to_bytes()) @@ -204,7 +204,7 @@ def controlsd_rcv_callback(msg, CP, cfg, fsm): def radar_rcv_callback(msg, CP, cfg, fsm): if msg.which() != "can": return [], False - elif CP.radarOffCan: + elif CP.radarUnavailable: return ["radarState", "liveTracks"], True radar_msgs = {"honda": [0x445], "toyota": [0x19f, 0x22f], "gm": [0x474], @@ -238,21 +238,14 @@ def torqued_rcv_callback(msg, CP, cfg, fsm): return recv_socks, fsm.frame == 0 or msg.which() == 'liveLocationKalman' -def ublox_rcv_callback(msg): +def ublox_rcv_callback(msg, CP, cfg, fsm): msg_class, msg_id = msg.ubloxRaw[2:4] if (msg_class, msg_id) in {(1, 7 * 16)}: - return ["gpsLocationExternal"] + return ["gpsLocationExternal"], True elif (msg_class, msg_id) in {(2, 1 * 16 + 5), (10, 9)}: - return ["ubloxGnss"] + return ["ubloxGnss"], True else: - return [] - - -def laika_rcv_callback(msg, CP, cfg, fsm): - if msg.which() == 'ubloxGnss' and msg.ubloxGnss.which() == "measurementReport": - return ["gnssMeasurements"], True - else: - return [], True + return [], False CONFIGS = [ @@ -290,7 +283,7 @@ CONFIGS = [ ProcessConfig( proc_name="plannerd", pub_sub={ - "modelV2": ["lateralPlan", "longitudinalPlan"], + "modelV2": ["lateralPlan", "longitudinalPlan", "uiPlan"], "carControl": [], "carState": [], "controlsState": [], "radarState": [], }, ignore=["logMonoTime", "valid", "longitudinalPlan.processingDelay", "longitudinalPlan.solverExecutionTime", "lateralPlan.solverExecutionTime"], @@ -329,7 +322,7 @@ CONFIGS = [ pub_sub={ "cameraOdometry": ["liveLocationKalman"], "accelerometer": [], "gyroscope": [], - "gpsLocationExternal": [], "liveCalibration": [], "carState": [], + "gpsLocationExternal": [], "liveCalibration": [], "carState": [], "gpsLocation": [], }, ignore=["logMonoTime", "valid"], init_callback=get_car_params, @@ -362,29 +355,16 @@ CONFIGS = [ ), ProcessConfig( proc_name="laikad", - subtest_name="Offline", pub_sub={ "ubloxGnss": ["gnssMeasurements"], + "qcomGnss": ["gnssMeasurements"], "clocks": [] }, ignore=["logMonoTime"], init_callback=get_car_params, - should_recv_callback=laika_rcv_callback, + should_recv_callback=None, tolerance=NUMPY_TOLERANCE, - fake_pubsubmaster=True, - environ={"LAIKAD_NO_INTERNET": "1"}, - ), - ProcessConfig( - proc_name="laikad", - pub_sub={ - "ubloxGnss": ["gnssMeasurements"], - "clocks": [] - }, - ignore=["logMonoTime"], - init_callback=get_car_params, - should_recv_callback=laika_rcv_callback, - tolerance=NUMPY_TOLERANCE, - fake_pubsubmaster=True, + fake_pubsubmaster=False, ), ProcessConfig( proc_name="torqued", @@ -406,10 +386,10 @@ def replay_process(cfg, lr, fingerprint=None): if cfg.fake_pubsubmaster: return python_replay_process(cfg, lr, fingerprint) else: - return cpp_replay_process(cfg, lr, fingerprint) + return replay_process_with_sockets(cfg, lr, fingerprint) -def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): +def setup_env(simulation=False, CP=None, cfg=None, controlsState=None, lr=None): params = Params() params.clear_all() params.put_bool("OpenpilotEnabledToggle", True) @@ -417,12 +397,19 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): params.put_bool("DisengageOnAccelerator", True) params.put_bool("WideCameraOnly", False) params.put_bool("DisableLogging", False) - params.put_bool("UbloxAvailable", True) os.environ["NO_RADAR_SLEEP"] = "1" os.environ["REPLAY"] = "1" - os.environ['SKIP_FW_QUERY'] = "" - os.environ['FINGERPRINT'] = "" + os.environ["SKIP_FW_QUERY"] = "" + os.environ["FINGERPRINT"] = "" + + if lr is not None: + services = {m.which() for m in lr} + params.put_bool("UbloxAvailable", "ubloxGnss" in services) + + if lr is not None: + services = {m.which() for m in lr} + params.put_bool("UbloxAvailable", "ubloxGnss" in services) if cfg is not None: # Clear all custom processConfig environment variables @@ -456,6 +443,9 @@ def setup_env(simulation=False, CP=None, cfg=None, controlsState=None): os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = CP.carFingerprint + if CP.openpilotLongitudinalControl: + params.put_bool("ExperimentalLongitudinalEnabled", True) + def python_replay_process(cfg, lr, fingerprint=None): sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] @@ -486,10 +476,10 @@ def python_replay_process(cfg, lr, fingerprint=None): if fingerprint is not None: os.environ['SKIP_FW_QUERY'] = "1" os.environ['FINGERPRINT'] = fingerprint - setup_env(cfg=cfg, controlsState=controlsState) + setup_env(cfg=cfg, controlsState=controlsState, lr=lr) else: CP = [m for m in lr if m.which() == 'carParams'][0].carParams - setup_env(CP=CP, cfg=cfg, controlsState=controlsState) + setup_env(CP=CP, cfg=cfg, controlsState=controlsState, lr=lr) assert(type(managed_processes[cfg.proc_name]) is PythonProcess) managed_processes[cfg.proc_name].prepare() @@ -518,7 +508,7 @@ def python_replay_process(cfg, lr, fingerprint=None): recv_socks, should_recv = cfg.should_recv_callback(msg, CP, cfg, fsm) else: recv_socks = [s for s in cfg.pub_sub[msg.which()] if - (fsm.frame + 1) % int(service_list[msg.which()].frequency / service_list[s].frequency) == 0] + (fsm.frame + 1) % max(1, int(service_list[msg.which()].frequency / service_list[s].frequency)) == 0] should_recv = bool(len(recv_socks)) if msg.which() == 'can': @@ -541,49 +531,53 @@ def python_replay_process(cfg, lr, fingerprint=None): return log_msgs -def cpp_replay_process(cfg, lr, fingerprint=None): - sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] # We get responses here +def replay_process_with_sockets(cfg, lr, fingerprint=None): + sub_sockets = [s for _, sub in cfg.pub_sub.items() for s in sub] pm = messaging.PubMaster(cfg.pub_sub.keys()) all_msgs = sorted(lr, key=lambda msg: msg.logMonoTime) pub_msgs = [msg for msg in all_msgs if msg.which() in list(cfg.pub_sub.keys())] - log_msgs = [] # We need to fake SubMaster alive since we can't inject a fake clock - setup_env(simulation=True, cfg=cfg) + setup_env(simulation=True, cfg=cfg, lr=lr) + + if cfg.proc_name == "laikad": + ublox = Params().get_bool("UbloxAvailable") + keys = set(cfg.pub_sub.keys()) - ({"qcomGnss", } if ublox else {"ubloxGnss", }) + pub_msgs = [msg for msg in pub_msgs if msg.which() in keys] managed_processes[cfg.proc_name].prepare() managed_processes[cfg.proc_name].start() + log_msgs = [] try: - with Timeout(TIMEOUT): - while not all(pm.all_readers_updated(s) for s in cfg.pub_sub.keys()): + # Wait for process to startup + with Timeout(10, error_msg=f"timed out waiting for process to start: {repr(cfg.proc_name)}"): + while not any(pm.all_readers_updated(s) for s in cfg.pub_sub.keys()): time.sleep(0) - # Make sure all subscribers are connected - sockets = {s: messaging.sub_sock(s, timeout=2000) for s in sub_sockets} - for s in sub_sockets: - messaging.recv_one_or_none(sockets[s]) + # Make sure all subscribers are connected + sockets = {s: messaging.sub_sock(s, timeout=2000) for s in sub_sockets} + for s in sub_sockets: + messaging.recv_one_or_none(sockets[s]) - for i, msg in enumerate(pub_msgs): + # Do the replay + cnt = 0 + for msg in pub_msgs: + with Timeout(TIMEOUT, error_msg=f"timed out testing process {repr(cfg.proc_name)}, {cnt}/{len(pub_msgs)} msgs done"): pm.send(msg.which(), msg.as_builder()) + while not pm.all_readers_updated(msg.which()): + time.sleep(0) - resp_sockets = cfg.pub_sub[msg.which()] if cfg.should_recv_callback is None else cfg.should_recv_callback(msg) + resp_sockets = cfg.pub_sub[msg.which()] + if cfg.should_recv_callback is not None: + resp_sockets, _ = cfg.should_recv_callback(msg, None, None, None) for s in resp_sockets: - response = messaging.recv_one(sockets[s]) - - if response is None: - print(f"Warning, no response received {i}") - else: - - response = response.as_builder() - response.logMonoTime = msg.logMonoTime - response = response.as_reader() - log_msgs.append(response) - - if not len(resp_sockets): # We only need to wait if we didn't already wait for a response - while not pm.all_readers_updated(msg.which()): - time.sleep(0) + m = messaging.recv_one_retry(sockets[s]) + m = m.as_builder() + m.logMonoTime = msg.logMonoTime + log_msgs.append(m.as_reader()) + cnt += 1 finally: managed_processes[cfg.proc_name].signal(signal.SIGKILL) managed_processes[cfg.proc_name].stop() diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 0b4081595b..556781738d 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1c1287691cbdd866d9e435d5d27f59f6a027d270 +50f1e873095fe2462d2aadb9c401bda76759c01c diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 683387dce8..a2dd938e2f 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -18,21 +18,23 @@ from tools.lib.logreader import LogReader source_segments = [ ("BODY", "937ccb7243511b65|2022-05-24--16-03-09--1"), # COMMA.BODY ("HYUNDAI", "02c45f73a2e5c6e9|2021-01-01--19-08-22--1"), # HYUNDAI.SONATA - ("HYUNDAI2", "d824e27e8c60172c|2022-09-13--11-26-50--2"), # HYUNDAI.KIA_EV6 + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), # HYUNDAI.KIA_EV6 (+ QCOM GPS) ("TOYOTA", "0982d79ebb0de295|2021-01-04--17-13-21--13"), # TOYOTA.PRIUS (INDI) ("TOYOTA2", "0982d79ebb0de295|2021-01-03--20-03-36--6"), # TOYOTA.RAV4 (LQR) ("TOYOTA3", "f7d7e3538cda1a2a|2021-08-16--08-55-34--6"), # TOYOTA.COROLLA_TSS2 ("HONDA", "eb140f119469d9ab|2021-06-12--10-46-24--27"), # HONDA.CIVIC (NIDEC) ("HONDA2", "7d2244f34d1bbcda|2021-06-25--12-25-37--26"), # HONDA.ACCORD (BOSCH) - ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA + ("CHRYSLER", "4deb27de11bee626|2021-02-20--11-28-55--8"), # CHRYSLER.PACIFICA_2018_HYBRID ("RAM", "2f4452b03ccb98f0|2022-09-07--13-55-08--10"), # CHRYSLER.RAM_1500 ("SUBARU", "341dccd5359e3c97|2022-09-12--10-35-33--3"), # SUBARU.OUTBACK ("GM", "0c58b6a25109da2b|2021-02-23--16-35-50--11"), # GM.VOLT + ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), # GM.BOLT_EUV ("NISSAN", "35336926920f3571|2021-02-12--18-38-48--46"), # NISSAN.XTRAIL ("VOLKSWAGEN", "de9592456ad7d144|2021-06-29--11-00-15--6"), # VOLKSWAGEN.GOLF ("MAZDA", "bd6a637565e91581|2021-10-30--15-14-53--4"), # MAZDA.CX9_2021 # Enable when port is tested and dashcamOnly is no longer set + #("FORD", "54827bf84c38b14f|2023-01-26--21-59-07--4"), # FORD.BRONCO_SPORT_MK1 #("TESLA", "bb50caf5f0945ab1|2021-06-19--17-20-18--3"), # TESLA.AP2_MODELS #("VOLKSWAGEN2", "3cfdec54aa035f3f|2022-07-19--23-45-10--2"), # VOLKSWAGEN.PASSAT_NMS ] @@ -40,7 +42,7 @@ source_segments = [ segments = [ ("BODY", "regenFA002A80700|2022-09-27--15-37-02--0"), ("HYUNDAI", "regenBE53A59065B|2022-09-27--16-52-03--0"), - ("HYUNDAI2", "regenFA8B5CA9840|2022-10-12--21-47-06--0"), + ("HYUNDAI2", "d545129f3ca90f28|2022-11-07--20-43-08--3"), ("TOYOTA", "regen929C5790007|2022-09-27--16-27-47--0"), ("TOYOTA2", "regenEA3950D7F22|2022-09-27--15-43-24--0"), ("TOYOTA3", "regen89026F6BD8D|2022-09-27--15-45-37--0"), @@ -50,6 +52,7 @@ segments = [ ("RAM", "regen20490083AE7|2022-09-27--15-53-15--0"), ("SUBARU", "regen1E72BBDCED5|2022-09-27--15-55-31--0"), ("GM", "regen45B05A80EF6|2022-09-27--15-57-22--0"), + ("GM2", "376bf99325883932|2022-10-27--13-41-22--1"), ("NISSAN", "regenC19D899B46D|2022-09-27--15-59-13--0"), ("VOLKSWAGEN", "regenD8F7AC4BD0D|2022-09-27--16-41-45--0"), ("MAZDA", "regenFC3F9ECBB64|2022-09-27--16-03-09--0"), @@ -67,7 +70,7 @@ def run_test_process(data): res = None if not args.upload_only: lr = LogReader.from_bytes(lr_dat) - res, log_msgs = test_process(cfg, lr, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) + res, log_msgs = test_process(cfg, lr, segment, ref_log_path, cur_log_fn, args.ignore_fields, args.ignore_msgs) # save logs so we can upload when updating refs save_log(cur_log_fn, log_msgs) @@ -85,7 +88,7 @@ def get_log_data(segment): return (segment, f.read()) -def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): +def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=None, ignore_msgs=None): if ignore_fields is None: ignore_fields = [] if ignore_msgs is None: @@ -93,7 +96,10 @@ def test_process(cfg, lr, ref_log_path, new_log_path, ignore_fields=None, ignore ref_log_msgs = list(LogReader(ref_log_path)) - log_msgs = replay_process(cfg, lr) + try: + log_msgs = replay_process(cfg, lr) + except Exception as e: + raise Exception("failed on segment: " + segment) from e # check to make sure openpilot is engaged in the route if cfg.proc_name == "controlsd": diff --git a/selfdrive/test/setup_device_ci.sh b/selfdrive/test/setup_device_ci.sh index a9c6527970..b7d586a83b 100755 --- a/selfdrive/test/setup_device_ci.sh +++ b/selfdrive/test/setup_device_ci.sh @@ -29,6 +29,7 @@ sudo abctl --set_success # patch sshd config sudo mount -o rw,remount / +echo tici-$(cat /proc/cmdline | sed -e 's/^.*androidboot.serialno=//' -e 's/ .*$//') | sudo tee /etc/hostname sudo sed -i "s,/data/params/d/GithubSshKeys,/usr/comma/setup_keys," /etc/ssh/sshd_config sudo systemctl daemon-reload sudo systemctl restart ssh @@ -55,11 +56,12 @@ cd $SOURCE_DIR rm -f .git/index.lock git reset --hard -git fetch --verbose origin $GIT_COMMIT +git fetch --no-tags --no-recurse-submodules -j4 --verbose --depth 1 origin $GIT_COMMIT find . -maxdepth 1 -not -path './.git' -not -name '.' -not -name '..' -exec rm -rf '{}' \; git reset --hard $GIT_COMMIT git checkout $GIT_COMMIT git clean -xdff +git submodule sync git submodule update --init --recursive git submodule foreach --recursive "git reset --hard && git clean -xdff" @@ -67,6 +69,7 @@ git lfs pull (ulimit -n 65535 && git lfs prune) echo "git checkout done, t=$SECONDS" +du -hs $SOURCE_DIR $SOURCE_DIR/.git rsync -a --delete $SOURCE_DIR $TEST_DIR diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 0d3d7b367a..07a3c61bc5 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -15,18 +15,18 @@ from common.basedir import BASEDIR from common.timeout import Timeout from common.params import Params from selfdrive.controls.lib.events import EVENTS, ET -from selfdrive.loggerd.config import ROOT +from system.loggerd.config import ROOT from selfdrive.test.helpers import set_params_enabled, release_only from tools.lib.logreader import LogReader # Baseline CPU usage by process PROCS = { - "selfdrive.controls.controlsd": 35.0, + "selfdrive.controls.controlsd": 39.0, "./loggerd": 10.0, "./encoderd": 17.0, "./camerad": 14.5, "./locationd": 9.1, - "selfdrive.controls.plannerd": 11.7, + "selfdrive.controls.plannerd": 16.5, "./_ui": 19.2, "selfdrive.locationd.paramsd": 9.0, "./_sensord": 12.0, @@ -202,9 +202,9 @@ class TestOnroad(unittest.TestCase): print(result) self.assertGreater(len(ts), 20*50, "insufficient samples") - self.assertLess(max(ts), 30.) + #self.assertLess(max(ts), 30.) self.assertLess(np.mean(ts), 10.) - self.assertLess(np.std(ts), 5.) + #self.assertLess(np.std(ts), 5.) def test_cpu_usage(self): proclogs = [m for m in self.lr if m.which() == 'procLog'] diff --git a/selfdrive/test/test_valgrind_replay.py b/selfdrive/test/test_valgrind_replay.py index 238b822ec9..46dd4901e5 100755 --- a/selfdrive/test/test_valgrind_replay.py +++ b/selfdrive/test/test_valgrind_replay.py @@ -28,7 +28,7 @@ CONFIGS = [ }, ignore=[], command="./ubloxd", - path="selfdrive/locationd/", + path="system/ubloxd", segment="0375fdf7b1ce594d|2019-06-13--08-32-25--3", wait_for_response=True ), diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 201ffb745a..a2b971999c 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -1,6 +1,8 @@ #!/usr/bin/env python3 +from functools import lru_cache import sys import subprocess +from tqdm import tqdm from azure.storage.blob import BlockBlobService # pylint: disable=import-error from selfdrive.car.tests.routes import routes as test_car_models_routes @@ -10,11 +12,11 @@ from xx.chffr.lib.storage import _DATA_ACCOUNT_PRODUCTION, _DATA_ACCOUNT_CI, _DA SOURCES = [ (_DATA_ACCOUNT_PRODUCTION, _DATA_BUCKET_PRODUCTION), - (_DATA_ACCOUNT_PRODUCTION, "preserve"), (_DATA_ACCOUNT_CI, "commadataci"), ] +@lru_cache def get_azure_keys(): dest_key = azureutil.get_user_token(_DATA_ACCOUNT_CI, "openpilotci") source_keys = [azureutil.get_user_token(account, bucket) for account, bucket in SOURCES] @@ -82,7 +84,7 @@ if __name__ == "__main__": to_sync.extend([rt.route for rt in test_car_models_routes]) to_sync.extend([s[1].rsplit('--', 1)[0] for s in replay_segments]) - for r in to_sync: + for r in tqdm(to_sync): if not sync_to_ci_public(r): failed_routes.append(r) diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 7834569088..85e9510eb7 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -1,24 +1,23 @@ import threading from typing import Optional -from cereal import log from common.params import Params, put_nonblocking from common.realtime import sec_since_boot from system.hardware import HARDWARE from system.swaglog import cloudlog from selfdrive.statsd import statlog -CAR_VOLTAGE_LOW_PASS_K = 0.091 # LPF gain for 5s tau (dt/tau / (dt/tau + 1)) +CAR_VOLTAGE_LOW_PASS_K = 0.011 # LPF gain for 45s tau (dt/tau / (dt/tau + 1)) -# A C2 uses about 1W while idling, and 30h seens like a good shutoff for most cars # While driving, a battery charges completely in about 30-60 minutes CAR_BATTERY_CAPACITY_uWh = 30e6 CAR_CHARGING_RATE_W = 45 -VBATT_PAUSE_CHARGING = 11.0 # Lower limit on the LPF car battery voltage +VBATT_PAUSE_CHARGING = 11.8 # Lower limit on the LPF car battery voltage VBATT_INSTANT_PAUSE_CHARGING = 7.0 # Lower limit on the instant car battery voltage measurements to avoid triggering on instant power loss MAX_TIME_OFFROAD_S = 30*3600 MIN_ON_TIME_S = 3600 +VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 60 class PowerMonitoring: def __init__(self): @@ -39,12 +38,12 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) # Calculation tick - def calculate(self, peripheralState, ignition): + def calculate(self, voltage: Optional[int], ignition: bool): try: now = sec_since_boot() # If peripheralState is None, we're probably not in a car, so we don't care - if peripheralState is None or peripheralState.pandaType == log.PandaState.PandaType.unknown: + if voltage is None: with self.integration_lock: self.last_measurement_time = None self.next_pulsed_measurement_time = None @@ -52,8 +51,8 @@ class PowerMonitoring: return # Low-pass battery voltage - self.car_voltage_instant_mV = peripheralState.voltage - self.car_voltage_mV = ((peripheralState.voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) + self.car_voltage_instant_mV = voltage + self.car_voltage_mV = ((voltage * CAR_VOLTAGE_LOW_PASS_K) + (self.car_voltage_mV * (1 - CAR_VOLTAGE_LOW_PASS_K))) statlog.gauge("car_voltage", self.car_voltage_mV / 1e3) # Cap the car battery power and save it in a param every 10-ish seconds @@ -115,8 +114,12 @@ class PowerMonitoring: now = sec_since_boot() should_shutdown = False - should_shutdown |= (now - offroad_timestamp) > MAX_TIME_OFFROAD_S - should_shutdown |= (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3)) and (self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3)) + offroad_time = (now - offroad_timestamp) + low_voltage_shutdown = (self.car_voltage_mV < (VBATT_PAUSE_CHARGING * 1e3) and + self.car_voltage_instant_mV > (VBATT_INSTANT_PAUSE_CHARGING * 1e3) and + offroad_time > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S) + should_shutdown |= offroad_time > MAX_TIME_OFFROAD_S + should_shutdown |= low_voltage_shutdown should_shutdown |= (self.car_battery_capacity_uWh <= 0) should_shutdown &= not ignition should_shutdown &= (not self.params.get_bool("DisablePowerDown")) diff --git a/selfdrive/thermald/tests/test_power_monitoring.py b/selfdrive/thermald/tests/test_power_monitoring.py index 5d7463d455..d41e92454b 100755 --- a/selfdrive/thermald/tests/test_power_monitoring.py +++ b/selfdrive/thermald/tests/test_power_monitoring.py @@ -1,10 +1,7 @@ #!/usr/bin/env python3 import unittest from unittest.mock import patch -from parameterized import parameterized -from cereal import log -import cereal.messaging as messaging from common.params import Params params = Params() @@ -21,7 +18,8 @@ with patch("common.realtime.sec_since_boot", new=mock_sec_since_boot): CAR_CHARGING_RATE_W, VBATT_PAUSE_CHARGING TEST_DURATION_S = 50 -ALL_PANDA_TYPES = [(log.PandaState.PandaType.dos,)] +GOOD_VOLTAGE = 12 * 1e3 +VOLTAGE_BELOW_PAUSE_CHARGING = (VBATT_PAUSE_CHARGING - 1) * 1e3 def pm_patch(name, value, constant=False): if constant: @@ -34,12 +32,6 @@ class TestPowerMonitoring(unittest.TestCase): params.remove("CarBatteryCapacity") params.remove("DisablePowerDown") - def mock_peripheralState(self, hw_type, car_voltage=12): - ps = messaging.new_message('peripheralState').peripheralState - ps.pandaType = hw_type - ps.voltage = car_voltage * 1e3 - return ps - # Test to see that it doesn't do anything when pandaState is None def test_pandaState_present(self): pm = PowerMonitoring() @@ -49,75 +41,68 @@ class TestPowerMonitoring(unittest.TestCase): self.assertEqual(pm.get_car_battery_capacity(), (CAR_BATTERY_CAPACITY_uWh / 10)) # Test to see that it doesn't integrate offroad when ignition is True - @parameterized.expand(ALL_PANDA_TYPES) - def test_offroad_ignition(self, hw_type): + def test_offroad_ignition(self): pm = PowerMonitoring() for _ in range(10): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) self.assertEqual(pm.get_power_used(), 0) # Test to see that it integrates with discharging battery - @parameterized.expand(ALL_PANDA_TYPES) - def test_offroad_integration_discharging(self, hw_type): + def test_offroad_integration_discharging(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) expected_power_usage = ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_power_used() - expected_power_usage), 10) # Test to check positive integration of car_battery_capacity - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_onroad(self, hw_type): + def test_car_battery_integration_onroad(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = 0 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) expected_capacity = ((TEST_DURATION_S/3600) * CAR_CHARGING_RATE_W * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) # Test to check positive integration upper limit - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_upper_limit(self, hw_type): + def test_car_battery_integration_upper_limit(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh - 1000 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), True) + pm.calculate(GOOD_VOLTAGE, True) estimated_capacity = CAR_BATTERY_CAPACITY_uWh + (CAR_CHARGING_RATE_W / 3600 * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) # Test to check negative integration of car_battery_capacity - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_offroad(self, hw_type): + def test_car_battery_integration_offroad(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) expected_capacity = CAR_BATTERY_CAPACITY_uWh - ((TEST_DURATION_S/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - expected_capacity), 10) # Test to check negative integration lower limit - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_battery_integration_lower_limit(self, hw_type): + def test_car_battery_integration_lower_limit(self): POWER_DRAW = 4 with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = 1000 for _ in range(TEST_DURATION_S + 1): - pm.calculate(self.mock_peripheralState(hw_type), False) + pm.calculate(GOOD_VOLTAGE, False) estimated_capacity = 0 - ((1/3600) * POWER_DRAW * 1e6) self.assertLess(abs(pm.get_car_battery_capacity() - estimated_capacity), 10) # Test to check policy of stopping charging after MAX_TIME_OFFROAD_S - @parameterized.expand(ALL_PANDA_TYPES) - def test_max_time_offroad(self, hw_type): + def test_max_time_offroad(self): MOCKED_MAX_OFFROAD_TIME = 3600 POWER_DRAW = 0 # To stop shutting down for other reasons with pm_patch("MAX_TIME_OFFROAD_S", MOCKED_MAX_OFFROAD_TIME, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): @@ -125,28 +110,29 @@ class TestPowerMonitoring(unittest.TestCase): pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh start_time = ssb ignition = False - peripheralState = self.mock_peripheralState(hw_type) while ssb <= start_time + MOCKED_MAX_OFFROAD_TIME: - pm.calculate(peripheralState, ignition) + pm.calculate(GOOD_VOLTAGE, ignition) if (ssb - start_time) % 1000 == 0 and ssb < start_time + MOCKED_MAX_OFFROAD_TIME: self.assertFalse(pm.should_shutdown(ignition, True, start_time, False)) self.assertTrue(pm.should_shutdown(ignition, True, start_time, False)) # Test to check policy of stopping charging when the car voltage is too low - @parameterized.expand(ALL_PANDA_TYPES) - def test_car_voltage(self, hw_type): + def test_car_voltage(self): POWER_DRAW = 0 # To stop shutting down for other reasons TEST_TIME = 100 - with pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): + VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S = 50 + with pm_patch("VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S", VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S, constant=True), pm_patch("HARDWARE.get_current_power_draw", POWER_DRAW): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(hw_type, car_voltage=(VBATT_PAUSE_CHARGING - 1)) + start_time = ssb for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: - self.assertEqual(pm.should_shutdown(ignition, True, ssb, True), (pm.car_voltage_mV < VBATT_PAUSE_CHARGING*1e3)) - self.assertTrue(pm.should_shutdown(ignition, True, ssb, True)) + self.assertEqual(pm.should_shutdown(ignition, True, start_time, True), + (pm.car_voltage_mV < VBATT_PAUSE_CHARGING*1e3 and + (ssb - start_time) > VOLTAGE_SHUTDOWN_MIN_OFFROAD_TIME_S)) + self.assertTrue(pm.should_shutdown(ignition, True, start_time, True)) # Test to check policy of not stopping charging when DisablePowerDown is set def test_disable_power_down(self): @@ -157,9 +143,8 @@ class TestPowerMonitoring(unittest.TestCase): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) @@ -172,9 +157,8 @@ class TestPowerMonitoring(unittest.TestCase): pm = PowerMonitoring() pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = True - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, True, ssb, False)) @@ -188,9 +172,8 @@ class TestPowerMonitoring(unittest.TestCase): pm.car_battery_capacity_uWh = CAR_BATTERY_CAPACITY_uWh ignition = False - peripheralState = self.mock_peripheralState(log.PandaState.PandaType.uno, car_voltage=(VBATT_PAUSE_CHARGING - 1)) for i in range(TEST_TIME): - pm.calculate(peripheralState, ignition) + pm.calculate(VOLTAGE_BELOW_PAUSE_CHARGING, ignition) if i % 10 == 0: self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) self.assertFalse(pm.should_shutdown(ignition, False, ssb, False)) diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index 89b81f06ec..531e0b04d1 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -18,7 +18,7 @@ from common.params import Params from common.realtime import DT_TRML, sec_since_boot from selfdrive.controls.lib.alertmanager import set_offroad_alert from system.hardware import HARDWARE, TICI, AGNOS -from selfdrive.loggerd.config import get_available_percent +from system.loggerd.config import get_available_percent from selfdrive.statsd import statlog from system.swaglog import cloudlog from selfdrive.thermald.power_monitoring import PowerMonitoring @@ -347,7 +347,8 @@ def thermald_thread(end_event, hw_queue): off_ts = sec_since_boot() # Offroad power monitoring - power_monitor.calculate(peripheralState, onroad_conditions["ignition"]) + voltage = None if peripheralState.pandaType == log.PandaState.PandaType.unknown else peripheralState.voltage + power_monitor.calculate(voltage, onroad_conditions["ignition"]) msg.deviceState.offroadPowerUsageUwh = power_monitor.get_power_used() msg.deviceState.carBatteryCapacityUwh = max(0, power_monitor.get_car_battery_capacity()) current_power_draw = HARDWARE.get_current_power_draw() diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 61a575f141..65fb45b678 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -10,7 +10,7 @@ import glob from typing import NoReturn from common.file_helpers import mkdirs_exists_ok -from selfdrive.loggerd.config import ROOT +from system.loggerd.config import ROOT import selfdrive.sentry as sentry from system.swaglog import cloudlog from system.version import get_commit diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index 84e055752a..a8c8463bd7 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -1,4 +1,5 @@ import os +import json Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') @@ -58,7 +59,7 @@ qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) qt_src = ["main.cc", "qt/sidebar.cc", "qt/onroad.cc", "qt/body.cc", "qt/window.cc", "qt/home.cc", "qt/offroad/settings.cc", "qt/offroad/software_settings.cc", "qt/offroad/onboarding.cc", - "qt/offroad/driverview.cc"] + "qt/offroad/driverview.cc", "qt/offroad/experimental_mode.cc"] qt_env.Program("_ui", qt_src + [asset_obj], LIBS=qt_libs) if GetOption('test'): qt_src.remove("main.cc") # replaced by test_runner @@ -66,11 +67,18 @@ if GetOption('test'): # build translation files -translation_sources = Glob("#selfdrive/ui/translations/*.ts", strings=True) +with open(File("translations/languages.json").abspath) as f: + languages = json.loads(f.read()) +translation_sources = [f"#selfdrive/ui/translations/{l}.ts" for l in languages.values()] translation_targets = [src.replace(".ts", ".qm") for src in translation_sources] -lrelease = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' -qt_env.Command(translation_targets, translation_sources, f"{lrelease} $SOURCES") +lrelease_bin = 'third_party/qt5/larch64/bin/lrelease' if arch == 'larch64' else 'lrelease' +lupdate = qt_env.Command(translation_sources, qt_src, "selfdrive/ui/update_translations.py") +lrelease = qt_env.Command(translation_targets, translation_sources, f"{lrelease_bin} $SOURCES") +qt_env.Depends(lrelease, lupdate) +qt_env.NoClean(translation_sources) +qt_env.Precious(translation_sources) +qt_env.NoCache(lupdate) # setup and factory resetter if GetOption('extras'): diff --git a/selfdrive/ui/installer/installer.cc b/selfdrive/ui/installer/installer.cc index 7d8bbf74e0..1af72c04df 100644 --- a/selfdrive/ui/installer/installer.cc +++ b/selfdrive/ui/installer/installer.cc @@ -141,9 +141,9 @@ void Installer::cachedFetch(const QString &cache) { void Installer::readProgress() { const QVector> stages = { // prefix, weight in percentage - {tr("Receiving objects: "), 91}, - {tr("Resolving deltas: "), 2}, - {tr("Updating files: "), 7}, + {"Receiving objects: ", 91}, + {"Resolving deltas: ", 2}, + {"Updating files: ", 7}, }; auto line = QString(proc.readAllStandardError()); diff --git a/selfdrive/ui/qt/home.cc b/selfdrive/ui/qt/home.cc index 78bc6aab40..3f3c9a5885 100644 --- a/selfdrive/ui/qt/home.cc +++ b/selfdrive/ui/qt/home.cc @@ -1,11 +1,10 @@ #include "selfdrive/ui/qt/home.h" -#include #include #include #include -#include "common/params.h" +#include "selfdrive/ui/qt/offroad/experimental_mode.h" #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/qt/widgets/drive_stats.h" #include "selfdrive/ui/qt/widgets/prime.h" @@ -24,7 +23,8 @@ HomeWindow::HomeWindow(QWidget* parent) : QWidget(parent) { slayout = new QStackedLayout(); main_layout->addLayout(slayout); - home = new OffroadHome(); + home = new OffroadHome(this); + QObject::connect(home, &OffroadHome::openSettings, this, &HomeWindow::openSettings); slayout->addWidget(home); onroad = new OnroadWindow(this); @@ -109,22 +109,20 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { header_layout->setContentsMargins(15, 15, 15, 0); header_layout->setSpacing(16); - date = new QLabel(); - header_layout->addWidget(date, 1, Qt::AlignHCenter | Qt::AlignLeft); - update_notif = new QPushButton(tr("UPDATE")); update_notif->setVisible(false); update_notif->setStyleSheet("background-color: #364DEF;"); QObject::connect(update_notif, &QPushButton::clicked, [=]() { center_layout->setCurrentIndex(1); }); - header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignRight); + header_layout->addWidget(update_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); alert_notif = new QPushButton(); alert_notif->setVisible(false); alert_notif->setStyleSheet("background-color: #E22C2C;"); QObject::connect(alert_notif, &QPushButton::clicked, [=] { center_layout->setCurrentIndex(2); }); - header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignRight); + header_layout->addWidget(alert_notif, 0, Qt::AlignHCenter | Qt::AlignLeft); - header_layout->addWidget(new QLabel(getBrandVersion()), 0, Qt::AlignHCenter | Qt::AlignRight); + version = new ElidedLabel(); + header_layout->addWidget(version, 0, Qt::AlignHCenter | Qt::AlignRight); main_layout->addLayout(header_layout); @@ -132,11 +130,24 @@ OffroadHome::OffroadHome(QWidget* parent) : QFrame(parent) { main_layout->addSpacing(25); center_layout = new QStackedLayout(); + // Vertical experimental button and drive stats layout + QWidget* statsAndExperimentalModeButtonWidget = new QWidget(this); + QVBoxLayout* statsAndExperimentalModeButton = new QVBoxLayout(statsAndExperimentalModeButtonWidget); + statsAndExperimentalModeButton->setSpacing(30); + statsAndExperimentalModeButton->setMargin(0); + + ExperimentalModeButton *experimental_mode = new ExperimentalModeButton(this); + QObject::connect(experimental_mode, &ExperimentalModeButton::openSettings, this, &OffroadHome::openSettings); + + statsAndExperimentalModeButton->addWidget(experimental_mode, 1); + statsAndExperimentalModeButton->addWidget(new DriveStats, 1); + + // Horizontal experimental + drive stats and setup widget QWidget* statsAndSetupWidget = new QWidget(this); QHBoxLayout* statsAndSetup = new QHBoxLayout(statsAndSetupWidget); statsAndSetup->setMargin(0); statsAndSetup->setSpacing(30); - statsAndSetup->addWidget(new DriveStats, 1); + statsAndSetup->addWidget(statsAndExperimentalModeButtonWidget, 1); statsAndSetup->addWidget(new SetupWidget); center_layout->addWidget(statsAndSetupWidget); @@ -184,9 +195,7 @@ void OffroadHome::hideEvent(QHideEvent *event) { } void OffroadHome::refresh() { - QString locale_name = QString(uiState()->language).replace("main_", ""); - QString dateString = QLocale(locale_name).toString(QDateTime::currentDateTime(), "dddd, MMMM d"); - date->setText(dateString); + version->setText(getBrand() + " " + QString::fromStdString(params.get("UpdaterCurrentDescription"))); bool updateAvailable = update_widget->refresh(); int alerts = alerts_widget->refresh(); diff --git a/selfdrive/ui/qt/home.h b/selfdrive/ui/qt/home.h index 94f1330109..ed1c215467 100644 --- a/selfdrive/ui/qt/home.h +++ b/selfdrive/ui/qt/home.h @@ -7,10 +7,12 @@ #include #include +#include "common/params.h" #include "selfdrive/ui/qt/offroad/driverview.h" #include "selfdrive/ui/qt/body.h" #include "selfdrive/ui/qt/onroad.h" #include "selfdrive/ui/qt/sidebar.h" +#include "selfdrive/ui/qt/widgets/controls.h" #include "selfdrive/ui/qt/widgets/offroad_alerts.h" #include "selfdrive/ui/ui.h" @@ -20,13 +22,18 @@ class OffroadHome : public QFrame { public: explicit OffroadHome(QWidget* parent = 0); +signals: + void openSettings(int index = 0, const QString ¶m = ""); + private: void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void refresh(); + Params params; + QTimer* timer; - QLabel* date; + ElidedLabel* version; QStackedLayout* center_layout; UpdateAlert *update_widget; OffroadAlert* alerts_widget; @@ -41,7 +48,7 @@ public: explicit HomeWindow(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); public slots: diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index b5519daccf..abbf434d69 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -24,8 +24,6 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; -const float VALID_POS_STD = 50.0; // m - const QString ICON_SUFFIX = ".png"; MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05) { @@ -47,7 +45,7 @@ MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), map_eta->setVisible(false); auto last_gps_position = coordinate_from_param("LastGPSPosition"); - if (last_gps_position) { + if (last_gps_position.has_value()) { last_position = *last_gps_position; } @@ -82,6 +80,7 @@ void MapWindow::initLayers() { m_map->setPaintProperty("navLayer", "line-color", QColor("#31a1ee")); m_map->setPaintProperty("navLayer", "line-width", 7.5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); + m_map->addAnnotationIcon("default_marker", QImage("../assets/navigation/default_marker.svg")); } if (!m_map->layerExists("carPosLayer")) { qDebug() << "Initializing carPosLayer"; @@ -124,41 +123,6 @@ void MapWindow::updateState(const UIState &s) { } } - if (sm.updated("gnssMeasurements")) { - auto laikad_location = sm["gnssMeasurements"].getGnssMeasurements(); - auto laikad_pos = laikad_location.getPositionECEF(); - auto laikad_pos_ecef = laikad_pos.getValue(); - auto laikad_pos_std = laikad_pos.getStd(); - auto laikad_velocity_ecef = laikad_location.getVelocityECEF().getValue(); - - laikad_valid = laikad_pos.getValid() && Eigen::Vector3d(laikad_pos_std[0], laikad_pos_std[1], laikad_pos_std[2]).norm() < VALID_POS_STD; - - if (laikad_valid && !locationd_valid) { - ECEF ecef = {.x = laikad_pos_ecef[0], .y = laikad_pos_ecef[1], .z = laikad_pos_ecef[2]}; - Geodetic laikad_pos_geodetic = ecef2geodetic(ecef); - last_position = QMapbox::Coordinate(laikad_pos_geodetic.lat, laikad_pos_geodetic.lon); - - // Compute NED velocity - LocalCoord converter(ecef); - ECEF next_ecef = {.x = ecef.x + laikad_velocity_ecef[0], .y = ecef.y + laikad_velocity_ecef[1], .z = ecef.z + laikad_velocity_ecef[2]}; - Eigen::VectorXd ned_vel = converter.ecef2ned(next_ecef).to_vector() - converter.ecef2ned(ecef).to_vector(); - - float velocity = ned_vel.norm(); - velocity_filter.update(velocity); - - // Convert NED velocity to angle - if (velocity > 1.0) { - float new_bearing = fmod(RAD2DEG(atan2(ned_vel[1], ned_vel[0])) + 360.0, 360.0); - if (last_bearing) { - float delta = 0.1 * angle_difference(*last_bearing, new_bearing); // Smooth heading - last_bearing = fmod(*last_bearing + delta + 360.0, 360.0); - } else { - last_bearing = new_bearing; - } - } - } - } - if (sm.updated("navRoute") && sm["navRoute"].getNavRoute().getCoordinates().size()) { qWarning() << "Got new navRoute from navd. Opening map:" << allow_open; @@ -220,7 +184,6 @@ void MapWindow::updateState(const UIState &s) { emit instructionsChanged(i); } } else { - m_map->setPitch(MIN_PITCH); clearRoute(); } } @@ -237,6 +200,7 @@ void MapWindow::updateState(const UIState &s) { m_map->setLayoutProperty("navLayer", "visibility", "visible"); route_rcv_frame = sm.rcv_frame("navRoute"); + updateDestinationMarker(); } } @@ -274,6 +238,7 @@ void MapWindow::clearRoute() { if (!m_map.isNull()) { m_map->setLayoutProperty("navLayer", "visibility", "none"); m_map->setPitch(MIN_PITCH); + updateDestinationMarker(); } map_instructions->hideIfNoError(); @@ -361,6 +326,19 @@ void MapWindow::offroadTransition(bool offroad) { last_bearing = {}; } +void MapWindow::updateDestinationMarker() { + if (marker_id != -1) { + m_map->removeAnnotation(marker_id); + marker_id = -1; + } + + auto nav_dest = coordinate_from_param("NavDestination"); + if (nav_dest.has_value()) { + auto ano = QMapbox::SymbolAnnotation {*nav_dest, "default_marker"}; + marker_id = m_map->addAnnotation(QVariant::fromValue(ano)); + } +} + MapInstructions::MapInstructions(QWidget * parent) : QWidget(parent) { is_rhd = Params().getBool("IsRhdDetected"); QHBoxLayout *main_layout = new QHBoxLayout(this); diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index c3d5e92530..0d8b93a5f4 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -79,6 +79,7 @@ private: QMapboxGLSettings m_settings; QScopedPointer m_map; + QMapbox::AnnotationID marker_id = -1; void initLayers(); @@ -111,6 +112,7 @@ private: MapETA* map_eta; void clearRoute(); + void updateDestinationMarker(); uint64_t route_rcv_frame = 0; private slots: diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 8d5d4e1715..95db4f2bbd 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -31,7 +31,7 @@ QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { QMapbox::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, - const cereal::ModelDataV2::XYZTData::Reader &line){ + const cereal::XYZTData::Reader &line){ Eigen::Vector3d ecef(positionECEF.getValue()[0], positionECEF.getValue()[1], positionECEF.getValue()[2]); Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]); diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 6bd5b0f067..f9c56107e3 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -20,7 +20,7 @@ QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); QMapbox::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, - const cereal::ModelDataV2::XYZTData::Reader &line); + const cereal::XYZTData::Reader &line); QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c); QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); diff --git a/selfdrive/ui/qt/maps/set_destination.py b/selfdrive/ui/qt/maps/set_destination.py deleted file mode 100755 index b9721171cc..0000000000 --- a/selfdrive/ui/qt/maps/set_destination.py +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env python3 -import json -import sys - -from common.params import Params - -if __name__ == "__main__": - coords = sys.argv[1].split("/@")[-1].split("/")[0].split(",") - dest = {"latitude": float(coords[0]), "longitude": float(coords[1])} - Params().put("NavDestination", json.dumps(dest)) diff --git a/selfdrive/ui/qt/offroad/driverview.cc b/selfdrive/ui/qt/offroad/driverview.cc index 0ff786fb91..327c7537a2 100644 --- a/selfdrive/ui/qt/offroad/driverview.cc +++ b/selfdrive/ui/qt/offroad/driverview.cc @@ -22,11 +22,12 @@ DriverViewWindow::DriverViewWindow(QWidget* parent) : QWidget(parent) { } void DriverViewWindow::mouseReleaseEvent(QMouseEvent* e) { + cameraView->stopVipcThread(); emit done(); } DriverViewScene::DriverViewScene(QWidget* parent) : sm({"driverStateV2"}), QWidget(parent) { - face_img = loadPixmap("../assets/img_driver_face.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); + face_img = loadPixmap("../assets/img_driver_face_static.png", {FACE_IMG_SIZE, FACE_IMG_SIZE}); } void DriverViewScene::showEvent(QShowEvent* event) { diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc new file mode 100644 index 0000000000..b99220c1d1 --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -0,0 +1,76 @@ +#include "selfdrive/ui/qt/offroad/experimental_mode.h" + +#include +#include +#include +#include +#include + +#include "selfdrive/ui/ui.h" + +ExperimentalModeButton::ExperimentalModeButton(QWidget *parent) : QPushButton(parent) { + chill_pixmap = QPixmap("../assets/img_couch.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + experimental_pixmap = QPixmap("../assets/img_experimental_grey.svg").scaledToWidth(img_width, Qt::SmoothTransformation); + + // go to toggles and expand experimental mode description + connect(this, &QPushButton::clicked, [=]() { emit openSettings(2, "ExperimentalMode"); }); + + setFixedHeight(125); + QHBoxLayout *main_layout = new QHBoxLayout; + main_layout->setContentsMargins(horizontal_padding, 0, horizontal_padding, 0); + + mode_label = new QLabel; + mode_icon = new QLabel; + mode_icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); + + main_layout->addWidget(mode_label, 1, Qt::AlignLeft); + main_layout->addWidget(mode_icon, 0, Qt::AlignRight); + + setLayout(main_layout); + + setStyleSheet(R"( + QPushButton { + border: none; + } + + QLabel { + font-size: 45px; + font-weight: 300; + text-align: left; + font-family: JetBrainsMono; + color: #000000; + } + )"); +} + +void ExperimentalModeButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setPen(Qt::NoPen); + p.setRenderHint(QPainter::Antialiasing); + + QPainterPath path; + path.addRoundedRect(rect(), 10, 10); + + // gradient + bool pressed = isDown(); + QLinearGradient gradient(rect().left(), 0, rect().right(), 0); + if (experimental_mode) { + gradient.setColorAt(0, QColor(255, 155, 63, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(219, 56, 34, pressed ? 0xcc : 0xff)); + } else { + gradient.setColorAt(0, QColor(20, 255, 171, pressed ? 0xcc : 0xff)); + gradient.setColorAt(1, QColor(35, 149, 255, pressed ? 0xcc : 0xff)); + } + p.fillPath(path, gradient); + + // vertical line + p.setPen(QPen(QColor(0, 0, 0, 0x4d), 3, Qt::SolidLine)); + int line_x = rect().right() - img_width - (2 * horizontal_padding); + p.drawLine(line_x, rect().bottom(), line_x, rect().top()); +} + +void ExperimentalModeButton::showEvent(QShowEvent *event) { + experimental_mode = params.getBool("ExperimentalMode"); + mode_icon->setPixmap(experimental_mode ? experimental_pixmap : chill_pixmap); + mode_label->setText(experimental_mode ? tr("EXPERIMENTAL MODE ON") : tr("CHILL MODE ON")); +} diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h new file mode 100644 index 0000000000..bfb7638bbe --- /dev/null +++ b/selfdrive/ui/qt/offroad/experimental_mode.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +#include "common/params.h" + +class ExperimentalModeButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalModeButton(QWidget* parent = 0); + +signals: + void openSettings(int index = 0, const QString &toggle = ""); + +private: + void showEvent(QShowEvent *event) override; + + Params params; + bool experimental_mode; + int img_width = 100; + int horizontal_padding = 30; + QPixmap experimental_pixmap; + QPixmap chill_pixmap; + QLabel *mode_label; + QLabel *mode_icon; + +protected: + void paintEvent(QPaintEvent *event) override; +}; diff --git a/selfdrive/ui/qt/offroad/networking.cc b/selfdrive/ui/qt/offroad/networking.cc index 13697adfb5..d69d67edeb 100644 --- a/selfdrive/ui/qt/offroad/networking.cc +++ b/selfdrive/ui/qt/offroad/networking.cc @@ -314,7 +314,7 @@ void WifiUI::refresh() { QPushButton *forgetBtn = new QPushButton(tr("FORGET")); forgetBtn->setObjectName("forgetBtn"); QObject::connect(forgetBtn, &QPushButton::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), this)) { + if (ConfirmationDialog::confirm(tr("Forget Wi-Fi Network \"%1\"?").arg(QString::fromUtf8(network.ssid)), tr("Forget"), this)) { wifi->forgetConnection(network.ssid); } }); diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ae878c5419..63b87149d4 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -27,7 +27,7 @@ #include "selfdrive/ui/qt/widgets/input.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { - // param, title, desc, icon + // param, title, desc, icon, confirm std::vector> toggle_defs{ { "OpenpilotEnabledToggle", @@ -35,6 +35,20 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { tr("Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off."), "../assets/offroad/icon_openpilot.png", }, + { + "ExperimentalMode", + tr("Experimental Mode"), + "", + "../assets/img_experimental_white.svg", + }, + { + "ExperimentalLongitudinalEnabled", + tr("Experimental openpilot Longitudinal Control"), + QString("%1
%2") + .arg(tr("WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB).")) + .arg(tr("On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control.")), + "../assets/offroad/icon_speed_limit.png", + }, { "IsLdwEnabled", tr("Enable Lane Departure Warnings"), @@ -55,22 +69,10 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }, { "DisengageOnAccelerator", - tr("Disengage On Accelerator Pedal"), + tr("Disengage on Accelerator Pedal"), tr("When enabled, pressing the accelerator pedal will disengage openpilot."), "../assets/offroad/icon_disengage_on_accelerator.svg", }, - { - "EndToEndLong", - tr("🌮 End-to-end longitudinal (extremely alpha) 🌮"), - "", - "../assets/offroad/icon_road.png", - }, - { - "ExperimentalLongitudinalEnabled", - tr("Experimental openpilot longitudinal control"), - tr("WARNING: openpilot longitudinal control is experimental for this car and will disable AEB."), - "../assets/offroad/icon_speed_limit.png", - }, #ifdef ENABLE_MAPS { "NavSettingTime24h", @@ -85,7 +87,6 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/offroad/icon_road.png", }, #endif - }; for (auto &[param, title, desc, icon] : toggle_defs) { @@ -98,30 +99,50 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { toggles[param.toStdString()] = toggle; } + // Toggles with confirmation dialogs + toggles["ExperimentalMode"]->setActiveIcon("../assets/img_experimental.svg"); + toggles["ExperimentalMode"]->setConfirmation(true, true); + toggles["ExperimentalLongitudinalEnabled"]->setConfirmation(true, false); + connect(toggles["ExperimentalLongitudinalEnabled"], &ToggleControl::toggleFlipped, [=]() { updateToggles(); }); } +void TogglesPanel::expandToggleDescription(const QString ¶m) { + toggles[param.toStdString()]->showDescription(); +} + void TogglesPanel::showEvent(QShowEvent *event) { updateToggles(); } void TogglesPanel::updateToggles() { - auto e2e_toggle = toggles["EndToEndLong"]; + auto e2e_toggle = toggles["ExperimentalMode"]; auto op_long_toggle = toggles["ExperimentalLongitudinalEnabled"]; - const QString e2e_description = tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental."); + const QString e2e_description = QString("%1
" + "

%2


" + "%3
" + "

%4


" + "%5") + .arg(tr("openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. Experimental features are listed below:")) + .arg(tr("🌮 End-to-End Longitudinal Control 🌮")) + .arg(tr("Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. " + "Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected.")) + .arg(tr("New Driving Visualization")) + .arg(tr("The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner.")); + const bool is_release = params.getBool("IsReleaseBranch"); auto cp_bytes = params.get("CarParamsPersistent"); if (!cp_bytes.empty()) { AlignedBuffer aligned_buf; capnp::FlatArrayMessageReader cmsg(aligned_buf.align(cp_bytes.data(), cp_bytes.size())); cereal::CarParams::Reader CP = cmsg.getRoot(); - if (!CP.getExperimentalLongitudinalAvailable()) { + if (!CP.getExperimentalLongitudinalAvailable() || is_release) { params.remove("ExperimentalLongitudinalEnabled"); } - op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable()); + op_long_toggle->setVisible(CP.getExperimentalLongitudinalAvailable() && !is_release); const bool op_long = CP.getOpenpilotLongitudinalControl() && !CP.getExperimentalLongitudinalAvailable(); const bool exp_long_enabled = CP.getExperimentalLongitudinalAvailable() && params.getBool("ExperimentalLongitudinalEnabled"); @@ -132,11 +153,20 @@ void TogglesPanel::updateToggles() { } else { // no long for now e2e_toggle->setEnabled(false); - params.remove("EndToEndLong"); + params.remove("ExperimentalMode"); - const QString no_long = tr("openpilot longitudinal control is not currently available for this car."); - const QString exp_long = tr("Enable experimental longitudinal control to enable this."); - e2e_toggle->setDescription("" + (CP.getExperimentalLongitudinalAvailable() ? exp_long : no_long) + "

" + e2e_description); + const QString unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control."); + + QString long_desc = unavailable + " " + \ + tr("openpilot longitudinal control may come in a future update."); + if (CP.getExperimentalLongitudinalAvailable()) { + if (is_release) { + long_desc = unavailable + " " + tr("An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches."); + } else { + long_desc = tr("Enable experimental longitudinal control to allow Experimental mode."); + } + } + e2e_toggle->setDescription("" + long_desc + "

" + e2e_description); } e2e_toggle->refresh(); @@ -161,7 +191,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { auto resetCalibBtn = new ButtonControl(tr("Reset Calibration"), tr("RESET"), ""); connect(resetCalibBtn, &ButtonControl::showDescriptionEvent, this, &DevicePanel::updateCalibDescription); connect(resetCalibBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reset calibration?"), tr("Reset"), this)) { params.remove("CalibrationParams"); } }); @@ -170,7 +200,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { if (!params.getBool("Passive")) { auto retrainingBtn = new ButtonControl(tr("Review Training Guide"), tr("REVIEW"), tr("Review the rules, features, and limitations of openpilot")); connect(retrainingBtn, &ButtonControl::clicked, [=]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to review the training guide?"), tr("Review"), this)) { emit reviewTrainingGuide(); } }); @@ -181,7 +211,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { auto regulatoryBtn = new ButtonControl(tr("Regulatory"), tr("VIEW"), ""); connect(regulatoryBtn, &ButtonControl::clicked, [=]() { const std::string txt = util::read_file("../assets/offroad/fcc.html"); - RichTextDialog::alert(QString::fromStdString(txt), this); + ConfirmationDialog::rich(QString::fromStdString(txt), this); }); addItem(regulatoryBtn); } @@ -258,7 +288,7 @@ void DevicePanel::updateCalibDescription() { void DevicePanel::reboot() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to reboot?"), tr("Reboot"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoReboot", true); @@ -271,7 +301,7 @@ void DevicePanel::reboot() { void DevicePanel::poweroff() { if (!uiState()->engaged()) { - if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to power off?"), tr("Power Off"), this)) { // Check engaged again in case it changed while the dialog was open if (!uiState()->engaged()) { Params().putBool("DoShutdown", true); @@ -283,8 +313,15 @@ void DevicePanel::poweroff() { } void SettingsWindow::showEvent(QShowEvent *event) { - panel_widget->setCurrentIndex(0); - nav_btns->buttons()[0]->setChecked(true); + setCurrentPanel(0); +} + +void SettingsWindow::setCurrentPanel(int index, const QString ¶m) { + panel_widget->setCurrentIndex(index); + nav_btns->buttons()[index]->setChecked(true); + if (!param.isEmpty()) { + emit expandToggleDescription(param); + } } SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { @@ -325,10 +362,13 @@ SettingsWindow::SettingsWindow(QWidget *parent) : QFrame(parent) { QObject::connect(device, &DevicePanel::reviewTrainingGuide, this, &SettingsWindow::reviewTrainingGuide); QObject::connect(device, &DevicePanel::showDriverView, this, &SettingsWindow::showDriverView); + TogglesPanel *toggles = new TogglesPanel(this); + QObject::connect(this, &SettingsWindow::expandToggleDescription, toggles, &TogglesPanel::expandToggleDescription); + QList> panels = { {tr("Device"), device}, {tr("Network"), new Networking(this)}, - {tr("Toggles"), new TogglesPanel(this)}, + {tr("Toggles"), toggles}, {tr("Software"), new SoftwarePanel(this)}, }; diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index 4177f28cf4..c63be4e138 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -17,6 +17,7 @@ class SettingsWindow : public QFrame { public: explicit SettingsWindow(QWidget *parent = 0); + void setCurrentPanel(int index, const QString ¶m = ""); protected: void showEvent(QShowEvent *event) override; @@ -25,6 +26,7 @@ signals: void closeSettings(); void reviewTrainingGuide(); void showDriverView(); + void expandToggleDescription(const QString ¶m); private: QPushButton *sidebar_alert_widget; @@ -56,6 +58,9 @@ public: explicit TogglesPanel(SettingsWindow *parent); void showEvent(QShowEvent *event) override; +public slots: + void expandToggleDescription(const QString ¶m); + private: Params params; std::map toggles; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 4f048241c3..6db6a6cdfe 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -54,7 +54,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { connect(targetBranchBtn, &ButtonControl::clicked, [=]() { auto current = params.get("GitBranch"); QStringList branches = QString::fromStdString(params.get("UpdaterAvailableBranches")).split(","); - for (QString b : {current.c_str(), "devel-staging", "devel", "master-ci", "master"}) { + for (QString b : {current.c_str(), "devel-staging", "devel", "nightly", "master-ci", "master"}) { auto i = branches.indexOf(b); if (i >= 0) { branches.removeAt(i); @@ -77,7 +77,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { // uninstall button auto uninstallBtn = new ButtonControl(tr("Uninstall %1").arg(getBrand()), tr("UNINSTALL")); connect(uninstallBtn, &ButtonControl::clicked, [&]() { - if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), this)) { + if (ConfirmationDialog::confirm(tr("Are you sure you want to uninstall?"), tr("Uninstall"), this)) { params.putBool("DoUninstall", true); } }); @@ -145,11 +145,11 @@ void SoftwarePanel::updateLabels() { targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); // current + new versions - versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription")).left(40)); + versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes"))); installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable")); - installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription")).left(35)); + installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription"))); installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); update(); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 1f677fc92d..009798a569 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -31,6 +31,11 @@ OnroadWindow::OnroadWindow(QWidget *parent) : QWidget(parent) { split->insertWidget(0, arCam); } + if (getenv("MAP_RENDER_VIEW")) { + CameraWidget *map_render = new CameraWidget("navd", VISION_STREAM_MAP, false, this); + split->insertWidget(0, map_render); + } + stacked_layout->addWidget(split_wrapper); alerts = new OnroadAlerts(this); @@ -100,10 +105,6 @@ void OnroadWindow::offroadTransition(bool offroad) { #endif alerts->updateAlert({}, bg); - - // update stream type - bool wide_cam = Params().getBool("WideCameraOnly"); - nvg->setStreamType(wide_cam ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); } void OnroadWindow::paintEvent(QPaintEvent *event) { @@ -174,11 +175,63 @@ void OnroadAlerts::paintEvent(QPaintEvent *event) { } +ExperimentalButton::ExperimentalButton(QWidget *parent) : QPushButton(parent) { + setVisible(false); + setFixedSize(btn_size, btn_size); + setCheckable(true); + + params = Params(); + engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); + experimental_img = loadPixmap("../assets/img_experimental.svg", {img_size, img_size}); + + QObject::connect(this, &QPushButton::toggled, [=](bool checked) { + params.putBool("ExperimentalMode", checked); + }); +} + +void ExperimentalButton::updateState(const UIState &s) { + const SubMaster &sm = *(s.sm); + + // button is "visible" if engageable or enabled + const auto cs = sm["controlsState"].getControlsState(); + setVisible(cs.getEngageable() || cs.getEnabled()); + + // button is "checked" if experimental mode is enabled + setChecked(sm["controlsState"].getControlsState().getExperimentalMode()); + + // disable button when experimental mode is not available, or has not been confirmed for the first time + const auto cp = sm["carParams"].getCarParams(); + const bool experimental_mode_available = cp.getExperimentalLongitudinalAvailable() ? params.getBool("ExperimentalLongitudinalEnabled") : cp.getOpenpilotLongitudinalControl(); + setEnabled(params.getBool("ExperimentalModeConfirmed") && experimental_mode_available); +} + +void ExperimentalButton::paintEvent(QPaintEvent *event) { + QPainter p(this); + p.setRenderHint(QPainter::Antialiasing); + + QPoint center(btn_size / 2, btn_size / 2); + QPixmap img = isChecked() ? experimental_img : engage_img; + + p.setOpacity(1.0); + p.setPen(Qt::NoPen); + p.setBrush(QColor(0, 0, 0, 166)); + p.drawEllipse(center, btn_size / 2, btn_size / 2); + p.setOpacity(isDown() ? 0.8 : 1.0); + p.drawPixmap((btn_size - img_size) / 2, (btn_size - img_size) / 2, img); +} + + AnnotatedCameraWidget::AnnotatedCameraWidget(VisionStreamType type, QWidget* parent) : fps_filter(UI_FREQ, 3, 1. / UI_FREQ), CameraWidget("camerad", type, true, parent) { pm = std::make_unique>({"uiDebug"}); - engage_img = loadPixmap("../assets/img_chffr_wheel.png", {img_size, img_size}); - dm_img = loadPixmap("../assets/img_driver_face.png", {img_size, img_size}); + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setMargin(bdr_s); + main_layout->setSpacing(0); + + experimental_btn = new ExperimentalButton(this); + main_layout->addWidget(experimental_btn, 0, Qt::AlignTop | Qt::AlignRight); + + dm_img = loadPixmap("../assets/img_driver_face.png", {img_size + 5, img_size + 5}); } void AnnotatedCameraWidget::updateState(const UIState &s) { @@ -207,7 +260,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { v_ego_cluster_seen = true; } float cur_speed = cs_alive ? std::max(0.0, v_ego) : 0.0; - cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; + cur_speed *= s.scene.is_metric ? MS_TO_KPH : MS_TO_MPH; auto speed_limit_sign = sm["navInstruction"].getNavInstruction().getSpeedLimitSign(); float speed_limit = nav_alive ? sm["navInstruction"].getNavInstruction().getSpeedLimit() : 0.0; @@ -222,21 +275,20 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { setProperty("speed", cur_speed); setProperty("setSpeed", set_speed); setProperty("speedUnit", s.scene.is_metric ? tr("km/h") : tr("mph")); - setProperty("hideDM", cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE); + setProperty("hideDM", (cs.getAlertSize() != cereal::ControlsState::AlertSize::NONE)); setProperty("status", s.status); - // update engageability and DM icons at 2Hz + // update engageability/experimental mode button + experimental_btn->updateState(s); + + // update DM icons at 2Hz if (sm.frame % (UI_FREQ / 2) == 0) { - setProperty("engageable", cs.getEngageable() || cs.getEnabled()); setProperty("dmActive", sm["driverMonitoringState"].getDriverMonitoringState().getIsActiveMode()); setProperty("rightHandDM", sm["driverMonitoringState"].getDriverMonitoringState().getIsRHD()); } - if (s.scene.calibration_valid) { - CameraWidget::updateCalibration(s.scene.view_from_calib); - } else { - CameraWidget::updateCalibration(DEFAULT_CALIBRATION); - } + // DM icon transition + dm_fade_state = fmax(0.0, fmin(1.0, dm_fade_state+0.2*(0.5-(float)(dmActive)))); } void AnnotatedCameraWidget::drawHud(QPainter &p) { @@ -386,18 +438,6 @@ void AnnotatedCameraWidget::drawHud(QPainter &p) { configFont(p, "Inter", 66, "Regular"); drawText(p, rect().center().x(), 290, speedUnit, 200); - // engage-ability icon - if (engageable) { - drawIcon(p, rect().right() - radius / 2 - bdr_s * 2, radius / 2 + int(bdr_s * 1.5), - engage_img, bg_colors[status], 1.0); - } - - // dm icon - if (!hideDM) { - int dm_icon_x = rightHandDM ? rect().right() - radius / 2 - (bdr_s * 2) : radius / 2 + (bdr_s * 2); - drawIcon(p, dm_icon_x, rect().bottom() - footer_h / 2, - dm_img, blackColor(70), dmActive ? 1.0 : 0.2); - } p.restore(); } @@ -414,11 +454,12 @@ void AnnotatedCameraWidget::drawText(QPainter &p, int x, int y, const QString &t } void AnnotatedCameraWidget::drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity) { + p.setOpacity(1.0); // bg dictates opacity of ellipse p.setPen(Qt::NoPen); p.setBrush(bg); - p.drawEllipse(x - radius / 2, y - radius / 2, radius, radius); + p.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size); p.setOpacity(opacity); - p.drawPixmap(x - img_size / 2, y - img_size / 2, img); + p.drawPixmap(x - img.size().width() / 2, y - img.size().height() / 2, img); } @@ -455,6 +496,8 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.save(); const UIScene &scene = s->scene; + SubMaster &sm = *(s->sm); + // lanelines for (int i = 0; i < std::size(scene.lane_line_vertices); ++i) { painter.setBrush(QColor::fromRgbF(1.0, 1.0, 1.0, std::clamp(scene.lane_line_probs[i], 0.0, 0.7))); @@ -470,15 +513,15 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { // paint path QLinearGradient bg(0, height(), 0, height() / 4); float start_hue, end_hue; - if (scene.end_to_end_long) { - const auto &acceleration = (*s->sm)["modelV2"].getModelV2().getAcceleration(); + if (sm["controlsState"].getControlsState().getExperimentalMode()) { + const auto &acceleration = sm["modelV2"].getModelV2().getAcceleration(); float acceleration_future = 0; if (acceleration.getZ().size() > 16) { acceleration_future = acceleration.getX()[16]; // 2.5 seconds } start_hue = 60; // speed up: 120, slow down: 0 - end_hue = fmax(fmin(start_hue + acceleration_future * 30, 120), 0); + end_hue = fmax(fmin(start_hue + acceleration_future * 45, 148), 0); // FIXME: painter.drawPolygon can be slow if hue is not rounded end_hue = int(end_hue * 100 + 0.5) / 100; @@ -487,21 +530,9 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { bg.setColorAt(0.5, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.35)); bg.setColorAt(1.0, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.0)); } else { - const auto &orientation = (*s->sm)["modelV2"].getModelV2().getOrientation(); - float orientation_future = 0; - if (orientation.getZ().size() > 16) { - orientation_future = std::abs(orientation.getZ()[16]); // 2.5 seconds - } - start_hue = 148; - // straight: 112, in turns: 70 - end_hue = fmax(70, 112 - (orientation_future * 420)); - - // FIXME: painter.drawPolygon can be slow if hue is not rounded - end_hue = int(end_hue * 100 + 0.5) / 100; - - bg.setColorAt(0.0, QColor::fromHslF(start_hue / 360., 0.94, 0.51, 0.4)); - bg.setColorAt(0.5, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.35)); - bg.setColorAt(1.0, QColor::fromHslF(end_hue / 360., 1.0, 0.68, 0.0)); + bg.setColorAt(0.0, QColor::fromHslF(148 / 360., 0.94, 0.51, 0.4)); + bg.setColorAt(0.5, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.35)); + bg.setColorAt(1.0, QColor::fromHslF(112 / 360., 1.0, 0.68, 0.0)); } painter.setBrush(bg); @@ -510,13 +541,59 @@ void AnnotatedCameraWidget::drawLaneLines(QPainter &painter, const UIState *s) { painter.restore(); } -void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd) { +void AnnotatedCameraWidget::drawDriverState(QPainter &painter, const UIState *s) { + const UIScene &scene = s->scene; + + painter.save(); + + // base icon + int x = rightHandDM ? rect().right() - (btn_size - 24) / 2 - (bdr_s * 2) : (btn_size - 24) / 2 + (bdr_s * 2); + int y = rect().bottom() - footer_h / 2; + float opacity = dmActive ? 0.65 : 0.2; + drawIcon(painter, x, y, dm_img, blackColor(0), opacity); + + // circle background + painter.setOpacity(1.0); + painter.setPen(Qt::NoPen); + painter.setBrush(blackColor(70)); + painter.drawEllipse(x - btn_size / 2, y - btn_size / 2, btn_size, btn_size); + + // face + QPointF face_kpts_draw[std::size(default_face_kpts_3d)]; + float kp; + for (int i = 0; i < std::size(default_face_kpts_3d); ++i) { + kp = (scene.face_kpts_draw[i].v[2] - 8) / 120 + 1.0; + face_kpts_draw[i] = QPointF(scene.face_kpts_draw[i].v[0] * kp + x, scene.face_kpts_draw[i].v[1] * kp + y); + } + + painter.setPen(QPen(QColor::fromRgbF(1.0, 1.0, 1.0, opacity), 5.2, Qt::SolidLine, Qt::RoundCap)); + painter.drawPolyline(face_kpts_draw, std::size(default_face_kpts_3d)); + + // tracking arcs + const int arc_l = 133; + const float arc_t_default = 6.7; + const float arc_t_extend = 12.0; + QColor arc_color = QColor::fromRgbF(0.545 - 0.445 * s->engaged(), + 0.545 + 0.4 * s->engaged(), + 0.545 - 0.285 * s->engaged(), + 0.4 * (1.0 - dm_fade_state)); + float delta_x = -scene.driver_pose_sins[1] * arc_l / 2; + float delta_y = -scene.driver_pose_sins[0] * arc_l / 2; + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[1] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(std::fmin(x + delta_x, x), y - arc_l / 2, fabs(delta_x), arc_l), (scene.driver_pose_sins[1]>0 ? 90 : -90) * 16, 180 * 16); + painter.setPen(QPen(arc_color, arc_t_default+arc_t_extend*fmin(1.0, scene.driver_pose_diff[0] * 5.0), Qt::SolidLine, Qt::RoundCap)); + painter.drawArc(QRectF(x - arc_l / 2, std::fmin(y + delta_y, y), arc_l, fabs(delta_y)), (scene.driver_pose_sins[0]>0 ? 0 : 180) * 16, 180 * 16); + + painter.restore(); +} + +void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd) { painter.save(); const float speedBuff = 10.; const float leadBuff = 40.; - const float d_rel = lead_data.getX()[0]; - const float v_rel = lead_data.getV()[0]; + const float d_rel = lead_data.getDRel(); + const float v_rel = lead_data.getVRel(); float fillAlpha = 0; if (d_rel < leadBuff) { @@ -547,32 +624,87 @@ void AnnotatedCameraWidget::drawLead(QPainter &painter, const cereal::ModelDataV } void AnnotatedCameraWidget::paintGL() { - const double start_draw_t = millis_since_boot(); - UIState *s = uiState(); - const cereal::ModelDataV2::Reader &model = (*s->sm)["modelV2"].getModelV2(); - CameraWidget::setFrameId(model.getFrameId()); - CameraWidget::paintGL(); + SubMaster &sm = *(s->sm); + const double start_draw_t = millis_since_boot(); + const cereal::ModelDataV2::Reader &model = sm["modelV2"].getModelV2(); + const cereal::RadarState::Reader &radar_state = sm["radarState"].getRadarState(); + + // draw camera frame + { + std::lock_guard lk(frame_lock); + + if (frames.empty()) { + if (skip_frame_count > 0) { + skip_frame_count--; + qDebug() << "skipping frame, not ready"; + return; + } + } else { + // skip drawing up to this many frames if we're + // missing camera frames. this smooths out the + // transitions from the narrow and wide cameras + skip_frame_count = 5; + } + + // Wide or narrow cam dependent on speed + bool has_wide_cam = available_streams.count(VISION_STREAM_WIDE_ROAD); + if (has_wide_cam) { + float v_ego = sm["carState"].getCarState().getVEgo(); + if ((v_ego < 10) || available_streams.size() == 1) { + wide_cam_requested = true; + } else if (v_ego > 15) { + wide_cam_requested = false; + } + wide_cam_requested = wide_cam_requested && sm["controlsState"].getControlsState().getExperimentalMode(); + // for replay of old routes, never go to widecam + wide_cam_requested = wide_cam_requested && s->scene.calibration_wide_valid; + } + CameraWidget::setStreamType(wide_cam_requested ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD); + + s->scene.wide_cam = CameraWidget::getStreamType() == VISION_STREAM_WIDE_ROAD; + if (s->scene.calibration_valid) { + auto calib = s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib; + CameraWidget::updateCalibration(calib); + } else { + CameraWidget::updateCalibration(DEFAULT_CALIBRATION); + } + CameraWidget::setFrameId(model.getFrameId()); + CameraWidget::paintGL(); + } QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing); painter.setPen(Qt::NoPen); if (s->worldObjectsVisible()) { + if (sm.rcv_frame("modelV2") > s->scene.started_frame) { + update_model(s, sm["modelV2"].getModelV2(), sm["uiPlan"].getUiPlan()); + if (sm.rcv_frame("radarState") > s->scene.started_frame) { + update_leads(s, radar_state, sm["modelV2"].getModelV2().getPosition()); + } + } drawLaneLines(painter, s); if (s->scene.longitudinal_control) { - const auto leads = model.getLeadsV3(); - if (leads[0].getProb() > .5) { - drawLead(painter, leads[0], s->scene.lead_vertices[0]); + auto lead_one = radar_state.getLeadOne(); + auto lead_two = radar_state.getLeadTwo(); + if (lead_one.getStatus()) { + drawLead(painter, lead_one, s->scene.lead_vertices[0]); } - if (leads[1].getProb() > .5 && (std::abs(leads[1].getX()[0] - leads[0].getX()[0]) > 3.0)) { - drawLead(painter, leads[1], s->scene.lead_vertices[1]); + if (lead_two.getStatus() && (std::abs(lead_one.getDRel() - lead_two.getDRel()) > 3.0)) { + drawLead(painter, lead_two, s->scene.lead_vertices[1]); } } } + // DMoji + if (!hideDM && (sm.rcv_frame("driverStateV2") > s->scene.started_frame)) { + update_dmonitoring(s, sm["driverStateV2"].getDriverStateV2(), dm_fade_state, rightHandDM); + drawDriverState(painter, s); + } + drawHud(painter); double cur_draw_t = millis_since_boot(); diff --git a/selfdrive/ui/qt/onroad.h b/selfdrive/ui/qt/onroad.h index 2a663185f4..73c2a37892 100644 --- a/selfdrive/ui/qt/onroad.h +++ b/selfdrive/ui/qt/onroad.h @@ -1,11 +1,16 @@ #pragma once +#include #include #include #include "common/util.h" -#include "selfdrive/ui/qt/widgets/cameraview.h" #include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/widgets/cameraview.h" + + +const int btn_size = 192; +const int img_size = (btn_size / 4) * 3; // ***** onroad widgets ***** @@ -24,6 +29,21 @@ private: Alert alert = {}; }; +class ExperimentalButton : public QPushButton { + Q_OBJECT + +public: + explicit ExperimentalButton(QWidget *parent = 0); + void updateState(const UIState &s); + +private: + void paintEvent(QPaintEvent *event) override; + + Params params; + QPixmap engage_img; + QPixmap experimental_img; +}; + // container window for the NVG UI class AnnotatedCameraWidget : public CameraWidget { Q_OBJECT @@ -36,7 +56,6 @@ class AnnotatedCameraWidget : public CameraWidget { Q_PROPERTY(bool has_us_speed_limit MEMBER has_us_speed_limit); Q_PROPERTY(bool is_metric MEMBER is_metric); - Q_PROPERTY(bool engageable MEMBER engageable); Q_PROPERTY(bool dmActive MEMBER dmActive); Q_PROPERTY(bool hideDM MEMBER hideDM); Q_PROPERTY(bool rightHandDM MEMBER rightHandDM); @@ -50,34 +69,36 @@ private: void drawIcon(QPainter &p, int x, int y, QPixmap &img, QBrush bg, float opacity); void drawText(QPainter &p, int x, int y, const QString &text, int alpha = 255); - QPixmap engage_img; + ExperimentalButton *experimental_btn; QPixmap dm_img; - const int radius = 192; - const int img_size = (radius / 2) * 1.5; float speed; QString speedUnit; float setSpeed; float speedLimit; bool is_cruise_set = false; bool is_metric = false; - bool engageable = false; bool dmActive = false; bool hideDM = false; bool rightHandDM = false; + float dm_fade_state = 1.0; bool has_us_speed_limit = false; bool has_eu_speed_limit = false; bool v_ego_cluster_seen = false; int status = STATUS_DISENGAGED; std::unique_ptr pm; + int skip_frame_count = 0; + bool wide_cam_requested = false; + protected: void paintGL() override; void initializeGL() override; void showEvent(QShowEvent *event) override; void updateFrameMat() override; void drawLaneLines(QPainter &painter, const UIState *s); - void drawLead(QPainter &painter, const cereal::ModelDataV2::LeadDataV3::Reader &lead_data, const QPointF &vd); + void drawLead(QPainter &painter, const cereal::RadarState::LeadData::Reader &lead_data, const QPointF &vd); void drawHud(QPainter &p); + void drawDriverState(QPainter &painter, const UIState *s); inline QColor redColor(int alpha = 255) { return QColor(201, 34, 49, alpha); } inline QColor whiteColor(int alpha = 255) { return QColor(255, 255, 255, alpha); } inline QColor blackColor(int alpha = 255) { return QColor(0, 0, 0, alpha); } diff --git a/selfdrive/ui/qt/setup/reset.cc b/selfdrive/ui/qt/setup/reset.cc index 582217c1d7..ccf9bf8053 100644 --- a/selfdrive/ui/qt/setup/reset.cc +++ b/selfdrive/ui/qt/setup/reset.cc @@ -11,14 +11,11 @@ #define NVME "/dev/nvme0n1" #define USERDATA "/dev/disk/by-partlabel/userdata" -void Reset::doReset() { - // best effort to wipe nvme and sd card +void Reset::doErase() { + // best effort to wipe nvme std::system("sudo umount " NVME); std::system("yes | sudo mkfs.ext4 " NVME); - // we handle two cases here - // * user-prompted factory reset - // * recovering from a corrupt userdata by formatting int rm = std::system("sudo rm -rf /data/*"); std::system("sudo umount " USERDATA); int fmt = std::system("yes | sudo mkfs.ext4 " USERDATA); @@ -30,22 +27,26 @@ void Reset::doReset() { rebootBtn->show(); } +void Reset::startReset() { + body->setText(tr("Resetting device...\nThis may take up to a minute.")); + rejectBtn->hide(); + rebootBtn->hide(); + confirmBtn->hide(); +#ifdef __aarch64__ + QTimer::singleShot(100, this, &Reset::doErase); +#endif +} + void Reset::confirm() { const QString confirm_txt = tr("Are you sure you want to reset your device?"); if (body->text() != confirm_txt) { body->setText(confirm_txt); } else { - body->setText(tr("Resetting device...")); - rejectBtn->hide(); - rebootBtn->hide(); - confirmBtn->hide(); -#ifdef __aarch64__ - QTimer::singleShot(100, this, &Reset::doReset); -#endif + startReset(); } } -Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { +Reset::Reset(ResetMode mode, QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(45, 220, 45, 45); main_layout->setSpacing(0); @@ -56,7 +57,7 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { main_layout->addSpacing(60); - body = new QLabel(tr("System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot.")); + body = new QLabel(tr("Press confirm to erase all content and settings. Press cancel to resume boot.")); body->setWordWrap(true); body->setStyleSheet("font-size: 80px; font-weight: light;"); main_layout->addWidget(body, 1, Qt::AlignTop | Qt::AlignLeft); @@ -82,10 +83,16 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { blayout->addWidget(confirmBtn); QObject::connect(confirmBtn, &QPushButton::clicked, this, &Reset::confirm); + bool recover = mode == ResetMode::RECOVER; rejectBtn->setVisible(!recover); rebootBtn->setVisible(recover); if (recover) { - body->setText(tr("Unable to mount data partition. Press confirm to reset your device.")); + body->setText(tr("Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device.")); + } + + // automatically start if we're just finishing up an ABL reset + if (mode == ResetMode::FORMAT) { + startReset(); } setStyleSheet(R"( @@ -108,9 +115,17 @@ Reset::Reset(bool recover, QWidget *parent) : QWidget(parent) { } int main(int argc, char *argv[]) { - bool recover = argc > 1 && strcmp(argv[1], "--recover") == 0; + ResetMode mode = ResetMode::USER_RESET; + if (argc > 1) { + if (strcmp(argv[1], "--recover") == 0) { + mode = ResetMode::RECOVER; + } else if (strcmp(argv[1], "--format") == 0) { + mode = ResetMode::FORMAT; + } + } + QApplication a(argc, argv); - Reset reset(recover); + Reset reset(mode); setMainWindow(&reset); return a.exec(); } diff --git a/selfdrive/ui/qt/setup/reset.h b/selfdrive/ui/qt/setup/reset.h index 3a4994077c..04a191d829 100644 --- a/selfdrive/ui/qt/setup/reset.h +++ b/selfdrive/ui/qt/setup/reset.h @@ -2,18 +2,25 @@ #include #include +enum ResetMode { + USER_RESET, // user initiated a factory reset from openpilot + RECOVER, // userdata is corrupt for some reason, give a chance to recover + FORMAT, // finish up an ABL factory reset +}; + class Reset : public QWidget { Q_OBJECT public: - explicit Reset(bool recover = false, QWidget *parent = 0); + explicit Reset(ResetMode mode, QWidget *parent = 0); private: QLabel *body; QPushButton *rejectBtn; QPushButton *rebootBtn; QPushButton *confirmBtn; - void doReset(); + void doErase(); + void startReset(); private slots: void confirm(); diff --git a/selfdrive/ui/qt/setup/setup.cc b/selfdrive/ui/qt/setup/setup.cc index 69dafcf741..de5021c8bc 100644 --- a/selfdrive/ui/qt/setup/setup.cc +++ b/selfdrive/ui/qt/setup/setup.cc @@ -20,15 +20,29 @@ const std::string USER_AGENT = "AGNOSSetup-"; const QString DASHCAM_URL = "https://dashcam.comma.ai"; +bool is_elf(char *fname) { + FILE *fp = fopen(fname, "rb"); + if (fp == NULL) { + return false; + } + char buf[4]; + size_t n = fread(buf, 1, 4, fp); + fclose(fp); + return n == 4 && buf[0] == 0x7f && buf[1] == 'E' && buf[2] == 'L' && buf[3] == 'F'; +} + void Setup::download(QString url) { CURL *curl = curl_easy_init(); if (!curl) { - emit finished(false); + emit finished(url, tr("Something went wrong. Reboot the device.")); return; } auto version = util::read_file("/VERSION"); + struct curl_slist *list = NULL; + list = curl_slist_append(list, ("X-openpilot-serial: " + Hardware::get_serial()).c_str()); + char tmpfile[] = "/tmp/installer_XXXXXX"; FILE *fp = fdopen(mkstemp(tmpfile), "w"); @@ -38,18 +52,28 @@ void Setup::download(QString url) { curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_USERAGENT, (USER_AGENT + version).c_str()); + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, list); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, 30L); int ret = curl_easy_perform(curl); - long res_status = 0; curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &res_status); - if (ret == CURLE_OK && res_status == 200) { - rename(tmpfile, "/tmp/installer"); - emit finished(true); + + if (ret != CURLE_OK || res_status != 200) { + emit finished(url, tr("Ensure the entered URL is valid, and the device’s internet connection is good.")); + } else if (!is_elf(tmpfile)) { + emit finished(url, tr("No custom software found at this URL.")); } else { - emit finished(false); + rename(tmpfile, "/tmp/installer"); + + FILE *fp_url = fopen("/tmp/installer_url", "w"); + fprintf(fp_url, "%s", url.toStdString().c_str()); + fclose(fp_url); + + emit finished(url); } + curl_slist_free_all(list); curl_easy_cleanup(curl); fclose(fp); } @@ -170,7 +194,20 @@ QWidget * Setup::network_setup() { QPushButton *cont = new QPushButton(); cont->setObjectName("navBtn"); cont->setProperty("primary", true); - QObject::connect(cont, &QPushButton::clicked, this, &Setup::nextPage); + QObject::connect(cont, &QPushButton::clicked, [=]() { + auto w = currentWidget(); + QTimer::singleShot(0, [=]() { + setCurrentWidget(downloading_widget); + }); + QString url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); + if (!url.isEmpty()) { + QTimer::singleShot(1000, this, [=]() { + download(url); + }); + } else { + setCurrentWidget(w); + } + }); blayout->addWidget(cont); // setup timer for testing internet connection @@ -178,8 +215,8 @@ QWidget * Setup::network_setup() { QObject::connect(request, &HttpRequest::requestDone, [=](const QString &, bool success) { cont->setEnabled(success); if (success) { - const bool cell = networking->wifi->currentNetworkType() == NetworkType::CELL; - cont->setText(cell ? tr("Continue without Wi-Fi") : tr("Continue")); + const bool wifi = networking->wifi->currentNetworkType() == NetworkType::WIFI; + cont->setText(wifi ? tr("Continue") : tr("Continue without Wi-Fi")); } else { cont->setText(tr("Waiting for internet")); } @@ -197,106 +234,6 @@ QWidget * Setup::network_setup() { return widget; } -QWidget * radio_button(QString title, QButtonGroup *group) { - QPushButton *btn = new QPushButton(title); - btn->setCheckable(true); - group->addButton(btn); - btn->setStyleSheet(R"( - QPushButton { - height: 230; - padding-left: 100px; - padding-right: 100px; - text-align: left; - font-size: 80px; - font-weight: 400; - border-radius: 10px; - background-color: #4F4F4F; - } - QPushButton:checked { - background-color: #465BEA; - } - )"); - - // checkmark icon - QPixmap pix(":/img_circled_check.svg"); - btn->setIcon(pix); - btn->setIconSize(QSize(0, 0)); - btn->setLayoutDirection(Qt::RightToLeft); - QObject::connect(btn, &QPushButton::toggled, [=](bool checked) { - btn->setIconSize(checked ? QSize(104, 104) : QSize(0, 0)); - }); - return btn; -} - -QWidget * Setup::software_selection() { - QWidget *widget = new QWidget(); - QVBoxLayout *main_layout = new QVBoxLayout(widget); - main_layout->setContentsMargins(55, 50, 55, 50); - main_layout->setSpacing(0); - - // title - QLabel *title = new QLabel(tr("Choose Software to Install")); - title->setStyleSheet("font-size: 90px; font-weight: 500;"); - main_layout->addWidget(title, 0, Qt::AlignLeft | Qt::AlignTop); - - main_layout->addSpacing(50); - - // dashcam + custom radio buttons - QButtonGroup *group = new QButtonGroup(widget); - group->setExclusive(true); - - QWidget *dashcam = radio_button(tr("Dashcam"), group); - main_layout->addWidget(dashcam); - - main_layout->addSpacing(30); - - QWidget *custom = radio_button(tr("Custom Software"), group); - main_layout->addWidget(custom); - - main_layout->addStretch(); - - // back + continue buttons - QHBoxLayout *blayout = new QHBoxLayout; - main_layout->addLayout(blayout); - blayout->setSpacing(50); - - QPushButton *back = new QPushButton(tr("Back")); - back->setObjectName("navBtn"); - QObject::connect(back, &QPushButton::clicked, this, &Setup::prevPage); - blayout->addWidget(back); - - QPushButton *cont = new QPushButton(tr("Continue")); - cont->setObjectName("navBtn"); - cont->setEnabled(false); - cont->setProperty("primary", true); - blayout->addWidget(cont); - - QObject::connect(cont, &QPushButton::clicked, [=]() { - auto w = currentWidget(); - QTimer::singleShot(0, [=]() { - setCurrentWidget(downloading_widget); - }); - QString url = DASHCAM_URL; - if (group->checkedButton() != dashcam) { - url = InputDialog::getText(tr("Enter URL"), this, tr("for Custom Software")); - } - if (!url.isEmpty()) { - QTimer::singleShot(1000, this, [=]() { - download(url); - }); - } else { - setCurrentWidget(w); - } - }); - - connect(group, QOverload::of(&QButtonGroup::buttonClicked), [=](QAbstractButton *btn) { - btn->setChecked(true); - cont->setEnabled(true); - }); - - return widget; -} - QWidget * Setup::downloading() { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); @@ -306,10 +243,10 @@ QWidget * Setup::downloading() { return widget; } -QWidget * Setup::download_failed() { +QWidget * Setup::download_failed(QLabel *url, QLabel *body) { QWidget *widget = new QWidget(); QVBoxLayout *main_layout = new QVBoxLayout(widget); - main_layout->setContentsMargins(55, 225, 55, 55); + main_layout->setContentsMargins(55, 185, 55, 55); main_layout->setSpacing(0); QLabel *title = new QLabel(tr("Download Failed")); @@ -318,7 +255,13 @@ QWidget * Setup::download_failed() { main_layout->addSpacing(67); - QLabel *body = new QLabel(tr("Ensure the entered URL is valid, and the device’s internet connection is good.")); + url->setWordWrap(true); + url->setAlignment(Qt::AlignTop | Qt::AlignLeft); + url->setStyleSheet("font-family: \"JetBrains Mono\"; font-size: 64px; font-weight: 400; margin-right: 100px;"); + main_layout->addWidget(url); + + main_layout->addSpacing(48); + body->setWordWrap(true); body->setAlignment(Qt::AlignTop | Qt::AlignLeft); body->setStyleSheet("font-size: 80px; font-weight: 300; margin-right: 100px;"); @@ -343,7 +286,7 @@ QWidget * Setup::download_failed() { restart->setProperty("primary", true); blayout->addWidget(restart); QObject::connect(restart, &QPushButton::clicked, this, [=]() { - setCurrentIndex(2); + setCurrentIndex(1); }); widget->setStyleSheet(R"( @@ -372,20 +315,23 @@ Setup::Setup(QWidget *parent) : QStackedWidget(parent) { addWidget(getting_started()); addWidget(network_setup()); - addWidget(software_selection()); downloading_widget = downloading(); addWidget(downloading_widget); - failed_widget = download_failed(); + QLabel *url_label = new QLabel(); + QLabel *body_label = new QLabel(); + failed_widget = download_failed(url_label, body_label); addWidget(failed_widget); - QObject::connect(this, &Setup::finished, [=](bool success) { - // hide setup on success - qDebug() << "finished" << success; - if (success) { + QObject::connect(this, &Setup::finished, [=](const QString &url, const QString &error) { + qDebug() << "finished" << url << error; + if (error.isEmpty()) { + // hide setup on success QTimer::singleShot(3000, this, &QWidget::hide); } else { + url_label->setText(url); + body_label->setText(error); setCurrentWidget(failed_widget); } }); diff --git a/selfdrive/ui/qt/setup/setup.h b/selfdrive/ui/qt/setup/setup.h index 8027e8bd4f..bf5d97070d 100644 --- a/selfdrive/ui/qt/setup/setup.h +++ b/selfdrive/ui/qt/setup/setup.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include #include @@ -14,15 +15,14 @@ private: QWidget *low_voltage(); QWidget *getting_started(); QWidget *network_setup(); - QWidget *software_selection(); QWidget *downloading(); - QWidget *download_failed(); + QWidget *download_failed(QLabel *url, QLabel *body); QWidget *failed_widget; QWidget *downloading_widget; signals: - void finished(bool success); + void finished(const QString &url, const QString &error = ""); public slots: void nextPage(); diff --git a/selfdrive/ui/qt/sidebar.h b/selfdrive/ui/qt/sidebar.h index 53ad7467ac..fb96e1d540 100644 --- a/selfdrive/ui/qt/sidebar.h +++ b/selfdrive/ui/qt/sidebar.h @@ -20,7 +20,7 @@ public: explicit Sidebar(QWidget* parent = 0); signals: - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void valueChanged(); public slots: diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index c5697c1045..4f52310649 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -2,11 +2,14 @@ #include #include +#include #include #include #include #include #include +#include +#include #include "common/params.h" #include "common/swaglog.h" @@ -21,10 +24,6 @@ QString getBrand() { return Params().getBool("Passive") ? QObject::tr("dashcam") : QObject::tr("openpilot"); } -QString getBrandVersion() { - return getBrand() + " v" + getVersion().left(14).trimmed(); -} - QString getUserAgent() { return "openpilot-" + getVersion(); } @@ -222,3 +221,37 @@ QColor interpColor(float xv, std::vector xp, std::vector fp) { ); } } + +static QHash load_bootstrap_icons() { + QHash icons; + + QFile f(":/bootstrap-icons.svg"); + if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { + QDomDocument xml; + xml.setContent(&f); + QDomNode n = xml.documentElement().firstChild(); + while (!n.isNull()) { + QDomElement e = n.toElement(); + if (!e.isNull() && e.hasAttribute("id")) { + QString svg_str; + QTextStream stream(&svg_str); + n.save(stream, 0); + svg_str.replace("", ""); + icons[e.attribute("id")] = svg_str.toUtf8(); + } + n = n.nextSibling(); + } + } + return icons; +} + +QPixmap bootstrapPixmap(const QString &id) { + static QHash icons = load_bootstrap_icons(); + + QPixmap pixmap; + if (auto it = icons.find(id); it != icons.end()) { + pixmap.loadFromData(it.value(), "svg"); + } + return pixmap; +} diff --git a/selfdrive/ui/qt/util.h b/selfdrive/ui/qt/util.h index 209f051963..3188f3f9b9 100644 --- a/selfdrive/ui/qt/util.h +++ b/selfdrive/ui/qt/util.h @@ -11,7 +11,6 @@ QString getVersion(); QString getBrand(); -QString getBrandVersion(); QString getUserAgent(); std::optional getDongleId(); QMap getSupportedLanguages(); @@ -23,6 +22,7 @@ void swagLogMessageHandler(QtMsgType type, const QMessageLogContext &context, co void initApp(int argc, char *argv[]); QWidget* topWidget (QWidget* widget); QPixmap loadPixmap(const QString &fileName, const QSize &size = {}, Qt::AspectRatioMode aspectRatioMode = Qt::KeepAspectRatio); +QPixmap bootstrapPixmap(const QString &id); QRect getTextRect(QPainter &p, int flags, const QString &text); void drawRoundedRect(QPainter &painter, const QRectF &rect, qreal xRadiusTop, qreal yRadiusTop, qreal xRadiusBottom, qreal yRadiusBottom); diff --git a/selfdrive/ui/qt/widgets/cameraview.cc b/selfdrive/ui/qt/widgets/cameraview.cc index 200257235d..de3b64cffd 100644 --- a/selfdrive/ui/qt/widgets/cameraview.cc +++ b/selfdrive/ui/qt/widgets/cameraview.cc @@ -94,14 +94,17 @@ mat4 get_fit_view_transform(float widget_aspect_ratio, float frame_aspect_ratio) } // namespace CameraWidget::CameraWidget(std::string stream_name, VisionStreamType type, bool zoom, QWidget* parent) : - stream_name(stream_name), stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { + stream_name(stream_name), requested_stream_type(type), zoomed_view(zoom), QOpenGLWidget(parent) { setAttribute(Qt::WA_OpaquePaintEvent); - connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); - connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived); + qRegisterMetaType>("availableStreams"); + QObject::connect(this, &CameraWidget::vipcThreadConnected, this, &CameraWidget::vipcConnected, Qt::BlockingQueuedConnection); + QObject::connect(this, &CameraWidget::vipcThreadFrameReceived, this, &CameraWidget::vipcFrameReceived, Qt::QueuedConnection); + QObject::connect(this, &CameraWidget::vipcAvailableStreamsUpdated, this, &CameraWidget::availableStreamsUpdated, Qt::QueuedConnection); } CameraWidget::~CameraWidget() { makeCurrent(); + stopVipcThread(); if (isValid()) { glDeleteVertexArrays(1, &frame_vao); glDeleteBuffers(1, &frame_vbo); @@ -124,7 +127,7 @@ void CameraWidget::initializeGL() { GLint frame_pos_loc = program->attributeLocation("aPosition"); GLint frame_texcoord_loc = program->attributeLocation("aTexCoord"); - auto [x1, x2, y1, y2] = stream_type == VISION_STREAM_DRIVER ? std::tuple(0.f, 1.f, 1.f, 0.f) : std::tuple(1.f, 0.f, 1.f, 0.f); + auto [x1, x2, y1, y2] = requested_stream_type == VISION_STREAM_DRIVER ? std::tuple(0.f, 1.f, 1.f, 0.f) : std::tuple(1.f, 0.f, 1.f, 0.f); const uint8_t frame_indicies[] = {0, 1, 2, 0, 2, 3}; const float frame_coords[4][4] = { {-1.0, -1.0, x2, y1}, // bl @@ -162,8 +165,8 @@ void CameraWidget::initializeGL() { } void CameraWidget::showEvent(QShowEvent *event) { - frames.clear(); if (!vipc_thread) { + clearFrames(); vipc_thread = new QThread(); connect(vipc_thread, &QThread::started, [=]() { vipcThread(); }); connect(vipc_thread, &QThread::finished, vipc_thread, &QObject::deleteLater); @@ -171,7 +174,7 @@ void CameraWidget::showEvent(QShowEvent *event) { } } -void CameraWidget::hideEvent(QHideEvent *event) { +void CameraWidget::stopVipcThread() { if (vipc_thread) { vipc_thread->requestInterruption(); vipc_thread->quit(); @@ -180,19 +183,28 @@ void CameraWidget::hideEvent(QHideEvent *event) { } } +void CameraWidget::availableStreamsUpdated(std::set streams) { + available_streams = streams; +} + void CameraWidget::updateFrameMat() { int w = width(), h = height(); if (zoomed_view) { - if (stream_type == VISION_STREAM_DRIVER) { + if (active_stream_type == VISION_STREAM_DRIVER) { frame_mat = get_driver_view_transform(w, h, stream_width, stream_height); } else { - intrinsic_matrix = (stream_type == VISION_STREAM_WIDE_ROAD) ? ecam_intrinsic_matrix : fcam_intrinsic_matrix; - zoom = (stream_type == VISION_STREAM_WIDE_ROAD) ? 2.5 : 1.1; - // Project point at "infinity" to compute x and y offsets // to ensure this ends up in the middle of the screen + // for narrow come and a little lower for wide cam. // TODO: use proper perspective transform? + if (active_stream_type == VISION_STREAM_WIDE_ROAD) { + intrinsic_matrix = ecam_intrinsic_matrix; + zoom = 2.0; + } else { + intrinsic_matrix = fcam_intrinsic_matrix; + zoom = 1.1; + } const vec3 inf = {{1000., 0., 0.}}; const vec3 Ep = matvecmul3(calibration, inf); const vec3 Kep = matvecmul3(intrinsic_matrix, Ep); @@ -206,11 +218,11 @@ void CameraWidget::updateFrameMat() { x_offset = std::clamp(x_offset_, -max_x_offset, max_x_offset); y_offset = std::clamp(y_offset_, -max_y_offset, max_y_offset); - float zx = zoom * 2 * intrinsic_matrix.v[2] / width(); - float zy = zoom * 2 * intrinsic_matrix.v[5] / height(); + float zx = zoom * 2 * intrinsic_matrix.v[2] / w; + float zy = zoom * 2 * intrinsic_matrix.v[5] / h; const mat4 frame_transform = {{ - zx, 0.0, 0.0, -x_offset / width() * 2, - 0.0, zy, 0.0, y_offset / height() * 2, + zx, 0.0, 0.0, -x_offset / w * 2, + 0.0, zy, 0.0, y_offset / h * 2, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, }}; @@ -218,7 +230,7 @@ void CameraWidget::updateFrameMat() { } } else if (stream_width > 0 && stream_height > 0) { // fit frame to widget size - float widget_aspect_ratio = (float)width() / height(); + float widget_aspect_ratio = (float)w / h; float frame_aspect_ratio = (float)stream_width / stream_height; frame_mat = get_fit_view_transform(widget_aspect_ratio, frame_aspect_ratio); } @@ -226,13 +238,13 @@ void CameraWidget::updateFrameMat() { void CameraWidget::updateCalibration(const mat3 &calib) { calibration = calib; - updateFrameMat(); } void CameraWidget::paintGL() { glClearColor(bg.redF(), bg.greenF(), bg.blueF(), bg.alphaF()); glClear(GL_STENCIL_BUFFER_BIT | GL_COLOR_BUFFER_BIT); + std::lock_guard lk(frame_lock); if (frames.empty()) return; int frame_idx = frames.size() - 1; @@ -249,19 +261,23 @@ void CameraWidget::paintGL() { qDebug() << "Skipped frame" << frames[frame_idx].first; } prev_frame_id = frames[frame_idx].first; + VisionBuf *frame = frames[frame_idx].second; + assert(frame != nullptr); + + updateFrameMat(); glViewport(0, 0, width(), height()); glBindVertexArray(frame_vao); glUseProgram(program->programId()); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - VisionBuf *frame = frames[frame_idx].second; - #ifdef QCOM2 + // no frame copy glActiveTexture(GL_TEXTURE0); glEGLImageTargetTexture2DOES(GL_TEXTURE_EXTERNAL_OES, egl_images[frame->idx]); assert(glGetError() == GL_NO_ERROR); #else + // fallback to copy glPixelStorei(GL_UNPACK_ROW_LENGTH, stream_stride); glActiveTexture(GL_TEXTURE0); glBindTexture(GL_TEXTURE_2D, textures[0]); @@ -288,7 +304,6 @@ void CameraWidget::paintGL() { void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { makeCurrent(); - frames.clear(); stream_width = vipc_client->buffers[0].width; stream_height = vipc_client->buffers[0].height; stream_stride = vipc_client->buffers[0].stride; @@ -335,30 +350,35 @@ void CameraWidget::vipcConnected(VisionIpcClient *vipc_client) { glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, stream_width/2, stream_height/2, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr); assert(glGetError() == GL_NO_ERROR); #endif - - updateFrameMat(); } -void CameraWidget::vipcFrameReceived(VisionBuf *buf, uint32_t frame_id) { - frames.push_back(std::make_pair(frame_id, buf)); - while (frames.size() > FRAME_BUFFER_SIZE) { - frames.pop_front(); - } +void CameraWidget::vipcFrameReceived() { update(); } void CameraWidget::vipcThread() { - VisionStreamType cur_stream_type = stream_type; + VisionStreamType cur_stream = requested_stream_type; std::unique_ptr vipc_client; VisionIpcBufExtra meta_main = {0}; while (!QThread::currentThread()->isInterruptionRequested()) { - if (!vipc_client || cur_stream_type != stream_type) { - cur_stream_type = stream_type; - vipc_client.reset(new VisionIpcClient(stream_name, cur_stream_type, false)); + if (!vipc_client || cur_stream != requested_stream_type) { + clearFrames(); + qDebug().nospace() << "connecting to stream " << requested_stream_type << ", was connected to " << cur_stream; + cur_stream = requested_stream_type; + vipc_client.reset(new VisionIpcClient(stream_name, cur_stream, false)); } + active_stream_type = cur_stream; if (!vipc_client->connected) { + clearFrames(); + auto streams = VisionIpcClient::getAvailableStreams(stream_name, false); + if (streams.empty()) { + QThread::msleep(100); + continue; + } + emit vipcAvailableStreamsUpdated(streams); + if (!vipc_client->connect(false)) { QThread::msleep(100); continue; @@ -367,7 +387,18 @@ void CameraWidget::vipcThread() { } if (VisionBuf *buf = vipc_client->recv(&meta_main, 1000)) { - emit vipcThreadFrameReceived(buf, meta_main.frame_id); + { + std::lock_guard lk(frame_lock); + frames.push_back(std::make_pair(meta_main.frame_id, buf)); + while (frames.size() > FRAME_BUFFER_SIZE) { + frames.pop_front(); + } + } + emit vipcThreadFrameReceived(); + } else { + if (!isVisible()) { + vipc_client->connected = false; + } } } @@ -378,3 +409,9 @@ void CameraWidget::vipcThread() { egl_images.clear(); #endif } + +void CameraWidget::clearFrames() { + std::lock_guard lk(frame_lock); + frames.clear(); + available_streams.clear(); +} diff --git a/selfdrive/ui/qt/widgets/cameraview.h b/selfdrive/ui/qt/widgets/cameraview.h index 9bcad935c0..9135620d35 100644 --- a/selfdrive/ui/qt/widgets/cameraview.h +++ b/selfdrive/ui/qt/widgets/cameraview.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -30,25 +31,28 @@ public: using QOpenGLWidget::QOpenGLWidget; explicit CameraWidget(std::string stream_name, VisionStreamType stream_type, bool zoom, QWidget* parent = nullptr); ~CameraWidget(); - void setStreamType(VisionStreamType type) { stream_type = type; } void setBackgroundColor(const QColor &color) { bg = color; } void setFrameId(int frame_id) { draw_frame_id = frame_id; } + void setStreamType(VisionStreamType type) { requested_stream_type = type; } + VisionStreamType getStreamType() { return active_stream_type; } + void stopVipcThread(); signals: void clicked(); void vipcThreadConnected(VisionIpcClient *); - void vipcThreadFrameReceived(VisionBuf *, quint32); + void vipcThreadFrameReceived(); + void vipcAvailableStreamsUpdated(std::set); protected: void paintGL() override; void initializeGL() override; void resizeGL(int w, int h) override { updateFrameMat(); } void showEvent(QShowEvent *event) override; - void hideEvent(QHideEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override { emit clicked(); } virtual void updateFrameMat(); void updateCalibration(const mat3 &calib); void vipcThread(); + void clearFrames(); bool zoomed_view; GLuint frame_vao, frame_vbo, frame_ibo; @@ -66,7 +70,9 @@ protected: int stream_width = 0; int stream_height = 0; int stream_stride = 0; - std::atomic stream_type; + std::atomic active_stream_type; + std::atomic requested_stream_type; + std::set available_streams; QThread *vipc_thread = nullptr; // Calibration @@ -76,11 +82,15 @@ protected: mat3 calibration = DEFAULT_CALIBRATION; mat3 intrinsic_matrix = fcam_intrinsic_matrix; + std::recursive_mutex frame_lock; std::deque> frames; uint32_t draw_frame_id = 0; uint32_t prev_frame_id = 0; protected slots: void vipcConnected(VisionIpcClient *vipc_client); - void vipcFrameReceived(VisionBuf *vipc_client, uint32_t frame_id); + void vipcFrameReceived(); + void availableStreamsUpdated(std::set streams); }; + +Q_DECLARE_METATYPE(std::set); diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index a1ebf57b07..456cf748f4 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -26,10 +26,10 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons hlayout->setSpacing(20); // left icon + icon_label = new QLabel(); if (!icon.isEmpty()) { - QPixmap pix(icon); - QLabel *icon_label = new QLabel(); - icon_label->setPixmap(pix.scaledToWidth(80, Qt::SmoothTransformation)); + icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + icon_label->setPixmap(icon_pixmap); icon_label->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed)); hlayout->addWidget(icon_label); } @@ -38,10 +38,10 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons title_label = new QPushButton(title); title_label->setFixedHeight(120); title_label->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left"); - hlayout->addWidget(title_label); + hlayout->addWidget(title_label, 1); // value next to control button - value = new QLabel(); + value = new ElidedLabel(); value->setAlignment(Qt::AlignRight | Qt::AlignVCenter); value->setStyleSheet("color: #aaaaaa"); hlayout->addWidget(value); @@ -70,7 +70,7 @@ AbstractControl::AbstractControl(const QString &title, const QString &desc, cons } void AbstractControl::hideEvent(QHideEvent *e) { - if(description != nullptr) { + if (description != nullptr) { description->hide(); } } diff --git a/selfdrive/ui/qt/widgets/controls.h b/selfdrive/ui/qt/widgets/controls.h index c42716828f..770b9b92dd 100644 --- a/selfdrive/ui/qt/widgets/controls.h +++ b/selfdrive/ui/qt/widgets/controls.h @@ -7,6 +7,7 @@ #include #include "common/params.h" +#include "selfdrive/ui/qt/widgets/input.h" #include "selfdrive/ui/qt/widgets/toggle.h" QFrame *horizontal_line(QWidget *parent = nullptr); @@ -49,6 +50,13 @@ public: value->setText(val); } + const QString getDescription() { + return description->text(); + } + + QLabel *icon_label; + QPixmap icon_pixmap; + public slots: void showDescription() { description->setVisible(true); @@ -65,7 +73,7 @@ protected: QPushButton *title_label; private: - QLabel *value; + ElidedLabel *value; QLabel *description = nullptr; }; @@ -137,13 +145,35 @@ public: ParamControl(const QString ¶m, const QString &title, const QString &desc, const QString &icon, QWidget *parent = nullptr) : ToggleControl(title, desc, icon, false, parent) { key = param.toStdString(); QObject::connect(this, &ParamControl::toggleFlipped, [=](bool state) { - params.putBool(key, state); + QString content("

" + title + "


" + "

" + getDescription() + "

"); + ConfirmationDialog dialog(content, tr("Enable"), tr("Cancel"), true, this); + + bool confirmed = store_confirm && params.getBool(key + "Confirmed"); + if (!confirm || confirmed || !state || dialog.exec()) { + if (store_confirm && state) params.putBool(key + "Confirmed", true); + params.putBool(key, state); + setIcon(state); + } else { + toggle.togglePosition(); + } }); } + void setConfirmation(bool _confirm, bool _store_confirm) { + confirm = _confirm; + store_confirm = _store_confirm; + }; + + void setActiveIcon(const QString &icon) { + active_icon_pixmap = QPixmap(icon).scaledToWidth(80, Qt::SmoothTransformation); + } + void refresh() { - if (params.getBool(key) != toggle.on) { + bool state = params.getBool(key); + if (state != toggle.on) { toggle.togglePosition(); + setIcon(state); } }; @@ -152,8 +182,19 @@ public: }; private: + void setIcon(bool state) { + if (state && !active_icon_pixmap.isNull()) { + icon_label->setPixmap(active_icon_pixmap); + } else if (!icon_pixmap.isNull()) { + icon_label->setPixmap(icon_pixmap); + } + }; + std::string key; Params params; + QPixmap active_icon_pixmap; + bool confirm = false; + bool store_confirm = false; }; class ListWidget : public QWidget { diff --git a/selfdrive/ui/qt/widgets/input.cc b/selfdrive/ui/qt/widgets/input.cc index d703825885..2026a70478 100644 --- a/selfdrive/ui/qt/widgets/input.cc +++ b/selfdrive/ui/qt/widgets/input.cc @@ -183,17 +183,21 @@ void InputDialog::setMinLength(int length) { // ConfirmationDialog ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, const QString &cancel_text, - QWidget *parent) : QDialogBase(parent) { + const bool rich, QWidget *parent) : QDialogBase(parent) { QFrame *container = new QFrame(this); - container->setStyleSheet("QFrame { border-radius: 0; background-color: #ECECEC; }"); + container->setStyleSheet(R"( + QFrame { background-color: #1B1B1B; color: #C9C9C9; } + #confirm_btn { background-color: #465BEA; } + #confirm_btn:pressed { background-color: #3049F4; } + )"); QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, 120, 32, 32); + main_layout->setContentsMargins(32, rich ? 32 : 120, 32, 32); QLabel *prompt = new QLabel(prompt_text, this); prompt->setWordWrap(true); - prompt->setAlignment(Qt::AlignHCenter); - prompt->setStyleSheet("font-size: 70px; font-weight: bold; color: black;"); - main_layout->addWidget(prompt, 1, Qt::AlignTop | Qt::AlignHCenter); + prompt->setAlignment(rich ? Qt::AlignLeft : Qt::AlignHCenter); + prompt->setStyleSheet((rich ? "font-size: 42px; font-weight: light;" : "font-size: 70px; font-weight: bold;") + QString(" margin: 45px;")); + main_layout->addWidget(rich ? (QWidget*)new ScrollView(prompt, this) : (QWidget*)prompt, 1, Qt::AlignTop); // cancel + confirm buttons QHBoxLayout *btn_layout = new QHBoxLayout(); @@ -208,54 +212,29 @@ ConfirmationDialog::ConfirmationDialog(const QString &prompt_text, const QString if (confirm_text.length()) { QPushButton* confirm_btn = new QPushButton(confirm_text); + confirm_btn->setObjectName("confirm_btn"); btn_layout->addWidget(confirm_btn); QObject::connect(confirm_btn, &QPushButton::clicked, this, &ConfirmationDialog::accept); } QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(210, 170, 210, 170); + int margin = rich ? 100 : 200; + outer_layout->setContentsMargins(margin, margin, margin, margin); outer_layout->addWidget(container); } bool ConfirmationDialog::alert(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", parent); + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", false, parent); return d.exec(); } -bool ConfirmationDialog::confirm(const QString &prompt_text, QWidget *parent) { - ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), tr("Cancel"), parent); +bool ConfirmationDialog::confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent) { + ConfirmationDialog d = ConfirmationDialog(prompt_text, confirm_text, tr("Cancel"), false, parent); return d.exec(); } - -// RichTextDialog - -RichTextDialog::RichTextDialog(const QString &prompt_text, const QString &btn_text, - QWidget *parent) : QDialogBase(parent) { - QFrame *container = new QFrame(this); - container->setStyleSheet("QFrame { background-color: #1B1B1B; }"); - QVBoxLayout *main_layout = new QVBoxLayout(container); - main_layout->setContentsMargins(32, 32, 32, 32); - - QLabel *prompt = new QLabel(prompt_text, this); - prompt->setWordWrap(true); - prompt->setAlignment(Qt::AlignLeft); - prompt->setTextFormat(Qt::RichText); - prompt->setStyleSheet("font-size: 42px; font-weight: light; color: #C9C9C9; margin: 45px;"); - main_layout->addWidget(new ScrollView(prompt, this), 1, Qt::AlignTop); - - // confirm button - QPushButton* confirm_btn = new QPushButton(btn_text); - main_layout->addWidget(confirm_btn); - QObject::connect(confirm_btn, &QPushButton::clicked, this, &QDialog::accept); - - QVBoxLayout *outer_layout = new QVBoxLayout(this); - outer_layout->setContentsMargins(100, 100, 100, 100); - outer_layout->addWidget(container); -} - -bool RichTextDialog::alert(const QString &prompt_text, QWidget *parent) { - auto d = RichTextDialog(prompt_text, tr("Ok"), parent); +bool ConfirmationDialog::rich(const QString &prompt_text, QWidget *parent) { + ConfirmationDialog d = ConfirmationDialog(prompt_text, tr("Ok"), "", true, parent); return d.exec(); } @@ -317,12 +296,13 @@ MultiOptionDialog::MultiOptionDialog(const QString &prompt_text, const QStringLi group->addButton(selectionLabel); listLayout->addWidget(selectionLabel); } + // add stretch to keep buttons spaced correctly + listLayout->addStretch(1); ScrollView *scroll_view = new ScrollView(listWidget, this); scroll_view->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); main_layout->addWidget(scroll_view); - main_layout->addStretch(1); main_layout->addSpacing(35); // cancel + confirm buttons diff --git a/selfdrive/ui/qt/widgets/input.h b/selfdrive/ui/qt/widgets/input.h index 6c47a31d87..e6c0fba86d 100644 --- a/selfdrive/ui/qt/widgets/input.h +++ b/selfdrive/ui/qt/widgets/input.h @@ -55,18 +55,10 @@ class ConfirmationDialog : public QDialogBase { public: explicit ConfirmationDialog(const QString &prompt_text, const QString &confirm_text, - const QString &cancel_text, QWidget* parent); - static bool alert(const QString &prompt_text, QWidget *parent); - static bool confirm(const QString &prompt_text, QWidget *parent); -}; - -// larger ConfirmationDialog for rich text -class RichTextDialog : public QDialogBase { - Q_OBJECT - -public: - explicit RichTextDialog(const QString &prompt_text, const QString &btn_text, QWidget* parent); + const QString &cancel_text, const bool rich, QWidget* parent); static bool alert(const QString &prompt_text, QWidget *parent); + static bool confirm(const QString &prompt_text, const QString &confirm_text, QWidget *parent); + static bool rich(const QString &prompt_text, QWidget *parent); }; class MultiOptionDialog : public QDialogBase { diff --git a/selfdrive/ui/qt/widgets/scrollview.cc b/selfdrive/ui/qt/widgets/scrollview.cc index bd4309d8d0..5536593016 100644 --- a/selfdrive/ui/qt/widgets/scrollview.cc +++ b/selfdrive/ui/qt/widgets/scrollview.cc @@ -3,6 +3,8 @@ #include #include +// TODO: disable horizontal scrolling and resize + ScrollView::ScrollView(QWidget *w, QWidget *parent) : QScrollArea(parent) { setWidget(w); setWidgetResizable(true); diff --git a/selfdrive/ui/qt/window.cc b/selfdrive/ui/qt/window.cc index 04ce15ef23..198b1edbf6 100644 --- a/selfdrive/ui/qt/window.cc +++ b/selfdrive/ui/qt/window.cc @@ -53,6 +53,7 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { QFontDatabase::addApplicationFont("../assets/fonts/Inter-Regular.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-SemiBold.ttf"); QFontDatabase::addApplicationFont("../assets/fonts/Inter-Thin.ttf"); + QFontDatabase::addApplicationFont("../assets/fonts/JetBrainsMono-Medium.ttf"); // no outline to prevent the focus rectangle setStyleSheet(R"( @@ -64,8 +65,9 @@ MainWindow::MainWindow(QWidget *parent) : QWidget(parent) { setAttribute(Qt::WA_NoSystemBackground); } -void MainWindow::openSettings() { +void MainWindow::openSettings(int index, const QString ¶m) { main_layout->setCurrentWidget(settingsWindow); + settingsWindow->setCurrentPanel(index, param); } void MainWindow::closeSettings() { diff --git a/selfdrive/ui/qt/window.h b/selfdrive/ui/qt/window.h index 0bd328aa8a..71fc466c20 100644 --- a/selfdrive/ui/qt/window.h +++ b/selfdrive/ui/qt/window.h @@ -15,7 +15,7 @@ public: private: bool eventFilter(QObject *obj, QEvent *event) override; - void openSettings(); + void openSettings(int index = 0, const QString ¶m = ""); void closeSettings(); Device device; diff --git a/selfdrive/ui/soundd/sound.cc b/selfdrive/ui/soundd/sound.cc index 6830450d8f..49c28373c5 100644 --- a/selfdrive/ui/soundd/sound.cc +++ b/selfdrive/ui/soundd/sound.cc @@ -12,7 +12,7 @@ // TODO: detect when we can't play sounds // TODO: detect when we can't display the UI -Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) { +Sound::Sound(QObject *parent) : sm({"controlsState", "deviceState", "microphone"}) { qInfo() << "default audio device: " << QAudioDeviceInfo::defaultOutputDevice().deviceName(); for (auto &[alert, fn, loops] : sound_list) { @@ -20,7 +20,6 @@ Sound::Sound(QObject *parent) : sm({"carState", "controlsState", "deviceState"}) QObject::connect(s, &QSoundEffect::statusChanged, [=]() { assert(s->status() != QSoundEffect::Error); }); - s->setVolume(Hardware::MIN_VOLUME); s->setSource(QUrl::fromLocalFile("../../assets/sounds/" + fn)); sounds[alert] = {s, loops}; } @@ -47,14 +46,11 @@ void Sound::update() { return; } - // scale volume with speed - if (sm.updated("carState")) { - float volume = util::map_val(sm["carState"].getCarState().getVEgo(), 11.f, 20.f, 0.f, 1.0f); + // scale volume using ambient noise level + if (sm.updated("microphone")) { + float volume = util::map_val(sm["microphone"].getMicrophone().getFilteredSoundPressureWeightedDb(), 30.f, 60.f, 0.f, 1.f); volume = QAudio::convertVolume(volume, QAudio::LogarithmicVolumeScale, QAudio::LinearVolumeScale); - volume = util::map_val(volume, 0.f, 1.f, Hardware::MIN_VOLUME, Hardware::MAX_VOLUME); - for (auto &[s, loops] : sounds) { - s->setVolume(std::round(100 * volume) / 100); - } + Hardware::set_volume(volume); } setAlert(Alert::get(sm, started_frame)); diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 11ecd30ae0..26d6c39349 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -9,6 +9,7 @@ import xml.etree.ElementTree as ET from selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations TMP_TRANSLATIONS_DIR = os.path.join(TRANSLATIONS_DIR, "tmp") +UNFINISHED_TRANSLATION_TAG = "" not in cur_translations, + self.assertTrue(UNFINISHED_TRANSLATION_TAG not in cur_translations, f"{file} ({name}) translation file has unfinished translations. Finish translations or mark them as completed in Qt Linguist") def test_vanished_translations(self): diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py index 32904f242a..575584dd50 100755 --- a/selfdrive/ui/translations/create_badges.py +++ b/selfdrive/ui/translations/create_badges.py @@ -2,19 +2,22 @@ import json import os import requests +import xml.etree.ElementTree as ET from common.basedir import BASEDIR +from selfdrive.ui.tests.test_translations import UNFINISHED_TRANSLATION_TAG from selfdrive.ui.update_translations import LANGUAGES_FILE, TRANSLATIONS_DIR TRANSLATION_TAG = "'] + badge_svg = [] + max_badge_width = 0 # keep track of max width to set parent element for idx, (name, file) in enumerate(translation_files.items()): with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts"), "r") as tr_f: tr_file = tr_f.read() @@ -28,19 +31,30 @@ if __name__ == "__main__": unfinished_translations += 1 percent_finished = int(100 - (unfinished_translations / total_translations * 100.)) - color = "green" if percent_finished == 100 else "orange" if percent_finished >= 70 else "red" + color = "green" if percent_finished == 100 else "orange" if percent_finished > 90 else "red" - r = requests.get(f"https://img.shields.io/badge/LANGUAGE {name}-{percent_finished}%25 complete-{color}", timeout=10) + # Download badge + badge_label = f"LANGUAGE {name}" + badge_message = f"{percent_finished}% complete" + if unfinished_translations != 0: + badge_message += f" ({unfinished_translations} unfinished)" + + r = requests.get(f"{SHIELDS_URL}/{badge_label}-{badge_message}-{color}", timeout=10) assert r.status_code == 200, "Error downloading badge" content_svg = r.content.decode("utf-8") - # make tag ids in each badge unique + xml = ET.fromstring(content_svg) + assert "width" in xml.attrib + max_badge_width = max(max_badge_width, int(xml.attrib["width"])) + + # Make tag ids in each badge unique to combine them into one svg for tag in ("r", "s"): content_svg = content_svg.replace(f'id="{tag}"', f'id="{tag}{idx}"') content_svg = content_svg.replace(f'"url(#{tag})"', f'"url(#{tag}{idx})"') badge_svg.extend([f'', content_svg, ""]) + badge_svg.insert(0, f'') badge_svg.append("") with open(os.path.join(BASEDIR, "translation_badge.svg"), "w") as badge_f: diff --git a/selfdrive/ui/translations/languages.json b/selfdrive/ui/translations/languages.json index 072d320c13..86d3e62d87 100644 --- a/selfdrive/ui/translations/languages.json +++ b/selfdrive/ui/translations/languages.json @@ -1,5 +1,6 @@ { "English": "main_en", + "Deutsch": "main_de", "Português": "main_pt-BR", "中文(繁體)": "main_zh-CHT", "中文(简体)": "main_zh-CHS", diff --git a/selfdrive/ui/translations/main_ar.ts b/selfdrive/ui/translations/main_ar.ts index a94d2fbc05..07a84fca08 100644 --- a/selfdrive/ui/translations/main_ar.ts +++ b/selfdrive/ui/translations/main_ar.ts @@ -4,17 +4,14 @@ AbstractAlert - Close أغلق - Snooze Update تأخير التحديث - Reboot and Update إعادة التشغيل والتحديث @@ -22,67 +19,84 @@ AdvancedNetworking - Back خلف - Enable Tethering تمكين الربط - Tethering Password كلمة مرور للربط - - EDIT تعديل - Enter new tethering password أدخل كلمة مرور جديدة للربط - IP Address عنوان IP - Enable Roaming تمكين التجوال - APN Setting إعداد APN - Enter APN أدخل APN - leave blank for automatic configuration اتركه فارغا للتكوين التلقائي + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + + + + AnnotatedCameraWidget + + km/h + km/h + + + mph + mph + + + MAX + الأعلى + + + SPEED + سرعة + + + LIMIT + حد + ConfirmationDialog - - Ok موافق - Cancel إلغاء @@ -90,17 +104,14 @@ DeclinePage - You must accept the Terms and Conditions in order to use openpilot. يجب عليك قبول الشروط والأحكام من أجل استخدام openpilot. - Back خلف - Decline, uninstall %1 رفض ، قم بإلغاء تثبيت %1 @@ -108,152 +119,122 @@ DevicePanel - Dongle ID معرف دونجل - N/A غير متاح - Serial التسلسلي - Driver Camera كاميرا السائق - PREVIEW لمح - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) قم بمعاينة الكاميرا المواجهة للسائق للتأكد من أن نظام مراقبة السائق يتمتع برؤية جيدة. (يجب أن تكون السيارة معطلة) - Reset Calibration إعادة ضبط المعايرة - RESET إعادة تعيين - Are you sure you want to reset calibration? هل أنت متأكد أنك تريد إعادة ضبط المعايرة؟ - Review Training Guide مراجعة دليل التدريب - REVIEW مراجعة - Review the rules, features, and limitations of openpilot راجع القواعد والميزات والقيود الخاصة بـ openpilot - Are you sure you want to review the training guide? هل أنت متأكد أنك تريد مراجعة دليل التدريب؟ - Regulatory تنظيمية - VIEW عرض - Change Language تغيير اللغة - CHANGE تغيير - Select a language اختر لغة - Reboot اعادة التشغيل - Power Off أطفاء - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. يتطلب openpilot أن يتم تركيب الجهاز في حدود 4 درجات يسارًا أو يمينًا و 5 درجات لأعلى أو 8 درجات لأسفل. يقوم برنامج openpilot بالمعايرة بشكل مستمر ، ونادراً ما تكون إعادة الضبط مطلوبة. - Your device is pointed %1° %2 and %3° %4. جهازك يشير %1° %2 و %3° %4. - down لأسفل - up إلى أعلى - left إلى اليسار - right إلى اليمين - Are you sure you want to reboot? هل أنت متأكد أنك تريد إعادة التشغيل؟ - Disengage to Reboot فك الارتباط لإعادة التشغيل - Are you sure you want to power off? هل أنت متأكد أنك تريد إيقاف التشغيل؟ - Disengage to Power Off فك الارتباط لإيقاف التشغيل @@ -261,32 +242,26 @@ DriveStats - Drives أرقام القيادة - Hours ساعات - ALL TIME في كل وقت - PAST WEEK الأسبوع الماضي - KM كم - Miles اميال @@ -294,7 +269,6 @@ DriverViewScene - camera starting بدء تشغيل الكاميرا @@ -302,12 +276,10 @@ InputDialog - Cancel إلغاء - Need at least %n character(s)! تحتاج على الأقل %n حرف! @@ -322,22 +294,18 @@ Installer - Installing... جارٍ التثبيت ... - Receiving objects: استقبال الكائنات: - Resolving deltas: حل دلتا: - Updating files: جارٍ تحديث الملفات: @@ -345,27 +313,22 @@ MapETA - eta eta - min دق - hr سع - km كم - mi مل @@ -373,22 +336,18 @@ MapInstructions - km كم - m م - mi مل - ft قد @@ -396,48 +355,40 @@ MapPanel - Current Destination الوجهة الحالية - CLEAR مسح - Recent Destinations الوجهات الأخيرة - Try the Navigation Beta جرب التنقل التجريبي - Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai احصل على الاتجاهات خطوة بخطوة معروضة والمزيد باستخدام comma الاشتراك الرئيسي. اشترك الآن: https://connect.comma.ai - No home location set لم يتم تعيين موقع المنزل - No work location set لم يتم تعيين موقع العمل - no recent destinations لا توجد وجهات حديثة @@ -445,12 +396,10 @@ location set MapWindow - Map Loading تحميل الخريطة - Waiting for GPS في انتظار GPS @@ -458,12 +407,10 @@ location set MultiOptionDialog - Select اختر - Cancel إلغاء @@ -471,72 +418,33 @@ location set Networking - Advanced متقدم - Enter password أدخل كلمة المرور - - for "%1" ل "%1" - Wrong password كلمة مرور خاطئة - - AnnotatedCameraWidget - - - km/h - km/h - - - - mph - mph - - - - - MAX - الأعلى - - - - - SPEED - سرعة - - - - - LIMIT - حد - - OffroadHome - UPDATE تحديث - ALERTS تنبيهات - ALERT تنبيه @@ -544,22 +452,18 @@ location set PairingPopup - Pair your device to your comma account قم بإقران جهازك بحساب comma الخاص بك - Go to https://connect.comma.ai on your phone اذهب إلى https://connect.comma.ai من هاتفك - Click "add new device" and scan the QR code on the right انقر على "إضافة جهاز جديد" وامسح رمز الاستجابة السريعة على اليمين - Bookmark connect.comma.ai to your home screen to use it like an app ضع إشارة مرجعية على connect.comma.ai على شاشتك الرئيسية لاستخدامه مثل أي تطبيق @@ -567,32 +471,26 @@ location set PrimeAdWidget - Upgrade Now قم بالترقية الآن - Become a comma prime member at connect.comma.ai كن عضوًا comme prime في connect.comma.ai - PRIME FEATURES: ميزات PRIME: - Remote access الوصول عن بعد - 1 year of storage سنة واحدة من التخزين - Developer perks امتيازات المطور @@ -600,22 +498,18 @@ location set PrimeUserWidget - ✓ SUBSCRIBED ✓ مشترك - comma prime comma prime - CONNECT.COMMA.AI CONNECT.COMMA.AI - COMMA POINTS COMMA POINTS @@ -623,27 +517,22 @@ location set QObject - Reboot اعادة التشغيل - Exit أغلق - dashcam dashcam - openpilot openpilot - %n minute(s) ago منذ %n دقيقة @@ -655,7 +544,6 @@ location set - %n hour(s) ago منذ %n ساعة @@ -667,7 +555,6 @@ location set - %n day(s) ago منذ %n يوم @@ -682,47 +569,38 @@ location set Reset - Reset failed. Reboot to try again. فشل إعادة التعيين. أعد التشغيل للمحاولة مرة أخرى. - Are you sure you want to reset your device? هل أنت متأكد أنك تريد إعادة ضبط جهازك؟ - Resetting device... جارٍ إعادة ضبط الجهاز ... - System Reset إعادة تعيين النظام - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. تم تشغيل إعادة تعيين النظام. اضغط على تأكيد لمسح كل المحتوى والإعدادات. اضغط على إلغاء لاستئناف التمهيد. - Cancel إلغاء - Reboot اعادة التشغيل - Confirm تأكيد - Unable to mount data partition. Press confirm to reset your device. تعذر تحميل قسم البيانات. اضغط على تأكيد لإعادة ضبط جهازك. @@ -730,7 +608,6 @@ location set RichTextDialog - Ok موافق @@ -738,33 +615,26 @@ location set SettingsWindow - × x - Device جهاز - - Network شبكة الاتصال - Toggles التبديل - Software برمجة - Navigation ملاحة @@ -772,105 +642,82 @@ location set Setup - WARNING: Low Voltage تحذير: الجهد المنخفض - Power your device in a car with a harness or proceed at your own risk. قم بتشغيل جهازك في سيارة باستخدام أداة تثبيت أو المضي قدمًا على مسؤوليتك الخاصة. - Power off اطفئ الجهاز - - - Continue أكمل - Getting Started ابدء - Before we get on the road, let’s finish installation and cover some details. قبل أن ننطلق على الطريق ، دعنا ننتهي من التثبيت ونغطي بعض التفاصيل. - Connect to Wi-Fi اتصل بشبكة Wi-Fi - - Back خلف - Continue without Wi-Fi استمر بدون Wi-Fi - Waiting for internet في انتظار الاتصال بالإنترنت - Choose Software to Install اختر البرنامج المراد تثبيته - Dashcam Dashcam - Custom Software برامج مخصصة - Enter URL إدخال عنوان الموقع - for Custom Software للبرامج المخصصة - Downloading... جارى التحميل... - Download Failed فشل التنزيل - Ensure the entered URL is valid, and the device’s internet connection is good. تأكد من أن عنوان موقع الويب الذي تم إدخاله صالح ، وأن اتصال الجهاز بالإنترنت جيد. - Reboot device إعادة تشغيل الجهاز - Start over ابدأ من جديد @@ -878,17 +725,14 @@ location set SetupWidget - Finish Setup إنهاء الإعداد - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. قم بإقران جهازك بفاصلة connect (connect.comma.ai) واطلب عرض comma prime الخاص بك. - Pair device إقران الجهاز @@ -896,106 +740,82 @@ location set Sidebar - - CONNECT الاتصال - OFFLINE غير متصل - - ONLINE متصل - ERROR خطأ - - - TEMP درجة الحرارة - HIGH عالي - GOOD جيد - OK موافق - VEHICLE مركبة - NO لا - PANDA PANDA - GPS GPS - SEARCH بحث - -- -- - Wi-Fi Wi-Fi - ETH ETH - 2G 2G - 3G 3G - LTE LTE - 5G 5G @@ -1003,138 +823,141 @@ location set SoftwarePanel - Git Branch - Git Branch + Git Branch - Git Commit - Git Commit + Git Commit - OS Version - إصدار نظام التشغيل + إصدار نظام التشغيل - Version - إصدار + إصدار - Last Update Check - التحقق من آخر تحديث + التحقق من آخر تحديث - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - آخر مرة نجح برنامج openpilot في التحقق من التحديث. يعمل المحدث فقط أثناء إيقاف تشغيل السيارة. + آخر مرة نجح برنامج openpilot في التحقق من التحديث. يعمل المحدث فقط أثناء إيقاف تشغيل السيارة. - Check for Update - فحص التحديثات + فحص التحديثات - CHECKING - تدقيق + تدقيق - Switch Branch - تبديل الفرع + تبديل الفرع - ENTER - أدخل + أدخل - - The new branch will be pulled the next time the updater runs. - سيتم سحب الفرع الجديد في المرة التالية التي يتم فيها تشغيل أداة التحديث. + سيتم سحب الفرع الجديد في المرة التالية التي يتم فيها تشغيل أداة التحديث. - Enter branch name - أدخل اسم الفرع + أدخل اسم الفرع - UNINSTALL الغاء التثبيت - Uninstall %1 الغاء التثبيت %1 - Are you sure you want to uninstall? هل أنت متأكد أنك تريد إلغاء التثبيت؟ - failed to fetch update - فشل في جلب التحديث + فشل في جلب التحديث - - CHECK تأكد الان + + Updates are only downloaded while the car is off. + + + + Current Version + + + + Download + + + + Install Update + + + + INSTALL + + + + Target Branch + + + + SELECT + + + + Select a branch + + SshControl - SSH Keys SSH Keys - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. تحذير: هذا يمنح SSH الوصول إلى جميع المفاتيح العامة في إعدادات GitHub. لا تدخل أبدًا اسم مستخدم GitHub بخلاف اسم المستخدم الخاص بك. لن يطلب منك موظف comma أبدًا إضافة اسم مستخدم GitHub الخاص به. - - ADD أضف - Enter your GitHub username أدخل اسم مستخدم GitHub الخاص بك - LOADING جار التحميل - REMOVE نزع - Username '%1' has no keys on GitHub لا يحتوي اسم المستخدم '%1' على مفاتيح على GitHub - Request timed out انتهت مهلة الطلب - Username '%1' doesn't exist on GitHub اسم المستخدم '%1' غير موجود على GitHub @@ -1142,7 +965,6 @@ location set SshToggle - Enable SSH تفعيل SSH @@ -1150,22 +972,18 @@ location set TermsPage - Terms & Conditions البنود و الظروف - Decline انحدار - Scroll to accept قم بالتمرير للقبول - Agree موافق @@ -1173,125 +991,125 @@ location set TogglesPanel - Enable openpilot تمكين openpilot - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. استخدم نظام الطيار المفتوح للتحكم التكيفي في ثبات السرعة والحفاظ على مساعدة السائق. انتباهك مطلوب في جميع الأوقات لاستخدام هذه الميزة. يسري تغيير هذا الإعداد عند إيقاف تشغيل السيارة. - 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 31 mph (50 km/h). تلقي تنبيهات للتوجه مرة أخرى إلى الحارة عندما تنجرف سيارتك فوق خط المسار المكتشف دون تنشيط إشارة الانعطاف أثناء القيادة لمسافة تزيد عن 31 ميلاً في الساعة (50 كم / ساعة). - Use Metric System استخدم النظام المتري - Display speed in km/h instead of mph. عرض السرعة بالكيلو متر في الساعة بدلاً من ميل في الساعة. - Record and Upload Driver Camera تسجيل وتحميل كاميرا السائق - Upload data from the driver facing camera and help improve the driver monitoring algorithm. قم بتحميل البيانات من الكاميرا المواجهة للسائق وساعد في تحسين خوارزمية مراقبة السائق. - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal فك الارتباط على دواسة التسريع - When enabled, pressing the accelerator pedal will disengage openpilot. عند التمكين ، سيؤدي الضغط على دواسة الوقود إلى فصل الطيار المفتوح. - Show ETA in 24h Format إظهار الوقت المقدر للوصول بتنسيق 24 ساعة - Use 24h format instead of am/pm استخدم تنسيق 24 ساعة بدلاً من صباحًا / مساءً - Show Map on Left Side of UI إظهار الخريطة على الجانب الأيسر من واجهة المستخدم - Show map on left side when in split screen view. إظهار الخريطة على الجانب الأيسر عندما تكون في طريقة عرض الشاشة المنقسمة. - openpilot Longitudinal Control - openpilot التحكم الطولي + openpilot التحكم الطولي - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - سوف يقوم برنامج openpilot بتعطيل رادار السيارة وسيتولى التحكم في الغاز والمكابح. تحذير: هذا يعطل AEB! + سوف يقوم برنامج openpilot بتعطيل رادار السيارة وسيتولى التحكم في الغاز والمكابح. تحذير: هذا يعطل AEB! + + + 🌮 End-to-end longitudinal (extremely alpha) 🌮 + + + + Experimental openpilot Longitudinal Control + + + + <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. + + + + openpilot longitudinal control is not currently available for this car. + + + + Enable experimental longitudinal control to enable this. + Updater - Update Required مطلوب التحديث - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. مطلوب تحديث نظام التشغيل. قم بتوصيل جهازك بشبكة Wi-Fi للحصول على أسرع تجربة تحديث. حجم التنزيل 1 غيغابايت تقريبًا. - Connect to Wi-Fi اتصل بشبكة Wi-Fi - Install ثبيت - Back خلف - Loading... جار التحميل... - Reboot اعادة التشغيل - Update failed فشل التحديث @@ -1299,23 +1117,18 @@ location set WifiUI - - Scanning for networks... جارٍ البحث عن شبكات ... - CONNECTING... جارٍ الاتصال ... - FORGET نزع - Forget Wi-Fi Network "%1"? نزع شبكة اWi-Fi "%1"? diff --git a/selfdrive/ui/translations/main_de.ts b/selfdrive/ui/translations/main_de.ts new file mode 100644 index 0000000000..5f3511d3cc --- /dev/null +++ b/selfdrive/ui/translations/main_de.ts @@ -0,0 +1,1106 @@ + + + + + AbstractAlert + + Close + Schließen + + + Snooze Update + Update pausieren + + + Reboot and Update + Aktualisieren und neu starten + + + + AdvancedNetworking + + Back + Zurück + + + Enable Tethering + Tethering aktivieren + + + Tethering Password + Tethering Passwort + + + EDIT + ÄNDERN + + + Enter new tethering password + Neues tethering Passwort eingeben + + + IP Address + IP Adresse + + + Enable Roaming + Roaming aktivieren + + + APN Setting + APN Einstellungen + + + Enter APN + APN eingeben + + + leave blank for automatic configuration + für automatische Konfiguration leer lassen + + + Cellular Metered + Getaktete Verbindung + + + Prevent large data uploads when on a metered connection + Hochladen großer Dateien über getaktete Verbindungen unterbinden + + + + AnnotatedCameraWidget + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + Geschwindigkeit + + + LIMIT + LIMIT + + + + ConfirmationDialog + + Ok + Ok + + + Cancel + Abbrechen + + + + DeclinePage + + You must accept the Terms and Conditions in order to use openpilot. + Du musst die Nutzungsbedingungen akzeptieren, um Openpilot zu benutzen. + + + Back + Zurück + + + Decline, uninstall %1 + Ablehnen, deinstallieren %1 + + + + DevicePanel + + Dongle ID + Dongle ID + + + N/A + Nicht verfügbar + + + Serial + Seriennummer + + + Driver Camera + Fahrerkamera + + + PREVIEW + VORSCHAU + + + Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) + Vorschau der auf den Fahrer gerichteten Kamera, um sicherzustellen, dass die Fahrerüberwachung eine gute Sicht hat. (Fahrzeug muss aus sein) + + + Reset Calibration + Neu kalibrieren + + + RESET + RESET + + + Are you sure you want to reset calibration? + Bist du sicher, dass du die Kalibrierung zurücksetzen möchtest? + + + Review Training Guide + Trainingsanleitung wiederholen + + + REVIEW + TRAINING + + + Review the rules, features, and limitations of openpilot + Wiederhole die Regeln, Fähigkeiten und Limitierungen von Openpilot + + + Are you sure you want to review the training guide? + Bist du sicher, dass du die Trainingsanleitung wiederholen möchtest? + + + Regulatory + Rechtliche Hinweise + + + VIEW + ANSEHEN + + + Change Language + Sprache ändern + + + CHANGE + ÄNDERN + + + Select a language + Sprache wählen + + + Reboot + Neustart + + + Power Off + Ausschalten + + + openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. + Damit Openpilot funktioniert, darf die Installationsposition nicht mehr als 4° nach rechts/links, 5° nach oben und 8° nach unten abweichen. Openpilot kalibriert sich durchgehend, ein Zurücksetzen ist selten notwendig. + + + Your device is pointed %1° %2 and %3° %4. + Deine Geräteausrichtung ist %1° %2 und %3° %4. + + + down + unten + + + up + oben + + + left + links + + + right + rechts + + + Are you sure you want to reboot? + Bist du sicher, dass du das Gerät neu starten möchtest? + + + Disengage to Reboot + Für Neustart deaktivieren + + + Are you sure you want to power off? + Bist du sicher, dass du das Gerät ausschalten möchtest? + + + Disengage to Power Off + Zum Ausschalten deaktivieren + + + Reset + Zurücksetzen + + + Review + Überprüfen + + + + DriveStats + + Drives + Fahrten + + + Hours + Stunden + + + ALL TIME + Gesamtzeit + + + PAST WEEK + Letzte Woche + + + KM + KM + + + Miles + Meilen + + + + DriverViewScene + + camera starting + Kamera startet + + + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + EXPERIMENTELLER MODUS AN + + + CHILL MODE ON + ENTSPANNTER MODUS AN + + + + InputDialog + + Cancel + Abbrechen + + + Need at least %n character(s)! + + Mindestens %n Buchstabe benötigt! + Mindestens %n Buchstaben benötigt! + + + + + Installer + + Installing... + Installiere... + + + + MapETA + + eta + Ankunft + + + min + min + + + hr + std + + + km + km + + + mi + mi + + + + MapInstructions + + km + km + + + m + m + + + mi + mi + + + ft + fuß + + + + MapPanel + + Current Destination + Aktuelles Ziel + + + CLEAR + LÖSCHEN + + + Recent Destinations + Letzte Ziele + + + Try the Navigation Beta + Beta Navigation ausprobieren + + + Get turn-by-turn directions displayed and more with a comma +prime subscription. Sign up now: https://connect.comma.ai + Erhalte echtzeit Wegführung und mehr mit dem comma prime +Abonnement. Melde dich jetzt an: https://connect.comma.ai + + + No home +location set + Keine Heimadresse gesetzt + + + No work +location set + Keine Arbeitsadresse gesetzt + + + no recent destinations + Keine kürzlich gewählten Ziele + + + + MapWindow + + Map Loading + Karte wird geladen + + + Waiting for GPS + Warten auf GPS + + + + MultiOptionDialog + + Select + Auswählen + + + Cancel + Abbrechen + + + + Networking + + Advanced + Erweitert + + + Enter password + Passwort eingeben + + + for "%1" + für "%1" + + + Wrong password + Falsches Passwort + + + + OffroadHome + + UPDATE + Aktualisieren + + + ALERTS + HINWEISE + + + ALERT + HINWEIS + + + + PairingPopup + + Pair your device to your comma account + Verbinde dein Gerät mit deinem comma Konto + + + Go to https://connect.comma.ai on your phone + Gehe zu https://connect.comma.ai auf deinem Handy + + + Click "add new device" and scan the QR code on the right + Klicke auf "neues Gerät hinzufügen" und scanne den QR code rechts + + + Bookmark connect.comma.ai to your home screen to use it like an app + Füge connect.comma.ai als Lesezeichen auf deinem Homescreen hinzu um es wie eine App zu verwenden + + + + ParamControl + + Cancel + Abbrechen + + + Enable + Aktivieren + + + + PrimeAdWidget + + Upgrade Now + Jetzt abonieren + + + Become a comma prime member at connect.comma.ai + Werde Comma Prime Mitglied auf connect.comma.ai + + + PRIME FEATURES: + PRIME FUNKTIONEN: + + + Remote access + Fernzugriff + + + 1 year of storage + 1 Jahr Speicherplatz + + + Developer perks + Entwickler Vorteile + + + + PrimeUserWidget + + ✓ SUBSCRIBED + ✓ ABBONIERT + + + comma prime + comma prime + + + CONNECT.COMMA.AI + CONNECT.COMMA.AI + + + COMMA POINTS + COMMA PUNKTE + + + + QObject + + Reboot + Neustart + + + Exit + Verlassen + + + dashcam + dashcam + + + openpilot + openpilot + + + %n minute(s) ago + + vor %n Minute + vor %n Minuten + + + + %n hour(s) ago + + vor %n Stunde + vor %n Stunden + + + + %n day(s) ago + + vor %n Tag + vor %n Tagen + + + + + Reset + + Reset failed. Reboot to try again. + Zurücksetzen fehlgeschlagen. Starte das Gerät neu und versuche es wieder. + + + Are you sure you want to reset your device? + Bist du sicher, dass du das Gerät auf Werkseinstellungen zurücksetzen möchtest? + + + System Reset + System auf Werkseinstellungen zurücksetzen + + + Cancel + Abbrechen + + + Reboot + Neustart + + + Confirm + Bestätigen + + + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + + + + Press confirm to erase all content and settings. Press cancel to resume boot. + + + + Resetting device... +This may take up to a minute. + + + + + SettingsWindow + + × + x + + + Device + Gerät + + + Network + Netzwerk + + + Toggles + Schalter + + + Software + Software + + + Navigation + Navigation + + + + Setup + + WARNING: Low Voltage + Warnung: Batteriespannung niedrig + + + Power your device in a car with a harness or proceed at your own risk. + Versorge dein Gerät über einen Kabelbaum im Auto mit Strom, oder fahre auf eigene Gefahr fort. + + + Power off + Ausschalten + + + Continue + Fortsetzen + + + Getting Started + Loslegen + + + Before we get on the road, let’s finish installation and cover some details. + Bevor wir uns auf die Straße begeben, lass uns die Installation fertigstellen und einige Details prüfen. + + + Connect to Wi-Fi + Mit WLAN verbinden + + + Back + Zurück + + + Continue without Wi-Fi + Ohne WLAN fortsetzen + + + Waiting for internet + Auf Internet warten + + + Enter URL + URL eingeben + + + for Custom Software + für spezifische Software + + + Downloading... + Herunterladen... + + + Download Failed + Herunterladen fehlgeschlagen + + + Ensure the entered URL is valid, and the device’s internet connection is good. + Stelle sicher, dass die eingegebene URL korrekt ist und dein Gerät eine stabile Internetverbindung hat. + + + Reboot device + Gerät neustarten + + + Start over + Von neuem beginnen + + + No custom software found at this URL. + + + + Something went wrong. Reboot the device. + + + + + SetupWidget + + Finish Setup + Einrichtung beenden + + + Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. + Koppele dein Gerät mit Comma Connect (connect.comma.ai) und sichere dir dein Comma Prime Angebot. + + + Pair device + Gerät koppeln + + + + Sidebar + + CONNECT + This is a brand/service name for comma connect, don't translate + CONNECT + + + OFFLINE + OFFLINE + + + ONLINE + ONLINE + + + ERROR + FEHLER + + + TEMP + TEMP + + + HIGH + HOCH + + + GOOD + GUT + + + OK + OK + + + VEHICLE + FAHRZEUG + + + NO + KEIN + + + PANDA + PANDA + + + GPS + GPS + + + SEARCH + SUCHEN + + + -- + -- + + + Wi-Fi + WLAN + + + ETH + LAN + + + 2G + 2G + + + 3G + 3G + + + LTE + LTE + + + 5G + 5G + + + + SoftwarePanel + + UNINSTALL + Too long for UI + DEINSTALL + + + Uninstall %1 + Deinstalliere %1 + + + Are you sure you want to uninstall? + Bist du sicher, dass du Openpilot entfernen möchtest? + + + CHECK + ÜBERPRÜFEN + + + Updates are only downloaded while the car is off. + Updates werden nur heruntergeladen, wenn das Auto aus ist. + + + Current Version + Aktuelle Version + + + Download + Download + + + Install Update + Update installieren + + + INSTALL + INSTALLIEREN + + + Target Branch + Ziel Branch + + + SELECT + AUSWÄHLEN + + + Select a branch + Wähle einen Branch + + + Uninstall + Deinstallieren + + + + SshControl + + SSH Keys + SSH Schlüssel + + + Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. + Warnung: Dies ermöglicht SSH zugriff für alle öffentlichen Schlüssel in deinen Github Einstellungen. Gib niemals einen anderen Benutzernamen, als deinen Eigenen an. Comma Angestellte fragen dich niemals danach ihren Github Benutzernamen hinzuzufügen. + + + ADD + HINZUFÜGEN + + + Enter your GitHub username + Gib deinen GitHub Benutzernamen ein + + + LOADING + LADEN + + + REMOVE + LÖSCHEN + + + Username '%1' has no keys on GitHub + Benutzername '%1' hat keine Schlüssel auf GitHub + + + Request timed out + Zeitüberschreitung der Anforderung + + + Username '%1' doesn't exist on GitHub + Benutzername '%1' existiert nicht auf GitHub + + + + SshToggle + + Enable SSH + SSH aktivieren + + + + TermsPage + + Terms & Conditions + Benutzungsbedingungen + + + Decline + Ablehnen + + + Scroll to accept + Scrolle herunter um zu akzeptieren + + + Agree + Zustimmen + + + + TogglesPanel + + Enable openpilot + Openpilot aktivieren + + + Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. + Benutze das Openpilot System als adaptiven Tempomaten und Spurhalteassistenten. Deine Aufmerksamkeit ist jederzeit erforderlich, um diese Funktion zu nutzen. Diese Einstellung wird übernommen, wenn das Auto aus ist. + + + Enable Lane Departure Warnings + Spurverlassenswarnungen aktivieren + + + 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). + Erhalte Warnungen, zurück in die Spur zu lenken, wenn dein Auto über eine erkannte Fahrstreifenmarkierung ohne aktivierten Blinker mit mehr als 50 km/h fährt. + + + Use Metric System + Benutze das metrische System + + + Display speed in km/h instead of mph. + Zeige die Geschwindigkeit in km/h anstatt von mph. + + + Record and Upload Driver Camera + Fahrerkamera aufnehmen und hochladen + + + Upload data from the driver facing camera and help improve the driver monitoring algorithm. + Lade Daten der Fahreraufmerksamkeitsüberwachungskamera hoch, um die Fahreraufmerksamkeitsüberwachungsalgorithmen zu verbessern. + + + When enabled, pressing the accelerator pedal will disengage openpilot. + Wenn aktiviert, deaktiviert sich Openpilot sobald das Gaspedal betätigt wird. + + + Use 24h format instead of am/pm + Benutze das 24Stunden Format anstatt am/pm + + + Show Map on Left Side of UI + Too long for UI + Zeige die Karte auf der linken Seite + + + Show map on left side when in split screen view. + Zeige die Karte auf der linken Seite der Benutzeroberfläche bei geteilten Bildschirm. + + + Show ETA in 24h Format + Too long for UI + Zeige die Ankunftszeit im 24 Stunden Format + + + Experimental Mode + Experimenteller Modus + + + Experimental openpilot Longitudinal Control + Experimenteller Openpilot Tempomat + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + WARNUNG: Der Openpilot Tempomat ist für dieses Auto experimentell und deaktiviert den Notbremsassistenten. + + + Disengage on Accelerator Pedal + Bei Gasbetätigung ausschalten + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + Bei diesem auto wird standardmäßig der im Auto eingebaute adaptive Tempomat anstelle des Openpilot Tempomats benutzt. Aktiviere diesen Schalter, um zum Openpilot Tempomaten zu wechseln. Es ist empfohlen den Experimentellen Modus bei Nutzung des Openpilot Tempomats zu aktivieren. + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + Openpilot fährt standardmäßig im <b>entspannten Modus</b>. Der Experimentelle Modus aktiviert<b>Alpha-level Funktionen</b>, die noch nicht für den entspannten Modus bereit sind. Die experimentellen Funktionen sind die Folgenden: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 Ende-zu-Ende Tempomat 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Lass das Fahrmodell Gas und Bremse kontrollieren. Openpilot wird so fahren, wie es dies von einem Menschen erwarten würde; inklusive des Anhaltens für Ampeln und Stoppschildern. Da das Fahrmodell entscheidet wie schnell es fährt stellt die gesetzte Geschwindigkeit lediglich das obere Limit dar. Dies ist ein Alpha-level Funktion. Fehler sind zu erwarten. + + + New Driving Visualization + Neue Fahrvisualisierung + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + Die Fahrvisualisierung wechselt bei niedrigen Geschwindigkeiten zur Straßengewandten Weitwinkelkamera, um manche Kurven besser zu zeigen. Außerdem wird das Experimenteller Modus logo oben rechts angezeigt. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + Der experimentelle Modus ist momentan für dieses Auto nicht verfügbar da es den eingebauten adaptiven Tempomaten des Autos benutzt. + + + openpilot longitudinal control may come in a future update. + + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable experimental longitudinal control to allow Experimental mode. + Aktiviere den experimentellen Openpilot Tempomaten für experimentelle Funktionen. + + + + Updater + + Update Required + Aktualisierung notwendig + + + An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. + Eine Aktualisierung des Betriebssystems ist notwendig. Verbinde dein Gerät mit WLAN für ein schnelleres Update. Die Download Größe ist ungefähr 1GB. + + + Connect to Wi-Fi + Mit WLAN verbinden + + + Install + Installieren + + + Back + Zurück + + + Loading... + Laden... + + + Reboot + Neustart + + + Update failed + Aktualisierung fehlgeschlagen + + + + WifiUI + + Scanning for networks... + Suche nach Netzwerken... + + + CONNECTING... + VERBINDEN... + + + FORGET + VERGESSEN + + + Forget Wi-Fi Network "%1"? + WLAN Netzwerk "%1" vergessen? + + + Forget + Vergessen + + + diff --git a/selfdrive/ui/translations/main_ja.ts b/selfdrive/ui/translations/main_ja.ts index 314a6b8338..1e0223606b 100644 --- a/selfdrive/ui/translations/main_ja.ts +++ b/selfdrive/ui/translations/main_ja.ts @@ -56,15 +56,15 @@ leave blank for automatic configuration - 空白のままにして、自動設定にします + 自動で設定するには、空白のままにしてください。 Cellular Metered - + 従量制通信設定 Prevent large data uploads when on a metered connection - + 大量のデータのアップロードを防止します。 @@ -136,7 +136,7 @@ PREVIEW - 見る + プレビュー Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) @@ -156,7 +156,7 @@ Review Training Guide - 入門書を見る + 使い方の確認 REVIEW @@ -168,7 +168,7 @@ Are you sure you want to review the training guide? - 入門書を見てもよろしいですか? + 使い方の確認をしますか? Regulatory @@ -200,11 +200,11 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot は、左または右の4°以内、上の5°または下の8°以内にデバイスを取付ける必要があります。キャリブレーションを引き続きます、リセットはほとんど必要ありません。 + openpilotの本体は、左右4°以内、上5°、下8°以内の角度で取付ける必要があります。継続してキャリブレーションを続けているので、手動でリセットを行う必要はほぼありません。 Your device is pointed %1° %2 and %3° %4. - このデバイスは%2の%1°、%4の%3°に向けます。 + このデバイスは%2 %1°、%4 %3°の向きに設置されています。 down @@ -238,6 +238,14 @@ Disengage to Power Off openpilot をキャンセルしてシャットダウンができます + + Reset + リセット + + + Review + 確認 + DriveStats @@ -273,6 +281,17 @@ カメラを起動しています + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + 実験モード + + + CHILL MODE ON + チルモード + + InputDialog @@ -292,24 +311,12 @@ Installing... インストールしています... - - Receiving objects: - オブジェクトをダウンロードしています: - - - Resolving deltas: - デルタを解決しています: - - - Updating files: - ファイルを更新しています: - MapETA eta - 予定到着時間 + 到着予定時間 min @@ -452,15 +459,26 @@ location set Go to https://connect.comma.ai on your phone - モバイルデバイスで「connect.comma.ai」にアクセスして + スマートフォンで「https://connect.comma.ai」にアクセスしてください。 Click "add new device" and scan the QR code on the right - 「新しいデバイスを追加」を押すと、右側のQRコードをスキャンしてください + 「新しいデバイスを追加」を押し、右側のQRコードをスキャンしてください。 Bookmark connect.comma.ai to your home screen to use it like an app - 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます + 「connect.comma.ai」をホーム画面に追加して、アプリのように使うことができます。 + + + + ParamControl + + Cancel + キャンセル + + + Enable + を有効化 @@ -556,18 +574,10 @@ location set Are you sure you want to reset your device? 初期化してもよろしいですか? - - Resetting device... - デバイスが初期化されます... - System Reset システムを初期化 - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - システムの初期化をリクエストしました。「確認」ボタンを押すとデバイスが初期化されます。「キャンセル」ボタンを押すと起動を続行します。 - Cancel キャンセル @@ -581,15 +591,17 @@ location set 確認 - Unable to mount data partition. Press confirm to reset your device. - 「data」パーティションをマウントできません。「確認」ボタンを押すとデバイスが初期化されます。 + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + - - - RichTextDialog - Ok - OK + Press confirm to erase all content and settings. Press cancel to resume boot. + + + + Resetting device... +This may take up to a minute. + @@ -608,7 +620,7 @@ location set Toggles - 切り替え + 機能設定 Software @@ -627,7 +639,7 @@ location set Power your device in a car with a harness or proceed at your own risk. - 自己責任でハーネスから電源を供給してください。 + 自己責任で実行を継続するか、ハーネスから電源を供給してください。 Power off @@ -643,7 +655,7 @@ location set Before we get on the road, let’s finish installation and cover some details. - その前に、インストールを完了し、いくつかの詳細を説明します。 + 道路に向かう前に、インストールを完了して使い方を確認しましょう。 Connect to Wi-Fi @@ -655,24 +667,12 @@ location set Continue without Wi-Fi - Wi-Fi に未接続で続行 + Wi-Fi に接続せずに続行 Waiting for internet インターネット接続を待機中 - - Choose Software to Install - インストールするソフトウェアを選びます - - - Dashcam - ドライブレコーダー - - - Custom Software - カスタムソフトウェア - Enter URL URL を入力 @@ -701,6 +701,14 @@ location set Start over 最初からやり直す + + No custom software found at this URL. + + + + Something went wrong. Reboot the device. + + SetupWidget @@ -710,7 +718,7 @@ location set Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. - デバイスを comma connect (connect.comma.ai)でペアリングし comma prime 特典を申請してください。 + デバイスを comma connect (connect.comma.ai)でペアリングし、comma primeの特典を申請してください。 Pair device @@ -836,7 +844,7 @@ location set UNINSTALL - アンインストール + 実行 Uninstall %1 @@ -850,6 +858,10 @@ location set CHECK 確認 + + Uninstall + アンインストール + SshControl @@ -859,7 +871,7 @@ location set Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. - 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。コンマのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。 + 警告: これは、GitHub の設定にあるすべての公開鍵への SSH アクセスを許可するものです。自分以外の GitHub のユーザー名を入力しないでください。commaのスタッフが GitHub のユーザー名を追加するようお願いすることはありません。 ADD @@ -871,7 +883,7 @@ location set LOADING - ローディング + 読み込み中 REMOVE @@ -924,7 +936,7 @@ location set Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. - アダプティブクルーズコントロールとレーンキーピングドライバーアシスト(openpilotシステム)。この機能を使用するには、常に注意が必要です。この設定を変更すると、車の電源が切れたときに有効になります。 + openpilotによるアダプティブクルーズコントロールとレーンキーピングドライバーアシストを利用します。この機能を利用する際は、常に前方への注意が必要です。この設定を変更すると、車の電源が切れた時に反映されます。 Enable Lane Departure Warnings @@ -932,11 +944,11 @@ location set 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). - 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、車線に戻るアラートを受信します。 + 時速31マイル(50km)を超えるスピードで走行中、ウインカーを作動させずに検出された車線ライン上に車両が触れた場合、手動で車線内に戻るように警告を行います。 Use Metric System - メートル法を有効化 + メートル法を使用 Display speed in km/h instead of mph. @@ -951,36 +963,16 @@ location set 車内カメラの映像をアップロードし、ドライバー監視システムのアルゴリズムの向上に役立てます。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 エンドツーエンドのアクセル制御 (超アルファ版) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control 実験段階のopenpilotによるアクセル制御 - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>警告: openpilotによるアクセル制御は実験段階であり、AEBを無効化します。</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - アクセルとブレーキの制御をopenpilotに任せます。openpilotが人間と同じように運転します。最初期の実験段階です。 - - - openpilot longitudinal control is not currently available for this car. - openpilotによるアクセル制御は、この車では現在利用できません。 - - - Enable experimental longitudinal control to enable this. - ここ機能を使う為には、「実験段階のopenpilotによるアクセル制御」を先に有効化してください。 - - - Disengage On Accelerator Pedal - アクセル踏むと openpilot をキャンセル + Disengage on Accelerator Pedal + アクセルを踏むと openpilot を中断 When enabled, pressing the accelerator pedal will disengage openpilot. - 有効な場合は、アクセルを踏むと openpilot をキャンセルします。 + この機能を有効化すると、openpilotを利用中にアクセルを踏むとopenpilotによる運転サポートを中断します。 Show ETA in 24h Format @@ -998,16 +990,64 @@ location set Show map on left side when in split screen view. 分割画面表示の場合、ディスプレイの左側にマップを表示します。 + + Experimental Mode + 実験モード + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: この車種でのopenpilotによるアクセル制御は実験段階であり、衝突被害軽減ブレーキ(AEB)を無効化します。 + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + openpilotはこの車の場合、車に内蔵されているACCを標準で利用します。この機能を有効にすることで実験段階のopenpilotによるアクセル制御を利用できます。実験モードと合わせて利用することをお勧めします。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilotは標準ではゆっくりとくつろげる運転を提供します。この実験モードを有効にすると、以下のくつろげる段階ではない開発中の機能を利用する事ができます。 + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 エンドツーエンドアクセル制御 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + openpilotにアクセルとブレーキを任せます。openpilotは赤信号や一時停止サインでの停止を含み、人間と同じように考えて運転を行います。openpilotが運転速度を決定するため、あなたが設定する速度は上限速度になります。この機能は実験段階のため、openpilotの運転ミスに常に備えて注意してください。 + + + New Driving Visualization + 新しい運転画面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 新しい運転画面では、低速時に広角カメラの映像を表示することで、曲がる際の道路の視覚を向上します。実験段階を表すマークが右上に表示されます。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + この車のACCがアクセル制御を行うため実験モードを利用することができません。 + + + openpilot longitudinal control may come in a future update. + + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable experimental longitudinal control to allow Experimental mode. + 実験段階のopenpilotによるアクセル制御を有効にしてください。 + Updater Update Required - 更新が必要です + アップデートが必要です An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. - オペレーティングシステムのアップデートが必要です。Wi-Fi に接続することで、最速のアップデートを体験できます。ダウンロードサイズは約 1GB です。 + オペレーティングシステムのアップデートが必要です。Wi-Fi に接続してアップデートする事をお勧めします。ダウンロードサイズは約 1GB です。 Connect to Wi-Fi @@ -1052,5 +1092,9 @@ location set Forget Wi-Fi Network "%1"? Wi-Fiネットワーク%1を削除してもよろしいですか? + + Forget + 削除 + diff --git a/selfdrive/ui/translations/main_ko.ts b/selfdrive/ui/translations/main_ko.ts index 021243595e..be11f2efde 100644 --- a/selfdrive/ui/translations/main_ko.ts +++ b/selfdrive/ui/translations/main_ko.ts @@ -238,6 +238,14 @@ Disengage to Power Off 전원을 종료하려면 해제하세요 + + Reset + 리셋 + + + Review + 다시보기 + DriveStats @@ -273,6 +281,17 @@ 카메라 시작중 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + 실험적 모드 사용 + + + CHILL MODE ON + 안정적 모드 사용 + + InputDialog @@ -292,18 +311,6 @@ Installing... 설치중... - - Receiving objects: - 수신중: - - - Resolving deltas: - 델타병합: - - - Updating files: - 파일갱신: - MapETA @@ -396,7 +403,7 @@ location set Waiting for GPS - GPS를 기다리는 중 + GPS 수신중 입니다 @@ -463,6 +470,17 @@ location set connect.comma.ai을 앱처럼 사용하려면 홈 화면에 바로가기를 만드십시오 + + ParamControl + + Cancel + 취소 + + + Enable + 사용 + + PrimeAdWidget @@ -471,7 +489,7 @@ location set Become a comma prime member at connect.comma.ai - connect.comma.ai에서 comma prime에 가입합니다 + connect.comma.ai 접속 comma prime 가입 PRIME FEATURES: @@ -556,18 +574,10 @@ location set Are you sure you want to reset your device? 장치를 초기화 하시겠습니까? - - Resetting device... - 장치 초기화중... - System Reset 장치 초기화 - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - 장치를 초기화 합니다. 확인버튼을 누르면 모든 내용과 설정이 초기화됩니다. 부팅을 재개하려면 취소를 누르세요. - Cancel 취소 @@ -581,15 +591,18 @@ location set 확인 - Unable to mount data partition. Press confirm to reset your device. - 데이터 파티션을 마운트할 수 없습니다. 확인 버튼을 눌러 장치를 리셋합니다. + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + 데이터 파티션을 마운트할 수 없습니다. 파티션이 손상되었을 수 있습니다. 장치를 초기화하려면 확인을 누르세요. - - - RichTextDialog - Ok - 확인 + Press confirm to erase all content and settings. Press cancel to resume boot. + 모든 콘텐츠와 설정을 지우려면 확인을 누르세요. 부팅을 재개하려면 취소를 누르세요. + + + Resetting device... +This may take up to a minute. + 장치 초기화 중... +최대 1분이 소요될 수 있습니다. @@ -661,18 +674,6 @@ location set Waiting for internet 네트워크 접속을 기다립니다 - - Choose Software to Install - 설치할 소프트웨어를 선택하세요 - - - Dashcam - Dashcam - - - Custom Software - Custom Software - Enter URL URL 입력 @@ -701,6 +702,14 @@ location set Start over 다시 시작 + + Something went wrong. Reboot the device. + 문제가 발생했습니다. 장치를 재부팅하세요. + + + No custom software found at this URL. + 이 URL에서 커스텀 소프트웨어를 찾을 수 없습니다. + SetupWidget @@ -850,6 +859,10 @@ location set CHECK 확인 + + Uninstall + 제거 + SshControl @@ -951,31 +964,11 @@ location set 운전자 카메라에서 데이터를 업로드하고 운전자 모니터링 알고리즘을 개선합니다. - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 e2e 롱컨트롤 사용 (매우 실험적) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control openpilot 롱컨트롤 (실험적) - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 AEB를 비활성화합니다.</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 주행모델이 가속과 감속을 제어하도록 하면 openpilot은 운전자가 생각하는것처럼 운전합니다. (매우 실험적) - - - openpilot longitudinal control is not currently available for this car. - 현재 이 차량에는 openpilot 롱컨트롤을 사용할 수 없습니다. - - - Enable experimental longitudinal control to enable this. - openpilot 롱컨트롤을 활성화합니다. (실험적) - - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 가속페달 조작시 해제 @@ -998,6 +991,54 @@ location set Show map on left side when in split screen view. 분할 화면 보기에서 지도를 왼쪽에 표시합니다. + + Experimental Mode + 실험적 모드 + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 경고: openpilot 롱컨트롤은 실험적인 기능으로 차량의 자동긴급제동(AEB)를 비활성화합니다. + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + 이 차량은 openpilot 롱컨트롤 대신 차량의 내장 ACC로 기본 설정됩니다. openpilot 롱컨트롤을 사용하려면 이 옵션을 활성화하세요. 실험적 openpilot 롱컨트롤을 사용하는 경우 실험적 모드를 활성화 하세요. + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot은 기본적으로 <b>안정적 모드</b>로 주행합니다. 실험적 모드는 안정적 모드에 준비되지 않은 <b>알파 수준 기능</b>을 활성화 합니다. 실험적 모드의 특징은 아래에 나열되어 있습니다 + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 E2E 롱컨트롤 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 주행모델이 가속과 감속을 제어하도록 합니다. openpilot은 신호등과 정지표지판을 보고 멈추는 것을 포함하여 운전자가 생각하는것처럼 주행합니다. 주행 모델이 주행할 속도를 결정하므로 설정된 속도는 상한선으로만 작용합니다. 이것은 알파 기능이므로 사용에 주의해야 합니다. + + + New Driving Visualization + 새로운 주행 시각화 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 주행 시각화는 저속에서 도로를 향하는 광각 카메라로 전환되어 일부 회전을 더 잘 보여줍니다. 실험적 모드의 로고도 우측상단에 표시됩니다. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 차량에 장착된 ACC가 롱컨트롤에 사용되기 때문에 현재 이 차량은 실험적 모드를 사용할 수 없습니다. + + + openpilot longitudinal control may come in a future update. + 오픈파일럿 롱컨트롤은 향후 업데이트에서 제공될 수 있습니다. + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 오픈파일럿 롱컨트롤의 실험 버전은 실험적 모드와 함께 릴리즈 되지 않은 브랜치에서 테스트할 수 있습니다. + + + Enable experimental longitudinal control to allow Experimental mode. + 실험적 롱컨트롤을 사용하려면 실험적 모드를 활성화 하세요. + Updater @@ -1052,5 +1093,9 @@ location set Forget Wi-Fi Network "%1"? wifi 네트워크 저장안함 "%1"? + + Forget + 저장안함 + diff --git a/selfdrive/ui/translations/main_nl.ts b/selfdrive/ui/translations/main_nl.ts index 21bb66d356..10651a4160 100644 --- a/selfdrive/ui/translations/main_nl.ts +++ b/selfdrive/ui/translations/main_nl.ts @@ -4,17 +4,14 @@ AbstractAlert - Close Sluit - Snooze Update Update uitstellen - Reboot and Update Opnieuw Opstarten en Updaten @@ -22,67 +19,84 @@ AdvancedNetworking - Back Terug - Enable Tethering Tethering Inschakelen - Tethering Password Tethering Wachtwoord - - EDIT AANPASSEN - Enter new tethering password Voer nieuw tethering wachtwoord in - IP Address IP Adres - Enable Roaming Roaming Inschakelen - APN Setting APN Instelling - Enter APN Voer APN in - leave blank for automatic configuration laat leeg voor automatische configuratie + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + + + + AnnotatedCameraWidget + + km/h + km/u + + + mph + mph + + + MAX + MAX + + + SPEED + SPEED + + + LIMIT + LIMIT + ConfirmationDialog - - Ok Ok - Cancel Annuleren @@ -90,17 +104,14 @@ DeclinePage - You must accept the Terms and Conditions in order to use openpilot. U moet de Algemene Voorwaarden accepteren om openpilot te gebruiken. - Back Terug - Decline, uninstall %1 Afwijzen, verwijder %1 @@ -108,152 +119,122 @@ DevicePanel - Dongle ID Dongle ID - N/A Nvt - Serial Serienummer - Driver Camera Bestuurders Camera - PREVIEW BEKIJKEN - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) Bekijk de naar de bestuurder gerichte camera om ervoor te zorgen dat het monitoren van de bestuurder goed zicht heeft. (Voertuig moet uitgschakeld zijn) - Reset Calibration Kalibratie Resetten - RESET RESET - Are you sure you want to reset calibration? Weet u zeker dat u de kalibratie wilt resetten? - Review Training Guide Doorloop de Training Opnieuw - REVIEW BEKIJKEN - Review the rules, features, and limitations of openpilot Bekijk de regels, functies en beperkingen van openpilot - Are you sure you want to review the training guide? Weet u zeker dat u de training opnieuw wilt doorlopen? - Regulatory Regelgeving - VIEW BEKIJKEN - Change Language Taal Wijzigen - CHANGE WIJZIGEN - Select a language Selecteer een taal - Reboot Opnieuw Opstarten - Power Off Uitschakelen - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. openpilot vereist dat het apparaat binnen 4° links of rechts en binnen 5° omhoog of 8° omlaag wordt gemonteerd. openpilot kalibreert continu, resetten is zelden nodig. - Your device is pointed %1° %2 and %3° %4. Uw apparaat is gericht op %1° %2 en %3° %4. - down omlaag - up omhoog - left links - right rechts - Are you sure you want to reboot? Weet u zeker dat u opnieuw wilt opstarten? - Disengage to Reboot Deactiveer openpilot om opnieuw op te starten - Are you sure you want to power off? Weet u zeker dat u wilt uitschakelen? - Disengage to Power Off Deactiveer openpilot om uit te schakelen @@ -261,32 +242,26 @@ DriveStats - Drives Ritten - Hours Uren - ALL TIME TOTAAL - PAST WEEK AFGELOPEN WEEK - KM Km - Miles Mijl @@ -294,7 +269,6 @@ DriverViewScene - camera starting Camera wordt gestart @@ -302,12 +276,10 @@ InputDialog - Cancel Annuleren - Need at least %n character(s)! Heeft minstens %n karakter nodig! @@ -318,22 +290,18 @@ Installer - Installing... Installeren... - Receiving objects: Objecten ontvangen: - Resolving deltas: Deltas verwerken: - Updating files: Bestanden bijwerken: @@ -341,27 +309,22 @@ MapETA - eta eta - min min - hr uur - km km - mi mi @@ -369,22 +332,18 @@ MapInstructions - km km - m m - mi mi - ft ft @@ -392,48 +351,40 @@ MapPanel - Current Destination Huidige Bestemming - CLEAR LEEGMAKEN - Recent Destinations Recente Bestemmingen - Try the Navigation Beta Probeer de Navigatie Bèta - Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai Krijg stapsgewijze routebeschrijving en meer met een comma prime abonnement. Meld u nu aan: https://connect.comma.ai - No home location set Geen thuislocatie ingesteld - No work location set Geen werklocatie ingesteld - no recent destinations geen recente bestemmingen @@ -441,12 +392,10 @@ ingesteld MapWindow - Map Loading Kaart wordt geladen - Waiting for GPS Wachten op GPS @@ -454,12 +403,10 @@ ingesteld MultiOptionDialog - Select Selecteer - Cancel Annuleren @@ -467,72 +414,33 @@ ingesteld Networking - Advanced Geavanceerd - Enter password Voer wachtwoord in - - for "%1" voor "%1" - Wrong password Verkeerd wachtwoord - - AnnotatedCameraWidget - - - km/h - km/u - - - - mph - mph - - - - - MAX - MAX - - - - - SPEED - SPEED - - - - - LIMIT - LIMIT - - OffroadHome - UPDATE UPDATE - ALERTS WAARSCHUWINGEN - ALERT WAARSCHUWING @@ -540,22 +448,18 @@ ingesteld PairingPopup - Pair your device to your comma account Koppel uw apparaat aan uw comma-account - Go to https://connect.comma.ai on your phone Ga naar https://connect.comma.ai op uw telefoon - Click "add new device" and scan the QR code on the right Klik op "add new device" en scan de QR-code aan de rechterkant - Bookmark connect.comma.ai to your home screen to use it like an app Voeg connect.comma.ai toe op uw startscherm om het als een app te gebruiken @@ -563,32 +467,26 @@ ingesteld PrimeAdWidget - Upgrade Now Upgrade nu - Become a comma prime member at connect.comma.ai Word een comma prime lid op connect.comma.ai - PRIME FEATURES: PRIME BEVAT: - Remote access Toegang op afstand - 1 year of storage 1 jaar lang opslag - Developer perks Voordelen voor ontwikkelaars @@ -596,22 +494,18 @@ ingesteld PrimeUserWidget - ✓ SUBSCRIBED ✓ GEABONNEERD - comma prime comma prime - CONNECT.COMMA.AI CONNECT.COMMA.AI - COMMA POINTS COMMA PUNTEN @@ -619,27 +513,22 @@ ingesteld QObject - Reboot Opnieuw Opstarten - Exit Afsluiten - dashcam dashcam - openpilot openpilot - %n minute(s) ago %n minuut geleden @@ -647,7 +536,6 @@ ingesteld - %n hour(s) ago %n uur geleden @@ -655,7 +543,6 @@ ingesteld - %n day(s) ago %n dag geleden @@ -666,47 +553,38 @@ ingesteld Reset - Reset failed. Reboot to try again. Opnieuw instellen mislukt. Start opnieuw op om opnieuw te proberen. - Are you sure you want to reset your device? Weet u zeker dat u uw apparaat opnieuw wilt instellen? - Resetting device... Apparaat opnieuw instellen... - System Reset Systeemreset - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. Systeemreset geactiveerd. Druk op bevestigen om alle inhoud en instellingen te wissen. Druk op Annuleren om het opstarten te hervatten. - Cancel Annuleren - Reboot Opnieuw Opstarten - Confirm Bevestigen - Unable to mount data partition. Press confirm to reset your device. Kan gegevenspartitie niet koppelen. Druk op bevestigen om uw apparaat te resetten. @@ -714,7 +592,6 @@ ingesteld RichTextDialog - Ok Ok @@ -722,33 +599,26 @@ ingesteld SettingsWindow - × × - Device Apparaat - - Network Netwerk - Toggles Opties - Software Software - Navigation Navigatie @@ -756,105 +626,82 @@ ingesteld Setup - WARNING: Low Voltage WAARCHUWING: Lage Spanning - Power your device in a car with a harness or proceed at your own risk. Voorzie uw apparaat van stroom in een auto met een harnas (car harness) of ga op eigen risico verder. - Power off Uitschakelen - - - Continue Doorgaan - Getting Started Aan de slag - Before we get on the road, let’s finish installation and cover some details. Laten we, voordat we op pad gaan, de installatie afronden en enkele details bespreken. - Connect to Wi-Fi Maak verbinding met Wi-Fi - - Back Terug - Continue without Wi-Fi Doorgaan zonder Wi-Fi - Waiting for internet Wachten op internet - Choose Software to Install Kies Software om te Installeren - Dashcam Dashcam - Custom Software Andere Software - Enter URL Voer URL in - for Custom Software voor Andere Software - Downloading... Downloaden... - Download Failed Downloaden Mislukt - Ensure the entered URL is valid, and the device’s internet connection is good. Zorg ervoor dat de ingevoerde URL geldig is en dat de internetverbinding van het apparaat goed is. - Reboot device Apparaat opnieuw opstarten - Start over Begin opnieuw @@ -862,17 +709,14 @@ ingesteld SetupWidget - Finish Setup Installatie voltooien - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. Koppel uw apparaat met comma connect (connect.comma.ai) en claim uw comma prime-aanbieding. - Pair device Apparaat koppelen @@ -880,106 +724,82 @@ ingesteld Sidebar - - CONNECT VERBINDING - OFFLINE OFFLINE - - ONLINE ONLINE - ERROR FOUT - - - TEMP TEMP - HIGH HOOG - GOOD GOED - OK OK - VEHICLE VOERTUIG - NO GEEN - PANDA PANDA - GPS GPS - SEARCH ZOEKEN - -- -- - Wi-Fi Wi-Fi - ETH ETH - 2G 2G - 3G 3G - LTE 4G - 5G 5G @@ -987,138 +807,141 @@ ingesteld SoftwarePanel - Git Branch - Git Branch + Git Branch - Git Commit - Git Commit + Git Commit - OS Version - OS Versie + OS Versie - Version - Versie + Versie - Last Update Check - Laatste Updatecontrole + Laatste Updatecontrole - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - De laatste keer dat openpilot met succes heeft gecontroleerd op een update. De updater werkt alleen als de auto is uitgeschakeld. + De laatste keer dat openpilot met succes heeft gecontroleerd op een update. De updater werkt alleen als de auto is uitgeschakeld. - Check for Update - Controleer op Updates + Controleer op Updates - CHECKING - CONTROLEER + CONTROLEER - Switch Branch - Branch Verwisselen + Branch Verwisselen - ENTER - INVOEREN + INVOEREN - - The new branch will be pulled the next time the updater runs. - Tijdens de volgende update wordt de nieuwe branch opgehaald. + Tijdens de volgende update wordt de nieuwe branch opgehaald. - Enter branch name - Voer branch naam in + Voer branch naam in - Uninstall %1 Verwijder %1 - UNINSTALL VERWIJDER - Are you sure you want to uninstall? Weet u zeker dat u de installatie ongedaan wilt maken? - failed to fetch update - ophalen van update mislukt + ophalen van update mislukt - - CHECK CONTROLEER + + Updates are only downloaded while the car is off. + + + + Current Version + + + + Download + + + + Install Update + + + + INSTALL + + + + Target Branch + + + + SELECT + + + + Select a branch + + SshControl - SSH Keys SSH Sleutels - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. Waarschuwing: dit geeft SSH toegang tot alle openbare sleutels in uw GitHub-instellingen. Voer nooit een andere GitHub-gebruikersnaam in dan die van uzelf. Een medewerker van comma zal u NOOIT vragen om zijn GitHub-gebruikersnaam toe te voegen. - - ADD TOEVOEGEN - Enter your GitHub username Voer uw GitHub gebruikersnaam in - LOADING LADEN - REMOVE VERWIJDEREN - Username '%1' has no keys on GitHub Gebruikersnaam '%1' heeft geen SSH sleutels op GitHub - Request timed out Time-out van aanvraag - Username '%1' doesn't exist on GitHub Gebruikersnaam '%1' bestaat niet op GitHub @@ -1126,7 +949,6 @@ ingesteld SshToggle - Enable SSH SSH Inschakelen @@ -1134,22 +956,18 @@ ingesteld TermsPage - Terms & Conditions Algemene Voorwaarden - Decline Afwijzen - Scroll to accept Scroll om te accepteren - Agree Akkoord @@ -1157,125 +975,125 @@ ingesteld TogglesPanel - Enable openpilot openpilot Inschakelen - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. Gebruik het openpilot-systeem voor adaptieve cruisecontrol en rijstrookassistentie. Uw aandacht is te allen tijde vereist om deze functie te gebruiken. Het wijzigen van deze instelling wordt van kracht wanneer de auto wordt uitgeschakeld. - Enable Lane Departure Warnings Waarschuwingen bij Verlaten Rijstrook Inschakelen - 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). Ontvang waarschuwingen om terug naar de rijstrook te sturen wanneer uw voertuig over een gedetecteerde rijstrookstreep drijft zonder dat de richtingaanwijzer wordt geactiveerd terwijl u harder rijdt dan 50 km/u (31 mph). - Use Metric System Gebruik Metrisch Systeem - Display speed in km/h instead of mph. Geef snelheid weer in km/u in plaats van mph. - Record and Upload Driver Camera Opnemen en Uploaden van de Bestuurders Camera - Upload data from the driver facing camera and help improve the driver monitoring algorithm. Upload gegevens van de bestuurders camera en help het algoritme voor het monitoren van de bestuurder te verbeteren. - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal Deactiveren Met Gaspedaal - When enabled, pressing the accelerator pedal will disengage openpilot. Indien ingeschakeld, zal het indrukken van het gaspedaal openpilot deactiveren. - Show ETA in 24h Format Toon verwachte aankomsttijd in 24-uurs formaat - Use 24h format instead of am/pm Gebruik 24-uurs formaat in plaats van AM en PM - Show Map on Left Side of UI Toon kaart aan linkerkant van het scherm - Show map on left side when in split screen view. Toon kaart links in gesplitste schermweergave. - openpilot Longitudinal Control - openpilot Longitudinale Controle + openpilot Longitudinale Controle - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - openpilot zal de radar van de auto uitschakelen en de controle over gas en remmen overnemen. Waarschuwing: hierdoor wordt AEB (automatische noodrem) uitgeschakeld! + openpilot zal de radar van de auto uitschakelen en de controle over gas en remmen overnemen. Waarschuwing: hierdoor wordt AEB (automatische noodrem) uitgeschakeld! + + + 🌮 End-to-end longitudinal (extremely alpha) 🌮 + + + + Experimental openpilot Longitudinal Control + + + + <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. + + + + openpilot longitudinal control is not currently available for this car. + + + + Enable experimental longitudinal control to enable this. + Updater - Update Required Update Vereist - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. Een update van het besturingssysteem is vereist. Verbind je apparaat met Wi-Fi voor de snelste update-ervaring. De downloadgrootte is ongeveer 1 GB. - Connect to Wi-Fi Maak verbinding met Wi-Fi - Install Installeer - Back Terug - Loading... Aan het laden... - Reboot Opnieuw Opstarten - Update failed Update mislukt @@ -1283,23 +1101,18 @@ ingesteld WifiUI - - Scanning for networks... Scannen naar netwerken... - CONNECTING... VERBINDEN... - FORGET VERGETEN - Forget Wi-Fi Network "%1"? Vergeet Wi-Fi Netwerk "%1"? diff --git a/selfdrive/ui/translations/main_pl.ts b/selfdrive/ui/translations/main_pl.ts index 92902d04a9..4f8b03ef50 100644 --- a/selfdrive/ui/translations/main_pl.ts +++ b/selfdrive/ui/translations/main_pl.ts @@ -4,17 +4,14 @@ AbstractAlert - Close Zamknij - Snooze Update Zaktualizuj później - Reboot and Update Uruchom ponownie i zaktualizuj @@ -22,67 +19,84 @@ AdvancedNetworking - Back Wróć - Enable Tethering Włącz hotspot osobisty - Tethering Password Hasło do hotspotu - - EDIT EDYTUJ - Enter new tethering password Wprowadź nowe hasło do hotspotu - IP Address Adres IP - Enable Roaming Włącz roaming danych - APN Setting Ustawienia APN - Enter APN Wprowadź APN - leave blank for automatic configuration Pozostaw puste, aby użyć domyślnej konfiguracji + + Cellular Metered + + + + Prevent large data uploads when on a metered connection + + + + + AnnotatedCameraWidget + + km/h + km/h + + + mph + mph + + + MAX + MAX + + + SPEED + PRĘDKOŚĆ + + + LIMIT + OGRANICZENIE + ConfirmationDialog - - Ok Ok - Cancel Anuluj @@ -90,17 +104,14 @@ DeclinePage - You must accept the Terms and Conditions in order to use openpilot. Aby korzystać z openpilota musisz zaakceptować regulamin. - Back Wróć - Decline, uninstall %1 Odrzuć, odinstaluj %1 @@ -108,152 +119,122 @@ DevicePanel - Dongle ID ID adaptera - N/A N/A - Serial Numer seryjny - Driver Camera Kamera kierowcy - PREVIEW PODGLĄD - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) Wyświetl podgląd z kamery skierowanej na kierowcę, aby upewnić się, że monitoring kierowcy ma dobry zakres widzenia. (pojazd musi być wyłączony) - Reset Calibration Zresetuj kalibrację - RESET ZRESETUJ - Are you sure you want to reset calibration? Czy na pewno chcesz zresetować kalibrację? - Review Training Guide Zapoznaj się z samouczkiem - REVIEW ZAPOZNAJ SIĘ - Review the rules, features, and limitations of openpilot Zapoznaj się z zasadami, funkcjami i ograniczeniami openpilota - Are you sure you want to review the training guide? Czy na pewno chcesz się zapoznać z samouczkiem? - Regulatory Regulacja - VIEW WIDOK - Change Language Zmień język - CHANGE ZMIEŃ - Select a language Wybierz język - Reboot Uruchom ponownie - Power Off Wyłącz - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. openpilot wymaga, aby urządzenie było zamontowane z maksymalnym odchyłem 4° poziomo, 5° w górę oraz 8° w dół. openpilot jest ciągle kalibrowany, rzadko konieczne jest resetowania urządzenia. - Your device is pointed %1° %2 and %3° %4. Twoje urządzenie jest skierowane %1° %2 oraz %3° %4. - down w dół - up w górę - left w lewo - right w prawo - Are you sure you want to reboot? Czy na pewno chcesz uruchomić ponownie urządzenie? - Disengage to Reboot Aby uruchomić ponownie, odłącz sterowanie - Are you sure you want to power off? Czy na pewno chcesz wyłączyć urządzenie? - Disengage to Power Off Aby wyłączyć urządzenie, odłącz sterowanie @@ -261,32 +242,26 @@ DriveStats - Drives Przejazdy - Hours Godziny - ALL TIME CAŁKOWICIE - PAST WEEK OSTATNI TYDZIEŃ - KM KM - Miles Mile @@ -294,7 +269,6 @@ DriverViewScene - camera starting uruchamianie kamery @@ -302,12 +276,10 @@ InputDialog - Cancel Anuluj - Need at least %n character(s)! Wpisana wartość powinna składać się przynajmniej z %n znaku! @@ -319,22 +291,18 @@ Installer - Installing... Instalowanie... - Receiving objects: Odbieranie obiektów: - Resolving deltas: Rozwiązywanie różnic: - Updating files: Aktualizacja plików: @@ -342,27 +310,22 @@ MapETA - eta przewidywany czas - min min - hr godz - km km - mi mi @@ -370,22 +333,18 @@ MapInstructions - km km - m m - mi mi - ft ft @@ -393,48 +352,40 @@ MapPanel - Current Destination Miejsce docelowe - CLEAR WYCZYŚĆ - Recent Destinations Ostatnie miejsca docelowe - Try the Navigation Beta Wypróbuj nawigację w wersji beta - Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai Odblokuj nawigację zakręt po zakęcie i wiele więcej subskrybując comma prime. Zarejestruj się teraz: https://connect.comma.ai - No home location set Lokalizacja domu nie została ustawiona - No work location set Miejsce pracy nie zostało ustawione - no recent destinations brak ostatnich miejsc docelowych @@ -442,12 +393,10 @@ nie zostało ustawione MapWindow - Map Loading Ładowanie Mapy - Waiting for GPS Oczekiwanie na sygnał GPS @@ -455,12 +404,10 @@ nie zostało ustawione MultiOptionDialog - Select Wybierz - Cancel Anuluj @@ -468,72 +415,33 @@ nie zostało ustawione Networking - Advanced Zaawansowane - Enter password Wprowadź hasło - - for "%1" do "%1" - Wrong password Niepoprawne hasło - - AnnotatedCameraWidget - - - km/h - km/h - - - - mph - mph - - - - - MAX - MAX - - - - - SPEED - PRĘDKOŚĆ - - - - - LIMIT - OGRANICZENIE - - OffroadHome - UPDATE UAKTUALNIJ - ALERTS ALERTY - ALERT ALERT @@ -541,22 +449,18 @@ nie zostało ustawione PairingPopup - Pair your device to your comma account Sparuj swoje urzadzenie ze swoim kontem comma - Go to https://connect.comma.ai on your phone Wejdź na stronę https://connect.comma.ai na swoim telefonie - Click "add new device" and scan the QR code on the right Kliknij "add new device" i zeskanuj kod QR znajdujący się po prawej stronie - Bookmark connect.comma.ai to your home screen to use it like an app Dodaj connect.comma.ai do zakładek na swoim ekranie początkowym, aby korzystać z niej jak z aplikacji @@ -564,32 +468,26 @@ nie zostało ustawione PrimeAdWidget - Upgrade Now Uaktualnij teraz - Become a comma prime member at connect.comma.ai Zostań członkiem comma prime na connect.comma.ai - PRIME FEATURES: FUNKCJE PRIME: - Remote access Zdalny dostęp - 1 year of storage 1 rok przechowywania danych - Developer perks Udogodnienia dla programistów @@ -597,22 +495,18 @@ nie zostało ustawione PrimeUserWidget - ✓ SUBSCRIBED ✓ ZASUBSKRYBOWANO - comma prime comma prime - CONNECT.COMMA.AI CONNECT.COMMA.AI - COMMA POINTS COMMA POINTS @@ -620,27 +514,22 @@ nie zostało ustawione QObject - Reboot Uruchom Ponownie - Exit Wyjdź - dashcam wideorejestrator - openpilot openpilot - %n minute(s) ago %n minutę temu @@ -649,7 +538,6 @@ nie zostało ustawione - %n hour(s) ago % godzinę temu @@ -658,7 +546,6 @@ nie zostało ustawione - %n day(s) ago %n dzień temu @@ -670,47 +557,38 @@ nie zostało ustawione Reset - Reset failed. Reboot to try again. Wymazywanie zakończone niepowodzeniem. Aby spróbować ponownie, uruchom ponownie urządzenie. - Are you sure you want to reset your device? Czy na pewno chcesz wymazać urządzenie? - Resetting device... Wymazywanie urządzenia... - System Reset Przywróć do ustawień fabrycznych - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. Przywracanie do ustawień fabrycznych. Wciśnij potwierdź, aby usunąć wszystkie dane oraz ustawienia. Wciśnij anuluj, aby wznowić uruchamianie. - Cancel Anuluj - Reboot Uruchom ponownie - Confirm Potwiedź - Unable to mount data partition. Press confirm to reset your device. Partycja nie została zamontowana poprawnie. Wciśnij potwierdź, aby uruchomić ponownie urządzenie. @@ -718,7 +596,6 @@ nie zostało ustawione RichTextDialog - Ok Ok @@ -726,33 +603,26 @@ nie zostało ustawione SettingsWindow - × x - Device Urządzenie - - Network Sieć - Toggles Przełączniki - Software Oprogramowanie - Navigation Nawigacja @@ -760,105 +630,82 @@ nie zostało ustawione Setup - WARNING: Low Voltage OSTRZEŻENIE: Niskie Napięcie - Power your device in a car with a harness or proceed at your own risk. Podłącz swoje urządzenie do zasilania poprzez podłączenienie go do pojazdu lub kontynuuj na własną odpowiedzialność. - Power off Wyłącz - - - Continue Kontynuuj - Getting Started Zacznij - Before we get on the road, let’s finish installation and cover some details. Zanim ruszysz w drogę, dokończ instalację i podaj kilka szczegółów. - Connect to Wi-Fi Połącz z Wi-Fi - - Back Wróć - Continue without Wi-Fi Kontynuuj bez połączenia z Wif-Fi - Waiting for internet Oczekiwanie na połączenie sieciowe - Choose Software to Install Wybierz oprogramowanie do instalacji - Dashcam Wideorejestrator - Custom Software Własne oprogramowanie - Enter URL Wprowadź adres URL - for Custom Software do własnego oprogramowania - Downloading... Pobieranie... - Download Failed Pobieranie nie powiodło się - Ensure the entered URL is valid, and the device’s internet connection is good. Upewnij się, że wpisany adres URL jest poprawny, a połączenie internetowe działa poprawnie. - Reboot device Uruchom ponownie - Start over Zacznij od początku @@ -866,17 +713,14 @@ nie zostało ustawione SetupWidget - Finish Setup Zakończ konfigurację - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. Sparuj swoje urządzenie z comma connect (connect.comma.ai) i wybierz swoją ofertę comma prime. - Pair device Sparuj urządzenie @@ -884,106 +728,82 @@ nie zostało ustawione Sidebar - - CONNECT POŁĄCZENIE - OFFLINE OFFLINE - - ONLINE ONLINE - ERROR BŁĄD - - - TEMP TEMP - HIGH WYSOKA - GOOD DOBRA - OK OK - VEHICLE POJAZD - NO BRAK - PANDA PANDA - GPS GPS - SEARCH SZUKAJ - -- -- - Wi-Fi Wi-FI - ETH ETH - 2G 2G - 3G 3G - LTE LTE - 5G 5G @@ -991,138 +811,141 @@ nie zostało ustawione SoftwarePanel - Git Branch - Gałąź Git + Gałąź Git - Git Commit - Git commit + Git commit - OS Version - Wersja systemu + Wersja systemu - Version - Wersja + Wersja - Last Update Check - Ostatnie sprawdzenie aktualizacji + Ostatnie sprawdzenie aktualizacji - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - Ostatni raz kiedy openpilot znalazł aktualizację. Aktualizator może być uruchomiony wyłącznie wtedy, kiedy pojazd jest wyłączony. + Ostatni raz kiedy openpilot znalazł aktualizację. Aktualizator może być uruchomiony wyłącznie wtedy, kiedy pojazd jest wyłączony. - Check for Update - Sprawdź uaktualnienia + Sprawdź uaktualnienia - CHECKING - SPRAWDZANIE + SPRAWDZANIE - Switch Branch - Zmień gąłąź + Zmień gąłąź - ENTER - WPROWADŹ + WPROWADŹ - - The new branch will be pulled the next time the updater runs. - Nowa gałąź będzie pobrana przy następnym uruchomieniu aktualizatora. + Nowa gałąź będzie pobrana przy następnym uruchomieniu aktualizatora. - Enter branch name - Wprowadź nazwę gałęzi + Wprowadź nazwę gałęzi - Uninstall %1 Odinstaluj %1 - UNINSTALL ODINSTALUJ - Are you sure you want to uninstall? Czy na pewno chcesz odinstalować? - failed to fetch update - pobieranie aktualizacji zakończone niepowodzeniem + pobieranie aktualizacji zakończone niepowodzeniem - - CHECK SPRAWDŹ + + Updates are only downloaded while the car is off. + + + + Current Version + + + + Download + + + + Install Update + + + + INSTALL + + + + Target Branch + + + + SELECT + + + + Select a branch + + SshControl - SSH Keys Klucze SSH - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. Ostrzeżenie: To spowoduje przekazanie dostępu do wszystkich Twoich publicznych kuczy z ustawień GitHuba. Nigdy nie wprowadzaj nazwy użytkownika innej niż swoja. Pracownik comma NIGDY nie poprosi o dodanie swojej nazwy uzytkownika. - - ADD DODAJ - Enter your GitHub username Wpisz swoją nazwę użytkownika GitHub - LOADING ŁADOWANIE - REMOVE USUŃ - Username '%1' has no keys on GitHub Użytkownik '%1' nie posiada żadnych kluczy na GitHubie - Request timed out Limit czasu rządania - Username '%1' doesn't exist on GitHub Użytkownik '%1' nie istnieje na GitHubie @@ -1130,7 +953,6 @@ nie zostało ustawione SshToggle - Enable SSH Włącz SSH @@ -1138,22 +960,18 @@ nie zostało ustawione TermsPage - Terms & Conditions Regulamin - Decline Odrzuć - Scroll to accept Przewiń w dół, aby zaakceptować - Agree Zaakceptuj @@ -1161,125 +979,125 @@ nie zostało ustawione TogglesPanel - Enable openpilot Włącz openpilota - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. Użyj openpilota do zachowania bezpiecznego odstępu między pojazdami i do asystowania w utrzymywaniu pasa ruchu. Twoja pełna uwaga jest wymagana przez cały czas korzystania z tej funkcji. Ustawienie to może być wdrożone wyłącznie wtedy, gdy pojazd jest wyłączony. - Enable Lane Departure Warnings Włącz ostrzeganie przed zmianą pasa ruchu - 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). Otrzymuj alerty o powrocie na właściwy pas, kiedy Twój pojazd przekroczy linię bez włączonego kierunkowskazu jadąc powyżej 50 km/h (31 mph). - Use Metric System Korzystaj z systemu metrycznego - Display speed in km/h instead of mph. Wyświetl prędkość w km/h zamiast mph. - Record and Upload Driver Camera Nagraj i prześlij nagranie z kamery kierowcy - Upload data from the driver facing camera and help improve the driver monitoring algorithm. Prześlij dane z kamery skierowanej na kierowcę i pomóż poprawiać algorytm monitorowania kierowcy. - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal Odłącz poprzez naciśnięcie gazu - When enabled, pressing the accelerator pedal will disengage openpilot. Po włączeniu, naciśnięcie na pedał gazu odłączy openpilota. - Show ETA in 24h Format Pokaż oczekiwany czas dojazdu w formacie 24-godzinnym - Use 24h format instead of am/pm Korzystaj z formatu 24-godzinnego zamiast 12-godzinnego - Show Map on Left Side of UI Pokaż mapę po lewej stronie ekranu - Show map on left side when in split screen view. Pokaż mapę po lewej stronie kiedy ekran jest podzielony. - openpilot Longitudinal Control - Kontrola wzdłużna openpilota + Kontrola wzdłużna openpilota - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - openpilot wyłączy radar samochodu i przejmie kontrolę nad gazem i hamulcem. Ostrzeżenie: wyłączony zostanie system AEB! + openpilot wyłączy radar samochodu i przejmie kontrolę nad gazem i hamulcem. Ostrzeżenie: wyłączony zostanie system AEB! + + + 🌮 End-to-end longitudinal (extremely alpha) 🌮 + + + + Experimental openpilot Longitudinal Control + + + + <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> + + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. + + + + openpilot longitudinal control is not currently available for this car. + + + + Enable experimental longitudinal control to enable this. + Updater - Update Required Wymagana Aktualizacja - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. Wymagana aktualizacja systemu operacyjnego. Aby przyspieszyć proces aktualizacji połącz swoje urzeądzenie do Wi-Fi. Rozmiar pobieranej paczki wynosi około 1GB. - Connect to Wi-Fi Połącz się z Wi-Fi - Install Zainstaluj - Back Wróć - Loading... Ładowanie... - Reboot Uruchom ponownie - Update failed Aktualizacja nie powiodła się @@ -1287,23 +1105,18 @@ nie zostało ustawione WifiUI - - Scanning for networks... Wyszukiwanie sieci... - CONNECTING... ŁĄCZENIE... - FORGET ZAPOMNIJ - Forget Wi-Fi Network "%1"? Czy chcesz zapomnieć sieć "%1"? diff --git a/selfdrive/ui/translations/main_pt-BR.ts b/selfdrive/ui/translations/main_pt-BR.ts index 2afdaf3388..8dc3a9830d 100644 --- a/selfdrive/ui/translations/main_pt-BR.ts +++ b/selfdrive/ui/translations/main_pt-BR.ts @@ -132,19 +132,19 @@ Driver Camera - Câmera voltada para o Motorista + Câmera do Motorista PREVIEW - PREVISUAL + VER Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - Pré-visualizar a câmera voltada para o motorista para garantir que monitor tem uma boa visibilidade (veículo precisa estar desligado) + Pré-visualizar a câmera voltada para o motorista para garantir que o monitoramento do sistema tenha uma boa visibilidade (veículo precisa estar desligado) Reset Calibration - Resetar Calibragem + Reinicializar Calibragem RESET @@ -200,7 +200,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - o openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. o openpilot está continuamente calibrando, resetar raramente é necessário. + O openpilot requer que o dispositivo seja montado dentro de 4° esquerda ou direita e dentro de 5° para cima ou 8° para baixo. O openpilot está continuamente calibrando, resetar raramente é necessário. Your device is pointed %1° %2 and %3° %4. @@ -238,6 +238,14 @@ Disengage to Power Off Desacione para Desligar + + Reset + Resetar + + + Review + Revisar + DriveStats @@ -273,6 +281,17 @@ câmera iniciando + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + MODO EXPERIMENTAL ATIVADO + + + CHILL MODE ON + MODO CHILL ATIVADO + + InputDialog @@ -293,18 +312,6 @@ Installing... Instalando... - - Receiving objects: - Recebendo objetos: - - - Resolving deltas: - Resolvendo deltas: - - - Updating files: - Atualizando arquivos: - MapETA @@ -369,8 +376,8 @@ Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai - Obtenha instruções passo a passo exibidas e muito mais com -uma assinatura prime Inscreva-se agora: https://connect.comma.ai + Obtenha instruções passo a passo exibidas e muito mais com +uma assinatura prime. Inscreva-se agora: https://connect.comma.ai No home @@ -464,6 +471,17 @@ trabalho definido Salve connect.comma.ai como sua página inicial para utilizar como um app + + ParamControl + + Cancel + Cancelar + + + Enable + Ativar + + PrimeAdWidget @@ -472,7 +490,7 @@ trabalho definido Become a comma prime member at connect.comma.ai - Torne-se um membro comma prime em connect.comma.ai + Seja um membro comma prime em connect.comma.ai PRIME FEATURES: @@ -484,11 +502,11 @@ trabalho definido 1 year of storage - 1 ano de armazenamento + 1 ano na nuvem Developer perks - Benefícios para desenvolvedor + Benefícios para devs @@ -560,18 +578,10 @@ trabalho definido Are you sure you want to reset your device? Tem certeza que quer resetar seu dispositivo? - - Resetting device... - Resetando dispositivo... - System Reset Resetar Sistema - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - Solicitado reset do sistema. Confirme para apagar todo conteúdo e configurações. Aperte cancelar para continuar boot. - Cancel Cancelar @@ -585,15 +595,18 @@ trabalho definido Confirmar - Unable to mount data partition. Press confirm to reset your device. - Não foi possível montar a partição de dados. Pressione confirmar para resetar seu dispositivo. + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + Não é possível montar a partição de dados. Partição corrompida. Confirme para apagar e redefinir o dispositivo. - - - RichTextDialog - Ok - Ok + Press confirm to erase all content and settings. Press cancel to resume boot. + Pressione confirmar para apagar todo o conteúdo e configurações. Pressione cancelar para voltar. + + + Resetting device... +This may take up to a minute. + Redefinindo o dispositivo +Isso pode levar até um minuto. @@ -665,18 +678,6 @@ trabalho definido Waiting for internet Esperando pela internet - - Choose Software to Install - Escolher Software para Instalar - - - Dashcam - Dashcam - - - Custom Software - Sofware Customizado - Enter URL Preencher URL @@ -705,6 +706,14 @@ trabalho definido Start over Inicializar + + No custom software found at this URL. + Não há software personalizado nesta URL. + + + Something went wrong. Reboot the device. + Algo deu errado. Reinicie o dispositivo. + SetupWidget @@ -812,7 +821,7 @@ trabalho definido Current Version - Versao Atual + Versão Atual Download @@ -840,11 +849,11 @@ trabalho definido UNINSTALL - DESINSTAL + REMOVER Uninstall %1 - Desintalar o %1 + Desinstalar o %1 Are you sure you want to uninstall? @@ -854,6 +863,10 @@ trabalho definido CHECK VERIFICAR + + Uninstall + Desinstalar + SshControl @@ -955,32 +968,12 @@ trabalho definido Upload dados da câmera voltada para o motorista e ajude a melhorar o algoritmo de monitoramentor. - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 End-to-end longitudinal (experimental) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control Controle longitudinal experimental openpilot - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>AVISO: o controle longitudinal openpilot é experimental para este carro e irá desabilitar AEB.</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - Deixe o modelo controlar o acelerador e os freios. openpilot irá conduzir como pensa que um humano faria. Super experimental. - - - openpilot longitudinal control is not currently available for this car. - controle longitudinal openpilot não está disponível para este carro. - - - Enable experimental longitudinal control to enable this. - Habilite o controle longitudinal experimental para habilitar isso. - - - Disengage On Accelerator Pedal - Desacionar Com Pedal Do Acelerador + Disengage on Accelerator Pedal + Desacionar com Pedal do Acelerador When enabled, pressing the accelerator pedal will disengage openpilot. @@ -988,7 +981,7 @@ trabalho definido Show ETA in 24h Format - Mostrar ETA em formato 24h + Mostrar ETA em Formato 24h Use 24h format instead of am/pm @@ -1002,6 +995,54 @@ trabalho definido Show map on left side when in split screen view. Exibir mapa do lado esquerdo quando a tela for dividida. + + Experimental Mode + Modo Experimental + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + ATENÇÃO: o controle longitudinal do openpilot é experimental para este carro e desativará a Frenagem Automática de Emergência (AEB). + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + Neste carro o penpilot por padrão utiliza o ACC nativo do veículo ao invés de controlar longitudinalmente. Ative isto para mudar para o controle longitudinal do openpilot. Ativar o Modo Experimental é recomendado quando em uso do controle longitudinal experimental do openpilot. + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot por padrão funciona em <b>modo chill</b>. modo Experimental ativa <b>recursos de nível-alfa</b> que não estão prontos para o modo chill. Recursos experimentais estão listados abaixo: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 Controle Longitudinal de Ponta a Ponta 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + Deixe o modelo de IA controlar o acelerador e os freios. O openpilot irá dirigir como pensa que um humano faria, incluindo parar em sinais vermelhos e sinais de parada. Uma vez que o modelo de condução decide a velocidade a conduzir, a velocidade definida apenas funcionará como um limite superior. Este é um recurso de qualidade alfa; erros devem ser esperados. + + + New Driving Visualization + Nova Visualização de Condução + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + A visualização da direção fará a transição para a câmera grande angular voltada para a estrada em baixas velocidades para mostrar melhor algumas curvas. O logotipo do modo Experimental também será exibido no canto superior direito. + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + O modo Experimental está atualmente indisponível para este carro já que o ACC original do carro é usado para controle longitudinal. + + + openpilot longitudinal control may come in a future update. + O controle longitudinal openpilot pode vir em uma atualização futura. + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + Uma versão experimental do controle longitudinal openpilot pode ser testada, juntamente com o modo Experimental, em branches de desenvolvimento. + + + Enable experimental longitudinal control to allow Experimental mode. + Ative o controle longitudinal experimental para permitir o modo Experimental. + Updater @@ -1056,5 +1097,9 @@ trabalho definido Forget Wi-Fi Network "%1"? Esquecer Rede Wi-Fi "%1"? + + Forget + Esquecer + diff --git a/selfdrive/ui/translations/main_th.ts b/selfdrive/ui/translations/main_th.ts index d502c3fce1..45f91f1cdb 100644 --- a/selfdrive/ui/translations/main_th.ts +++ b/selfdrive/ui/translations/main_th.ts @@ -4,17 +4,14 @@ AbstractAlert - Close ปิด - Snooze Update เลื่อนการอัปเดต - Reboot and Update รีบูตและอัปเดต @@ -22,67 +19,84 @@ AdvancedNetworking - Back ย้อนกลับ - Enable Tethering ปล่อยฮอตสปอต - Tethering Password รหัสผ่านฮอตสปอต - - EDIT แก้ไข - Enter new tethering password ป้อนรหัสผ่านฮอตสปอตใหม่ - IP Address หมายเลขไอพี - Enable Roaming เปิดใช้งานโรมมิ่ง - APN Setting ตั้งค่า APN - Enter APN ป้อนค่า APN - leave blank for automatic configuration เว้นว่างเพื่อตั้งค่าอัตโนมัติ + + Cellular Metered + ลดการส่งข้อมูลผ่านเซลลูล่าร์ + + + Prevent large data uploads when on a metered connection + ปิดการอัพโหลดข้อมูลขนาดใหญ่เมื่อเชื่อมต่อผ่านเซลลูล่าร์ + + + + AnnotatedCameraWidget + + km/h + กม./ชม. + + + mph + ไมล์/ชม. + + + MAX + สูงสุด + + + SPEED + ความเร็ว + + + LIMIT + จำกัด + ConfirmationDialog - - Ok ตกลง - Cancel ยกเลิก @@ -90,17 +104,14 @@ DeclinePage - You must accept the Terms and Conditions in order to use openpilot. คุณต้องยอมรับเงื่อนไขและข้อตกลง เพื่อใช้งาน openpilot - Back ย้อนกลับ - Decline, uninstall %1 ปฏิเสธ และถอนการติดตั้ง %1 @@ -108,185 +119,157 @@ DevicePanel - Dongle ID Dongle ID - N/A ไม่มี - Serial ซีเรียล - Driver Camera กล้องฝั่งคนขับ - PREVIEW แสดงภาพ - Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) ดูภาพตัวอย่างกล้องที่หันเข้าหาคนขับเพื่อให้แน่ใจว่าการตรวจสอบคนขับมีทัศนวิสัยที่ดี (รถต้องดับเครื่องยนต์) - Reset Calibration รีเซ็ตการคาลิเบรท - RESET รีเซ็ต - Are you sure you want to reset calibration? คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตการคาลิเบรท? - Review Training Guide ทบทวนคู่มือการใช้งาน - REVIEW ทบทวน - Review the rules, features, and limitations of openpilot ตรวจสอบกฎ คุณสมบัติ และข้อจำกัดของ openpilot - Are you sure you want to review the training guide? คุณแน่ใจหรือไม่ว่าต้องการทบทวนคู่มือการใช้งาน? - Regulatory ระเบียบข้อบังคับ - VIEW ดู - Change Language เปลี่ยนภาษา - CHANGE เปลี่ยน - Select a language เลือกภาษา - Reboot รีบูต - Power Off ปิดเครื่อง - openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. openpilot กำหนดให้ติดตั้งอุปกรณ์ โดยสามารถเอียงด้านซ้ายหรือขวาไม่เกิน 4° และเอียงขึ้นด้านบนไม่เกิน 5° หรือเอียงลงด้านล่างไม่เกิน 8° openpilot ทำการคาลิเบรทอย่างต่อเนื่อง แทบจะไม่จำเป็นต้องทำการรีเซ็ตการคาลิเบรท - Your device is pointed %1° %2 and %3° %4. อุปกรณ์ของคุณเอียงไปทาง %2 %1° และ %4 %3° - down ด้านล่าง - up ด้านบน - left ด้านซ้าย - right ด้านขวา - Are you sure you want to reboot? คุณแน่ใจหรือไม่ว่าต้องการรีบูต? - Disengage to Reboot ยกเลิกระบบช่วยขับเพื่อรีบูต - Are you sure you want to power off? คุณแน่ใจหรือไม่ว่าต้องการปิดเครื่อง? - Disengage to Power Off ยกเลิกระบบช่วยขับเพื่อปิดเครื่อง + + Reset + รีเซ็ต + + + Review + ทบทวน + DriveStats - Drives การขับขี่ - Hours ชั่วโมง - ALL TIME ทั้งหมด - PAST WEEK สัปดาห์ที่ผ่านมา - KM กิโลเมตร - Miles ไมล์ @@ -294,20 +277,28 @@ DriverViewScene - camera starting กำลังเปิดกล้อง + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + คุณกำลังใช้โหมดทดลอง + + + CHILL MODE ON + คุณกำลังใช้โหมดชิล + + InputDialog - Cancel ยกเลิก - Need at least %n character(s)! ต้องการอย่างน้อย %n ตัวอักษร! @@ -317,50 +308,29 @@ Installer - Installing... กำลังติดตั้ง... - - - Receiving objects: - กำลังรับข้อมูล: - - - - Resolving deltas: - การแก้ไขเดลต้า: - - - - Updating files: - กำลังอัปเดตไฟล์: - MapETA - eta eta - min นาที - hr ชม. - km กม. - mi ไมล์ @@ -368,22 +338,18 @@ MapInstructions - km กม. - m ม. - mi ไมล์ - ft ฟุต @@ -391,48 +357,40 @@ MapPanel - Current Destination ปลายทางปัจจุบัน - CLEAR ล้างข้อมูล - Recent Destinations ปลายทางล่าสุด - Try the Navigation Beta ลองใช้ระบบนำทาง (เบต้า) - Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai รับการแสดงเส้นทางแบบเลี้ยวต่อเลี้ยว และอื่นๆ ด้วยการสมัครบริการ comma prime สมัครเลย: https://connect.comma.ai - No home location set ยังไม่ได้กำหนด ตำแหน่งของบ้าน - No work location set ยังไม่ได้กำหนด ตำแหน่งของที่ทำงาน - no recent destinations ไม่พบปลายทางล่าสุด @@ -440,12 +398,10 @@ location set MapWindow - Map Loading กำลังโหลดแผนที่ - Waiting for GPS กำลังรอสัญญาณ GPS @@ -453,12 +409,10 @@ location set MultiOptionDialog - Select เลือก - Cancel ยกเลิก @@ -466,72 +420,33 @@ location set Networking - Advanced ขั้นสูง - Enter password ใส่รหัสผ่าน - - for "%1" สำหรับ "%1" - Wrong password รหัสผ่านผิด - - AnnotatedCameraWidget - - - km/h - กม./ชม. - - - - mph - ไมล์/ชม. - - - - - MAX - สูงสุด - - - - - SPEED - ความเร็ว - - - - - LIMIT - จำกัด - - OffroadHome - UPDATE อัปเดต - ALERTS การแจ้งเตือน - ALERT การแจ้งเตือน @@ -539,55 +454,56 @@ location set PairingPopup - Pair your device to your comma account จับคู่อุปกรณ์ของคุณกับบัญชี comma ของคุณ - Go to https://connect.comma.ai on your phone ไปที่ https://connect.comma.ai ด้วยโทรศัพท์ของคุณ - Click "add new device" and scan the QR code on the right กดที่ "add new device" และสแกนคิวอาร์โค้ดทางด้านขวา - Bookmark connect.comma.ai to your home screen to use it like an app จดจำ connect.comma.ai โดยการเพิ่มไปยังหน้าจอโฮม เพื่อใช้งานเหมือนเป็นแอปพลิเคชัน + + ParamControl + + Enable + เปิดใช้งาน + + + Cancel + ยกเลิก + + PrimeAdWidget - Upgrade Now อัพเกรดเดี๋ยวนี้ - Become a comma prime member at connect.comma.ai สมัครสมาชิก comma prime ได้ที่ connect.comma.ai - PRIME FEATURES: คุณสมบัติของ PRIME: - Remote access การเข้าถึงระยะไกล - 1 year of storage จัดเก็บข้อมูลนาน 1 ปี - Developer perks สิทธิพิเศษสำหรับนักพัฒนา @@ -595,22 +511,18 @@ location set PrimeUserWidget - ✓ SUBSCRIBED ✓ สมัครสำเร็จ - comma prime comma prime - CONNECT.COMMA.AI CONNECT.COMMA.AI - COMMA POINTS คะแนน COMMA @@ -618,41 +530,34 @@ location set QObject - Reboot รีบูต - Exit ปิด - dashcam กล้องติดรถยนต์ - openpilot openpilot - %n minute(s) ago %n นาทีที่แล้ว - %n hour(s) ago %n ชั่วโมงที่แล้ว - %n day(s) ago %n วันที่แล้ว @@ -662,89 +567,67 @@ location set Reset - Reset failed. Reboot to try again. การรีเซ็ตล้มเหลว รีบูตเพื่อลองอีกครั้ง - Are you sure you want to reset your device? คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตอุปกรณ์? - - Resetting device... - กำลังรีเซ็ตอุปกรณ์... - - - System Reset รีเซ็ตระบบ - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - มีการสั่งรีเซ็ตระบบ กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตเข้าระบบตามปกติ - - - Cancel ยกเลิก - Reboot รีบูต - Confirm ยืนยัน - - Unable to mount data partition. Press confirm to reset your device. - ไม่สามารถเมานต์พาร์ติชั่นข้อมูล กดยืนยันเพื่อรีเซ็ตอุปกรณ์ของคุณ + Resetting device... +This may take up to a minute. + กำลังรีเซ็ตอุปกรณ์... +อาจใช้เวลาถึงหนึ่งนาที - - - RichTextDialog - - Ok - ตกลง + Press confirm to erase all content and settings. Press cancel to resume boot. + กดยืนยันเพื่อลบข้อมูลและการตั้งค่าทั้งหมด กดยกเลิกเพื่อบูตต่อ + + + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + ไม่สามารถเมานต์พาร์ติชั่นข้อมูลได้ พาร์ติชั่นอาจเสียหาย กดยืนยันเพื่อลบและรีเซ็ตอุปกรณ์ของคุณ SettingsWindow - × × - Device อุปกรณ์ - - Network เครือข่าย - Toggles ตัวเลือก - Software ซอฟต์แวร์ - Navigation การนำทาง @@ -752,123 +635,93 @@ location set Setup - WARNING: Low Voltage คำเตือน: แรงดันแบตเตอรี่ต่ำ - Power your device in a car with a harness or proceed at your own risk. โปรดต่ออุปกรณ์ของคุณเข้ากับสายควบคุมในรถยนต์ หรือดำเนินการด้วยความเสี่ยงของคุณเอง - Power off ปิดเครื่อง - - - Continue ดำเนินการต่อ - Getting Started เริ่มกันเลย - Before we get on the road, let’s finish installation and cover some details. ก่อนออกเดินทาง เรามาทำการติดตั้งซอฟต์แวร์ และตรวจสอบการตั้งค่า - Connect to Wi-Fi เชื่อมต่อ Wi-Fi - - Back ย้อนกลับ - Continue without Wi-Fi ดำเนินการต่อโดยไม่ใช้ Wi-Fi - Waiting for internet กำลังรอสัญญาณอินเตอร์เน็ต - - Choose Software to Install - เลือกซอฟต์แวร์ที่จะติดตั้ง - - - - Dashcam - กล้องติดรถยนต์ - - - - Custom Software - ซอฟต์แวร์ที่กำหนดเอง - - - Enter URL ป้อน URL - for Custom Software สำหรับซอฟต์แวร์ที่กำหนดเอง - Downloading... กำลังดาวน์โหลด... - Download Failed ดาวน์โหลดล้มเหลว - Ensure the entered URL is valid, and the device’s internet connection is good. ตรวจสอบให้แน่ใจว่า URL ที่ป้อนนั้นถูกต้อง และอุปกรณ์เชื่อมต่ออินเทอร์เน็ตอยู่ - Reboot device รีบูตอุปกรณ์ - Start over เริ่มต้นใหม่ + + Something went wrong. Reboot the device. + มีบางอย่างผิดพลาด รีบูตอุปกรณ์ + + + No custom software found at this URL. + ไม่พบซอฟต์แวร์ที่กำหนดเองที่ URL นี้ + SetupWidget - Finish Setup ตั้งค่าเสร็จสิ้น - Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer. จับคู่อุปกรณ์ของคุณกับ comma connect (connect.comma.ai) และรับข้อเสนอ comma prime ของคุณ - Pair device จับคู่อุปกรณ์ @@ -876,106 +729,82 @@ location set Sidebar - - CONNECT เชื่อมต่อ - OFFLINE ออฟไลน์ - - ONLINE ออนไลน์ - ERROR เกิดข้อผิดพลาด - - - TEMP อุณหภูมิ - HIGH สูง - GOOD ดี - OK พอใช้ - VEHICLE รถยนต์ - NO ไม่พบ - PANDA PANDA - GPS จีพีเอส - SEARCH ค้นหา - -- -- - Wi-Fi Wi-Fi - ETH ETH - 2G 2G - 3G 3G - LTE LTE - 5G 5G @@ -983,138 +812,93 @@ location set SoftwarePanel - - Git Branch - Git Branch - - - - Git Commit - Git Commit - - - - OS Version - เวอร์ชันระบบปฏิบัติการ - - - - Version - เวอร์ชั่น - - - - Last Update Check - ตรวจสอบการอัปเดตล่าสุด - - - - The last time openpilot successfully checked for an update. The updater only runs while the car is off. - ครั้งสุดท้ายที่ openpilot ตรวจสอบการอัปเดตสำเร็จ ตัวอัปเดตจะทำงานในขณะที่รถดับเครื่องอยู่เท่านั้น - - - - Check for Update - ตรวจสอบการอัปเดต - - - - CHECKING - กำลังตรวจสอบ - - - - Switch Branch - เปลี่ยน Branch - - - - ENTER - เปลี่ยน - - - - - The new branch will be pulled the next time the updater runs. - Branch ใหม่จะถูกติดตั้งในครั้งต่อไปที่ตัวอัปเดตทำงาน - - - - Enter branch name - ใส่ชื่อ Branch - - - Uninstall %1 ถอนการติดตั้ง %1 - UNINSTALL ถอนการติดตั้ง - Are you sure you want to uninstall? คุณแน่ใจหรือไม่ว่าต้องการถอนการติดตั้ง? - - failed to fetch update - โหลดข้อมูลอัปเดตไม่สำเร็จ - - - - CHECK ตรวจสอบ + + Updates are only downloaded while the car is off. + ตัวอัปเดตจะดำเนินการดาวน์โหลดเมื่อรถดับเครื่องยนต์อยู่เท่านั้น + + + Current Version + เวอร์ชั่นปัจจุบัน + + + Download + ดาวน์โหลด + + + Install Update + ติดตั้งตัวอัปเดต + + + INSTALL + ติดตั้ง + + + Target Branch + Branch ที่เลือก + + + SELECT + เลือก + + + Select a branch + เลือก Branch + + + Uninstall + ถอนการติดตั้ง + SshControl - SSH Keys คีย์ SSH - Warning: This grants SSH access to all public keys in your GitHub settings. Never enter a GitHub username other than your own. A comma employee will NEVER ask you to add their GitHub username. คำเตือน: สิ่งนี้ให้สิทธิ์ SSH เข้าถึงคีย์สาธารณะทั้งหมดใน GitHub ของคุณ อย่าป้อนชื่อผู้ใช้ GitHub อื่นนอกเหนือจากของคุณเอง พนักงาน comma จะไม่ขอให้คุณเพิ่มชื่อผู้ใช้ GitHub ของพวกเขา - - ADD เพิ่ม - Enter your GitHub username ป้อนชื่อผู้ใช้ GitHub ของคุณ - LOADING กำลังโหลด - REMOVE ลบ - Username '%1' has no keys on GitHub ชื่อผู้ใช้ '%1' ไม่มีคีย์บน GitHub - Request timed out ตรวจสอบไม่สำเร็จ เนื่องจากใช้เวลามากเกินไป - Username '%1' doesn't exist on GitHub ไม่พบชื่อผู้ใช้ '%1' บน GitHub @@ -1122,7 +906,6 @@ location set SshToggle - Enable SSH เปิดใช้งาน SSH @@ -1130,22 +913,18 @@ location set TermsPage - Terms & Conditions ข้อตกลงและเงื่อนไข - Decline ปฏิเสธ - Scroll to accept เลื่อนเพื่อตอบรับข้อตกลง - Agree ยอมรับ @@ -1153,125 +932,145 @@ location set TogglesPanel - Enable openpilot เปิดใช้งาน openpilot - Use the openpilot system for adaptive cruise control and lane keep driver assistance. Your attention is required at all times to use this feature. Changing this setting takes effect when the car is powered off. ใช้ระบบ openpilot สำหรับระบบควบคุมความเร็วอัตโนมัติ และระบบช่วยควบคุมรถให้อยู่ในเลน คุณจำเป็นต้องให้ความสนใจตลอดเวลาที่ใช้คุณสมบัตินี้ การเปลี่ยนการตั้งค่านี้จะมีผลเมื่อคุณดับเครื่องยนต์ - 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 31 mph (50 km/h). รับการแจ้งเตือนให้เลี้ยวกลับเข้าเลนเมื่อรถของคุณตรวจพบการข้ามช่องจราจรโดยไม่เปิดสัญญาณไฟเลี้ยวในขณะขับขี่ที่ความเร็วเกิน 31 ไมล์ต่อชั่วโมง (50 กม./ชม) - Use Metric System ใช้ระบบเมตริก - Display speed in km/h instead of mph. แสดงความเร็วเป็น กม./ชม. แทน ไมล์/ชั่วโมง - Record and Upload Driver Camera บันทึกและอัปโหลดภาพจากกล้องคนขับ - Upload data from the driver facing camera and help improve the driver monitoring algorithm. อัปโหลดข้อมูลจากกล้องที่หันหน้าไปทางคนขับ และช่วยปรับปรุงอัลกอริธึมการตรวจสอบผู้ขับขี่ - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal ยกเลิกระบบช่วยขับเมื่อเหยียบคันเร่ง - When enabled, pressing the accelerator pedal will disengage openpilot. เมื่อเปิดใช้งาน การกดแป้นคันเร่งจะเป็นการยกเลิกระบบช่วยขับโดย openpilot - Show ETA in 24h Format แสดงเวลา ETA ในรูปแบบ 24 ชั่วโมง - Use 24h format instead of am/pm ใช้รูปแบบเวลา 24 ชั่วโมง แทน am/pm - Show Map on Left Side of UI แสดงแผนที่ที่ด้านซ้ายของหน้าจอ - Show map on left side when in split screen view. แสดงแผนที่ด้านซ้ายของหน้าจอเมื่ออยู่ในโหมดแบ่งหน้าจอ - - openpilot Longitudinal Control - openpilot การควบคุมการเร่งและลดความเร็ว + Experimental openpilot Longitudinal Control + ทดลองใช้ระบบควบคุมการเร่ง/เบรคโดย openpilot - - openpilot will disable the car's radar and will take over control of gas and brakes. Warning: this disables AEB! - openpilot จะปิดการใช้งานเรดาร์ของรถ และจะเข้าควบคุมการเร่งและเบรก คำเตือน: สิ่งนี้จะปิดระบบ AEB! + Experimental Mode + โหมดทดลอง + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + คำเตือน: การควบคุมการเร่ง/เบรคโดย openpilot สำหรับรถคันนี้ยังอยู่ในขั้นพัฒนา และระบบเบรคฉุกเฉินอัตโนมัติ (AEB) จะถูกปิด + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + โดยปกติสำหรับรถคันนี้ openpilot จะควบคุมการเร่ง/เบรคด้วยระบบ ACC จากโรงงาน แทนการควยคุมโดย openpilot เปิดสวิตซ์นี้เพื่อให้ openpilot ควบคุมการเร่ง/เบรค แนะนำให้เปิดโหมดทดลองเมื่อต้องการให้ openpilot ควบคุมการเร่ง/เบรค ซึ่งอยู่ในขั้นพัฒนา + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + โดยปกติ openpilot จะขับใน<b>โหมดชิล</b> เปิดโหมดทดลองเพื่อใช้<b>ความสามารถในขั้นพัฒนา</b> ซึ่งยังไม่พร้อมสำหรับโหมดชิล ความสามารถในขั้นพัฒนามีดังนี้: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 ควบคุมเร่ง/เบรคแบบ End-to-End 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + ให้ openpilot ควบคุมการเร่ง/เบรค โดย openpilot จะขับอย่างที่มนุษย์คิด รวมถึงการหยุดที่ไฟแดง และป้ายหยุดรถ เนื่องจาก openpilot จะกำหนดความเร็วในการขับด้วยตัวเอง การตั้งความเร็วจะเป็นเพียงการกำหนดความเร็วสูงสูดเท่านั้น ความสามารถนี้ยังอยู่ในขั้นพัฒนา อาจเกิดข้อผิดพลาดขึ้นได้ + + + New Driving Visualization + การแสดงภาพการขับขี่แบบใหม่ + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + การแสดงภาพการขับขี่จะเปลี่ยนไปใช้กล้องมุมกว้างที่หันหน้าไปทางถนนเมื่ออยู่ในความเร็วต่ำ เพื่อแสดงภาพการเลี้ยวที่ดีขึ้น โลโก้โหมดการทดลองจะแสดงที่มุมบนขวาด้วย + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + ขณะนี้โหมดทดลองไม่สามารถใช้งานได้ในรถคันนี้ เนื่องจากเปิดใช้ระบบควบคุมการเร่ง/เบรคของรถที่ติดตั้งจากโรงงานอยู่ + + + openpilot longitudinal control may come in a future update. + ระบบควบคุมการเร่ง/เบรคโดย openpilot อาจมาในการอัปเดตในอนาคต + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + เวอร์ชันทดลองของระบบควบคุมการเร่ง/เบรคโดย openpilot สามารถทดสอบได้พร้อมกับโหมดการทดลอง บน branch ที่กำลังพัฒนา + + + Enable experimental longitudinal control to allow Experimental mode. + เปิดระบบควบคุมการเร่ง/เบรคขั้นพัฒนาโดย openpilot เพื่อเปิดใช้งานโหมดทดลอง Updater - Update Required จำเป็นต้องอัปเดต - An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. The download size is approximately 1GB. จำเป็นต้องมีการอัปเดตระบบปฏิบัติการ เชื่อมต่ออุปกรณ์ของคุณกับ Wi-Fi เพื่อประสบการณ์การอัปเดตที่เร็วที่สุด ขนาดดาวน์โหลดประมาณ 1GB - Connect to Wi-Fi เชื่อมต่อกับ Wi-Fi - Install ติดตั้ง - Back ย้อนกลับ - Loading... กำลังโหลด... - Reboot รีบูต - Update failed การอัปเดตล้มเหลว @@ -1279,25 +1078,24 @@ location set WifiUI - - Scanning for networks... กำลังสแกนหาเครือข่าย... - CONNECTING... กำลังเชื่อมต่อ... - FORGET เลิกใช้ - Forget Wi-Fi Network "%1"? เลิกใช้เครือข่าย Wi-Fi "%1"? + + Forget + เลิกใช้ + diff --git a/selfdrive/ui/translations/main_zh-CHS.ts b/selfdrive/ui/translations/main_zh-CHS.ts index 9e7c354444..cfc055ac98 100644 --- a/selfdrive/ui/translations/main_zh-CHS.ts +++ b/selfdrive/ui/translations/main_zh-CHS.ts @@ -60,11 +60,11 @@ Cellular Metered - + 按流量计费的手机移动网络 Prevent large data uploads when on a metered connection - + 当使用按流量计费的连接时,避免上传大流量数据 @@ -140,7 +140,7 @@ Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off) - 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。仅熄火时可用。 + 打开并预览驾驶员摄像头,以确保驾驶员监控具有良好视野。(仅熄火时可用) Reset Calibration @@ -204,7 +204,7 @@ Your device is pointed %1° %2 and %3° %4. - 您的设备校准为%1° %2、%3° %4。 + 您的设备校准为%1° %2、%3° %4。 down @@ -238,6 +238,14 @@ Disengage to Power Off 取消openpilot以关机 + + Reset + 重置 + + + Review + 预览 + DriveStats @@ -273,6 +281,17 @@ 正在启动相机 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + 试验模式运行 + + + CHILL MODE ON + 轻松模式运行 + + InputDialog @@ -292,18 +311,6 @@ Installing... 正在安装…… - - Receiving objects: - 正在接收: - - - Resolving deltas: - 正在处理: - - - Updating files: - 正在更新文件: - MapETA @@ -368,7 +375,7 @@ Get turn-by-turn directions displayed and more with a comma prime subscription. Sign up now: https://connect.comma.ai - 订阅comma prime以获取导航。 + 订阅comma prime以获取导航。 立即注册:https://connect.comma.ai @@ -461,6 +468,17 @@ location set 将 connect.comma.ai 收藏到您的主屏幕,以便像应用程序一样使用它 + + ParamControl + + Cancel + 取消 + + + Enable + 启用 + + PrimeAdWidget @@ -554,18 +572,10 @@ location set Are you sure you want to reset your device? 您确定要重置您的设备吗? - - Resetting device... - 正在重置设备…… - System Reset 恢复出厂设置 - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - 已触发系统重置:确认以删除所有内容和设置。取消以正常启动设备。 - Cancel 取消 @@ -579,15 +589,17 @@ location set 确认 - Unable to mount data partition. Press confirm to reset your device. - 无法挂载数据分区。 确认以重置您的设备。 + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + - - - RichTextDialog - Ok - 好的 + Press confirm to erase all content and settings. Press cancel to resume boot. + + + + Resetting device... +This may take up to a minute. + @@ -659,18 +671,6 @@ location set Waiting for internet 等待网络连接 - - Choose Software to Install - 选择要安装的软件 - - - Dashcam - Dashcam(行车记录仪) - - - Custom Software - 自定义软件 - Enter URL 输入网址 @@ -699,6 +699,14 @@ location set Start over 重来 + + No custom software found at this URL. + + + + Something went wrong. Reboot the device. + + SetupWidget @@ -802,35 +810,35 @@ location set SoftwarePanel Updates are only downloaded while the car is off. - + 车辆熄火时才能下载升级文件。 Current Version - + 当前版本 Download - + 下载 Install Update - + 安装更新 INSTALL - + 安装 Target Branch - + 目标分支 SELECT - + 选择 Select a branch - + 选择分支 UNINSTALL @@ -848,6 +856,10 @@ location set CHECK 查看 + + Uninstall + 卸载 + SshControl @@ -949,31 +961,11 @@ location set 上传驾驶员摄像头的数据,帮助改进驾驶员监控算法。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 端对端纵向控制(实验性功能) 🌮 + Experimental openpilot Longitudinal Control + 试验性的openpilot纵向控制 - Experimental openpilot longitudinal control - - - - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 让驾驶模型直接控制油门和刹车,openpilot将会模仿人类司机的驾驶方式。该功能仍非常实验性。 - - - openpilot longitudinal control is not currently available for this car. - - - - Enable experimental longitudinal control to enable this. - - - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 踩油门时取消控制 @@ -996,6 +988,54 @@ location set Show map on left side when in split screen view. 在分屏模式中,将地图置于屏幕左侧。 + + Experimental Mode + 测试模式 + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告: 此车辆的openpilot纵向控制是试验性功能,且将禁用AEB自动刹车功能。 + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + 针对此车辆,openpilot默认使用车辆自带的ACC,而非openpilot的纵向控制。启用此选项将切换到openpilot纵向控制。当使用试验性的openpilot纵向控制时,建议同时启用试验模式。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 默认 <b>轻松模式</b>驾驶车辆。试验模式启用一些轻松模式之外的 <b>试验性功能</b>。试验性功能包括: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮 端到端(End-to-End) 纵向控制 🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 允许驾驶模型控制加速和制动,openpilot将模仿人类驾驶车辆,包括在红灯和停车让行标识前停车。鉴于驾驶模型确定行驶车速,所设定的车速仅作为上限。此功能尚处于早期测试状态,有可能会出现操作错误。 + + + New Driving Visualization + 新驾驶视角 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 当低速行驶时,驾驶视角将切换到前向广角摄像头,便于更完整地显示转向路径。右上角将显示试验模式图标。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 由于此车辆使用自带的ACC纵向控制,当前无法使用试验模式。 + + + openpilot longitudinal control may come in a future update. + + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + + + + Enable experimental longitudinal control to allow Experimental mode. + 启用试验性的纵向控制,以便允许使用试验模式。 + Updater @@ -1044,11 +1084,15 @@ location set FORGET - 忘记 + 忽略 Forget Wi-Fi Network "%1"? - 忘记WiFi网络 "%1"? + 忽略WiFi网络 "%1"? + + + Forget + 忽略 diff --git a/selfdrive/ui/translations/main_zh-CHT.ts b/selfdrive/ui/translations/main_zh-CHT.ts index 513135c7f4..83db0b7301 100644 --- a/selfdrive/ui/translations/main_zh-CHT.ts +++ b/selfdrive/ui/translations/main_zh-CHT.ts @@ -60,11 +60,11 @@ Cellular Metered - + 行動網路 Prevent large data uploads when on a metered connection - + 防止使用行動網路上傳大量的數據 @@ -113,7 +113,7 @@ Decline, uninstall %1 - 拒絕並卸載 %1 + 拒絕並解除安裝 %1 @@ -132,7 +132,7 @@ Driver Camera - 駕駛員攝像頭 + 駕駛員監控鏡頭 PREVIEW @@ -200,7 +200,7 @@ openpilot requires the device to be mounted within 4° left or right and within 5° up or 8° down. openpilot is continuously calibrating, resetting is rarely required. - openpilot 需要將裝置固定在左右偏差 4° 以內,朝上偏差 5° 以内或朝下偏差 8° 以内。鏡頭在後台會持續自動校準,很少有需要重置的情况。 + openpilot 需要將設備固定在左右偏差 4° 以內,朝上偏差 5° 以内或朝下偏差 8° 以内。鏡頭在後台會持續自動校準,很少有需要重置的情况。 Your device is pointed %1° %2 and %3° %4. @@ -238,6 +238,14 @@ Disengage to Power Off 請先取消控車才能關機 + + Reset + 重設 + + + Review + 回顧 + DriveStats @@ -273,6 +281,17 @@ 開啟相機中 + + ExperimentalModeButton + + EXPERIMENTAL MODE ON + 實驗模式 ON + + + CHILL MODE ON + 輕鬆模式 ON + + InputDialog @@ -292,18 +311,6 @@ Installing... 安裝中… - - Receiving objects: - 接收對象: - - - Resolving deltas: - 分析差異: - - - Updating files: - 更新檔案: - MapETA @@ -463,6 +470,17 @@ location set 將 connect.comma.ai 加入您的主屏幕,以便像手機 App 一樣使用它 + + ParamControl + + Cancel + 取消 + + + Enable + 啟用 + + PrimeAdWidget @@ -556,18 +574,10 @@ location set Are you sure you want to reset your device? 您確定要重置你的設備嗎? - - Resetting device... - 重置設備中… - System Reset 系統重置 - - System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot. - 系統重置已觸發。請按確認刪除所有內容和設置。按取消恢復啟動。 - Cancel 取消 @@ -581,15 +591,17 @@ location set 確認 - Unable to mount data partition. Press confirm to reset your device. - 無法掛載數據分區。請按確認重置您的設備。 + Unable to mount data partition. Partition may be corrupted. Press confirm to erase and reset your device. + 無法掛載資料分割區 分割區可能已經毀損 請確認是否要刪除並重新設定 - - - RichTextDialog - Ok - 確定 + Press confirm to erase all content and settings. Press cancel to resume boot. + 按下確認以刪除所有內容及設定 按下取消來繼續開機 + + + Resetting device... +This may take up to a minute. + 設備重置中 此過程可能需要幾分鐘 @@ -661,18 +673,6 @@ location set Waiting for internet 連接至網路中 - - Choose Software to Install - 選擇要安裝的軟體 - - - Dashcam - 行車記錄器 - - - Custom Software - 定制的軟體 - Enter URL 輸入網址 @@ -701,6 +701,14 @@ location set Start over 重新開始 + + No custom software found at this URL. + 無法在此URL找到定制的軟體 + + + Something went wrong. Reboot the device. + 發生了一些錯誤 請重新啟動您的設備 + SetupWidget @@ -836,20 +844,24 @@ location set UNINSTALL - 卸載 + 解除安裝 Uninstall %1 - 卸載 %1 + 解除安裝 %1 Are you sure you want to uninstall? - 您確定您要卸載嗎? + 您確定您要解除安裝嗎? CHECK 檢查 + + Uninstall + 解除安裝 + SshControl @@ -951,31 +963,11 @@ location set 上傳駕駛監控的錄像來協助我們提升駕駛監控的準確率。 - 🌮 End-to-end longitudinal (extremely alpha) 🌮 - 🌮 端對端縱向控制(實驗性功能) 🌮 - - - Experimental openpilot longitudinal control + Experimental openpilot Longitudinal Control 使用 openpilot 縱向控制(實驗) - <b>WARNING: openpilot longitudinal control is experimental for this car and will disable AEB.</b> - <b>注意:這台車的 openpilot 縱向控制仍然是實驗中的功能,開啟這功能將會關閉自動緊急煞車 (AEB)。</b> - - - Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would. Super experimental. - 讓駕駛模型直接控製油門和剎車,openpilot將會模仿人類司機的駕駛方式。該功能仍非常實驗性。 - - - openpilot longitudinal control is not currently available for this car. - openpilot 縱向控制目前不適用於這輛車。 - - - Enable experimental longitudinal control to enable this. - 打開縱向控制(實驗)以啟用此功能。 - - - Disengage On Accelerator Pedal + Disengage on Accelerator Pedal 油門取消控車 @@ -998,6 +990,54 @@ location set Show map on left side when in split screen view. 進入分割畫面後,地圖將會顯示在畫面的左側。 + + Experimental Mode + 實驗模式 + + + WARNING: openpilot longitudinal control is experimental for this car and will disable Automatic Emergency Braking (AEB). + 警告:openpilot 縱向控制在這輛車上仍屬實驗性質,啟用後會喪失自動緊急煞車 (AEB) 功能。 + + + On this car, openpilot defaults to the car's built-in ACC instead of openpilot's longitudinal control. Enable this to switch to openpilot longitudinal control. Enabling Experimental mode is recommended when using experimental openpilot longitudinal control. + 在本車輛中,openpilot預設將使用原車內建的ACC系統,而非openpilot縱向控制。開啟此開關來啟用openpilot縱向控制,使用此選項時建議一併啟用實驗模式。 + + + openpilot defaults to driving in <b>chill mode</b>. Experimental mode enables <b>alpha-level features</b> that aren't ready for chill mode. Experimental features are listed below: + openpilot 預設以 <b>輕鬆模式</b> 駕駛。 實驗模式啟用了尚未準備好進入輕鬆模式的 <b>alpha 級功能</b>。實驗功能如下: + + + 🌮 End-to-End Longitudinal Control 🌮 + 🌮端到端縱向控制🌮 + + + Let the driving model control the gas and brakes. openpilot will drive as it thinks a human would, including stopping for red lights and stop signs. Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; mistakes should be expected. + 讓駕駛模型來控制油門及煞車。openpilot將會模擬人類的駕駛行為,包含在看見紅燈及停止標示時停車。由於車速將由駕駛模型決定,因此您設定的時速將成為速度上限。本功能仍在早期實驗階段,請預期模型有犯錯的可能性。 + + + New Driving Visualization + 新的駕駛視覺介面 + + + The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. The Experimental mode logo will also be shown in the top right corner. + 低速行駛時,將會切換成路側廣角鏡頭,以完整顯示轉彎路徑,右上角將出現實驗模式圖案。 + + + Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control. + 因車輛使用內建ACC系統,無法在本車輛上啟動實驗模式。 + + + openpilot longitudinal control may come in a future update. + 未來可能會推出openpilot縱向控制 + + + An experimental version of openpilot longitudinal control can be tested, along with Experimental mode, on non-release branches. + 在非發行分支中 可找到包含實驗模式的openpilot縱向控制測試版本 + + + Enable experimental longitudinal control to allow Experimental mode. + 啟用實驗性縱向控制以使用實驗模式。 + Updater @@ -1052,5 +1092,9 @@ location set Forget Wi-Fi Network "%1"? 清除 Wi-Fi 網路 "%1"? + + Forget + 清除 + diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 152c7fbfcd..50ffc84f4f 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -23,8 +23,8 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, const QRectF clip_region{-margin, -margin, s->fb_w + 2 * margin, s->fb_h + 2 * margin}; const vec3 pt = (vec3){{in_x, in_y, in_z}}; - const vec3 Ep = matvecmul3(s->scene.view_from_calib, pt); - const vec3 KEp = matvecmul3(s->wide_camera ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep); + const vec3 Ep = matvecmul3(s->scene.wide_cam ? s->scene.view_from_wide_calib : s->scene.view_from_calib, pt); + const vec3 KEp = matvecmul3(s->scene.wide_cam ? ecam_intrinsic_matrix : fcam_intrinsic_matrix, Ep); // Project. QPointF point = s->car_space_transform.map(QPointF{KEp.v[0] / KEp.v[2], KEp.v[1] / KEp.v[2]}); @@ -35,7 +35,7 @@ static bool calib_frame_to_full_frame(const UIState *s, float in_x, float in_y, return false; } -static int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line, const float path_height) { +int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height) { const auto line_x = line.getX(); int max_idx = 0; for (int i = 1; i < TRAJECTORY_SIZE && line_x[i] <= path_height; ++i) { @@ -44,7 +44,7 @@ static int get_path_length_idx(const cereal::ModelDataV2::XYZTData::Reader &line return max_idx; } -static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::ModelDataV2::XYZTData::Reader &line) { +void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::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()) { @@ -54,15 +54,16 @@ static void update_leads(UIState *s, const cereal::RadarState::Reader &radar_sta } } -static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTData::Reader &line, - float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert=true) { const auto line_x = line.getX(), line_y = line.getY(), line_z = line.getZ(); - QPolygonF left_points, right_points; left_points.reserve(max_idx + 1); right_points.reserve(max_idx + 1); for (int i = 0; i <= max_idx; i++) { + // highly negative x positions are drawn above the frame and cause flickering, clip to zy plane of camera + if (line_x[i] < 0) continue; QPointF left, right; bool l = calib_frame_to_full_frame(s, line_x[i], line_y[i] - y_off, line_z[i] + z_off, &left); bool r = calib_frame_to_full_frame(s, line_x[i], line_y[i] + y_off, line_z[i] + z_off, &right); @@ -78,10 +79,15 @@ static void update_line_data(const UIState *s, const cereal::ModelDataV2::XYZTDa *pvd = left_points + right_points; } -static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { +void update_model(UIState *s, + const cereal::ModelDataV2::Reader &model, + const cereal::UiPlan::Reader &plan) { UIScene &scene = s->scene; - auto model_position = model.getPosition(); - float max_distance = std::clamp(model_position.getX()[TRAJECTORY_SIZE - 1], + auto plan_position = plan.getPosition(); + if (plan_position.getX().size() < TRAJECTORY_SIZE){ + plan_position = model.getPosition(); + } + float max_distance = std::clamp(plan_position.getX()[TRAJECTORY_SIZE - 1], MIN_DRAW_DISTANCE, MAX_DRAW_DISTANCE); // update lane lines @@ -107,8 +113,41 @@ static void update_model(UIState *s, const cereal::ModelDataV2::Reader &model) { 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); - update_line_data(s, model_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); + max_idx = get_path_length_idx(plan_position, max_distance); + update_line_data(s, plan_position, 0.9, 1.22, &scene.track_vertices, max_idx, false); +} + +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd) { + UIScene &scene = s->scene; + const auto driver_orient = is_rhd ? driverstate.getRightDriverData().getFaceOrientation() : driverstate.getLeftDriverData().getFaceOrientation(); + for (int i = 0; i < std::size(scene.driver_pose_vals); i++) { + float v_this = (i == 0 ? (driver_orient[i] < 0 ? 0.7 : 0.9) : 0.4) * driver_orient[i]; + scene.driver_pose_diff[i] = fabs(scene.driver_pose_vals[i] - v_this); + scene.driver_pose_vals[i] = 0.8 * v_this + (1 - 0.8) * scene.driver_pose_vals[i]; + scene.driver_pose_sins[i] = sinf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + scene.driver_pose_coss[i] = cosf(scene.driver_pose_vals[i]*(1.0-dm_fade_state)); + } + + const mat3 r_xyz = (mat3){{ + scene.driver_pose_coss[1]*scene.driver_pose_coss[2], + scene.driver_pose_coss[1]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[1], + + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_coss[0]*scene.driver_pose_sins[2], + -scene.driver_pose_sins[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_coss[0]*scene.driver_pose_coss[2], + -scene.driver_pose_sins[0]*scene.driver_pose_coss[1], + + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_coss[2] - scene.driver_pose_sins[0]*scene.driver_pose_sins[2], + scene.driver_pose_coss[0]*scene.driver_pose_sins[1]*scene.driver_pose_sins[2] + scene.driver_pose_sins[0]*scene.driver_pose_coss[2], + scene.driver_pose_coss[0]*scene.driver_pose_coss[1], + }}; + + // transform vertices + for (int kpi = 0; kpi < std::size(default_face_kpts_3d); kpi++) { + vec3 kpt_this = default_face_kpts_3d[kpi]; + kpt_this = matvecmul3(r_xyz, kpt_this); + scene.face_kpts_draw[kpi] = (vec3){{(float)kpt_this.v[0], (float)kpt_this.v[1], (float)(kpt_this.v[2] * (1.0-dm_fade_state) + 8 * dm_fade_state)}}; + } } static void update_sockets(UIState *s) { @@ -121,28 +160,27 @@ static void update_state(UIState *s) { if (sm.updated("liveCalibration")) { auto rpy_list = sm["liveCalibration"].getLiveCalibration().getRpyCalib(); + auto wfde_list = sm["liveCalibration"].getLiveCalibration().getWideFromDeviceEuler(); Eigen::Vector3d rpy; - rpy << rpy_list[0], rpy_list[1], rpy_list[2]; + Eigen::Vector3d wfde; + if (rpy_list.size() == 3) rpy << rpy_list[0], rpy_list[1], rpy_list[2]; + if (wfde_list.size() == 3) wfde << wfde_list[0], wfde_list[1], wfde_list[2]; Eigen::Matrix3d device_from_calib = euler2rot(rpy); + Eigen::Matrix3d wide_from_device = euler2rot(wfde); Eigen::Matrix3d view_from_device; view_from_device << 0,1,0, 0,0,1, 1,0,0; Eigen::Matrix3d view_from_calib = view_from_device * device_from_calib; + Eigen::Matrix3d view_from_wide_calib = view_from_device * wide_from_device * device_from_calib ; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { scene.view_from_calib.v[i*3 + j] = view_from_calib(i,j); + scene.view_from_wide_calib.v[i*3 + j] = view_from_wide_calib(i,j); } } scene.calibration_valid = sm["liveCalibration"].getLiveCalibration().getCalStatus() == 1; - } - if (s->worldObjectsVisible()) { - if (sm.updated("modelV2")) { - update_model(s, sm["modelV2"].getModelV2()); - } - if (sm.updated("radarState") && sm.rcv_frame("modelV2") > s->scene.started_frame) { - update_leads(s, sm["radarState"].getRadarState(), sm["modelV2"].getModelV2().getPosition()); - } + scene.calibration_wide_valid = wfde_list.size() == 3; } if (sm.updated("pandaStates")) { auto pandaStates = sm["pandaStates"].getPandaStates(); @@ -163,7 +201,8 @@ static void update_state(UIState *s) { scene.longitudinal_control = sm["carParams"].getCarParams().getOpenpilotLongitudinalControl(); } if (sm.updated("wideRoadCameraState")) { - scene.light_sensor = 100.0f - sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(); + float scale = (sm["wideRoadCameraState"].getWideRoadCameraState().getSensor() == cereal::FrameData::ImageSensor::AR0231) ? 6.0f : 1.0f; + scene.light_sensor = std::max(100.0f - scale * sm["wideRoadCameraState"].getWideRoadCameraState().getExposureValPercent(), 0.0f); } scene.started = sm["deviceState"].getDeviceState().getStarted() && scene.ignition; } @@ -172,7 +211,6 @@ void ui_update_params(UIState *s) { auto params = Params(); s->scene.is_metric = params.getBool("IsMetric"); s->scene.map_on_left = params.getBool("NavSettingLeftSide"); - s->scene.end_to_end_long = params.getBool("EndToEndLong"); } void UIState::updateStatus() { @@ -196,7 +234,6 @@ void UIState::updateStatus() { if (scene.started) { status = STATUS_DISENGAGED; scene.started_frame = sm->frame; - wide_camera = Params().getBool("WideCameraOnly"); } started_prev = scene.started; emit offroadTransition(!scene.started); @@ -213,12 +250,11 @@ void UIState::updateStatus() { UIState::UIState(QObject *parent) : QObject(parent) { sm = std::make_unique>({ "modelV2", "controlsState", "liveCalibration", "radarState", "deviceState", "roadCameraState", - "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", - "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "gnssMeasurements", + "pandaStates", "carParams", "driverMonitoringState", "carState", "liveLocationKalman", "driverStateV2", + "wideRoadCameraState", "managerState", "navInstruction", "navRoute", "uiPlan", }); Params params; - wide_camera = params.getBool("WideCameraOnly"); prime_type = std::atoi(params.get("PrimeType").c_str()); language = QString::fromStdString(params.get("LanguageSetting")); diff --git a/selfdrive/ui/ui.h b/selfdrive/ui/ui.h index f60c26b59a..4c4bd462e7 100644 --- a/selfdrive/ui/ui.h +++ b/selfdrive/ui/ui.h @@ -20,11 +20,21 @@ const int bdr_s = 30; const int header_h = 420; const int footer_h = 280; -const int UI_FREQ = 20; // Hz +const int UI_FREQ = 20; // Hz typedef cereal::CarControl::HUDControl::AudibleAlert AudibleAlert; const mat3 DEFAULT_CALIBRATION = {{ 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0 }}; +const vec3 default_face_kpts_3d[] = { + {-5.98, -51.20, 8.00}, {-17.64, -49.14, 8.00}, {-23.81, -46.40, 8.00}, {-29.98, -40.91, 8.00}, {-32.04, -37.49, 8.00}, + {-34.10, -32.00, 8.00}, {-36.16, -21.03, 8.00}, {-36.16, 6.40, 8.00}, {-35.47, 10.51, 8.00}, {-32.73, 19.43, 8.00}, + {-29.30, 26.29, 8.00}, {-24.50, 33.83, 8.00}, {-19.01, 41.37, 8.00}, {-14.21, 46.17, 8.00}, {-12.16, 47.54, 8.00}, + {-4.61, 49.60, 8.00}, {4.99, 49.60, 8.00}, {12.53, 47.54, 8.00}, {14.59, 46.17, 8.00}, {19.39, 41.37, 8.00}, + {24.87, 33.83, 8.00}, {29.67, 26.29, 8.00}, {33.10, 19.43, 8.00}, {35.84, 10.51, 8.00}, {36.53, 6.40, 8.00}, + {36.53, -21.03, 8.00}, {34.47, -32.00, 8.00}, {32.42, -37.49, 8.00}, {30.36, -40.91, 8.00}, {24.19, -46.40, 8.00}, + {18.02, -49.14, 8.00}, {6.36, -51.20, 8.00}, {-5.98, -51.20, 8.00}, +}; + struct Alert { QString text1; QString text2; @@ -78,7 +88,7 @@ typedef enum UIStatus { } UIStatus; const QColor bg_colors [] = { - [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), + [STATUS_DISENGAGED] = QColor(0x17, 0x33, 0x49, 0xc8), [STATUS_OVERRIDE] = QColor(0x91, 0x9b, 0x95, 0xf1), [STATUS_ENGAGED] = QColor(0x17, 0x86, 0x44, 0xf1), [STATUS_WARNING] = QColor(0xDA, 0x6F, 0x25, 0xf1), @@ -87,7 +97,10 @@ const QColor bg_colors [] = { typedef struct UIScene { bool calibration_valid = false; + bool calibration_wide_valid = false; + bool wide_cam = true; mat3 view_from_calib = DEFAULT_CALIBRATION; + mat3 view_from_wide_calib = DEFAULT_CALIBRATION; cereal::PandaState::PandaType pandaType; // modelV2 @@ -100,8 +113,15 @@ typedef struct UIScene { // lead QPointF lead_vertices[2]; + // DMoji state + float driver_pose_vals[3]; + float driver_pose_diff[3]; + float driver_pose_sins[3]; + float driver_pose_coss[3]; + vec3 face_kpts_draw[std::size(default_face_kpts_3d)]; + float light_sensor; - bool started, ignition, is_metric, map_on_left, longitudinal_control, end_to_end_long; + bool started, ignition, is_metric, map_on_left, longitudinal_control; uint64_t started_frame; } UIScene; @@ -130,7 +150,6 @@ public: QString language; QTransform car_space_transform; - bool wide_camera; signals: void uiUpdate(const UIState &s); @@ -149,7 +168,6 @@ private: UIState *uiState(); // device management class - class Device : public QObject { Q_OBJECT @@ -179,3 +197,11 @@ public slots: }; void ui_update_params(UIState *s); +int get_path_length_idx(const cereal::XYZTData::Reader &line, const float path_height); +void update_model(UIState *s, + const cereal::ModelDataV2::Reader &model, + const cereal::UiPlan::Reader &plan); +void update_dmonitoring(UIState *s, const cereal::DriverStateV2::Reader &driverstate, float dm_fade_state, bool is_rhd); +void update_leads(UIState *s, const cereal::RadarState::Reader &radar_state, const cereal::XYZTData::Reader &line); +void update_line_data(const UIState *s, const cereal::XYZTData::Reader &line, + float y_off, float z_off, QPolygonF *pvd, int max_idx, bool allow_invert); diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e15d4c3433..5964eb49dc 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -19,7 +19,7 @@ def update_translations(vanish=False, plural_only=None, translations_dir=TRANSLA for file in translation_files.values(): tr_file = os.path.join(translations_dir, f"{file}.ts") - args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file}" + args = f"lupdate -locations none -recursive {UI_DIR} -ts {tr_file} -I {BASEDIR}" if vanish: args += " -no-obsolete" if file in plural_only: diff --git a/selfdrive/updated.py b/selfdrive/updated.py index 57f957cfff..2cb7d1c13b 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -175,7 +175,7 @@ def finalize_update() -> None: shutil.copytree(OVERLAY_MERGED, FINALIZED, symlinks=True) run(["git", "reset", "--hard"], FINALIZED) - run(["git", "submodule", "foreach", "--recursive", "git", "reset"], FINALIZED) + run(["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"], FINALIZED) cloudlog.info("Starting git cleanup in finalized update") t = time.monotonic() @@ -280,17 +280,25 @@ class Updater: # Write out current and new version info def get_description(basedir: str) -> str: + if not os.path.exists(basedir): + return "" + version = "" branch = "" commit = "" + commit_date = "" try: branch = self.get_branch(basedir) - commit = self.get_commit_hash(basedir) + commit = self.get_commit_hash(basedir)[:7] with open(os.path.join(basedir, "common", "version.h")) as f: version = f.read().split('"')[1] + + commit_unix_ts = run(["git", "show", "-s", "--format=%ct", "HEAD"], basedir).rstrip() + dt = datetime.datetime.fromtimestamp(int(commit_unix_ts)) + commit_date = dt.strftime("%b %d") except Exception: - pass - return f"{version} / {branch} / {commit[:7]}" + cloudlog.exception("updater.get_description") + return f"{version} / {branch} / {commit} / {commit_date}" self.params.put("UpdaterCurrentDescription", get_description(BASEDIR)) self.params.put("UpdaterCurrentReleaseNotes", parse_release_notes(BASEDIR)) self.params.put("UpdaterNewDescription", get_description(FINALIZED)) @@ -366,8 +374,8 @@ class Updater: ["git", "reset", "--hard"], ["git", "clean", "-xdff"], ["git", "submodule", "sync"], - ["git", "submodule", "init"], - ["git", "submodule", "update"], + ["git", "submodule", "update", "--init", "--recursive"], + ["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"], ] r = [run(cmd, OVERLAY_MERGED) for cmd in cmds] cloudlog.info("git reset success: %s", '\n'.join(r)) @@ -415,6 +423,9 @@ def main() -> None: wait_helper = WaitTimeHelper() wait_helper.only_check_for_update = True + # invalidate old finalized update + set_consistent_flag(False) + # Run the update loop while True: wait_helper.ready_event.clear() diff --git a/system/camerad/SConscript b/system/camerad/SConscript index ddc763b53d..3ecc3f6d72 100644 --- a/system/camerad/SConscript +++ b/system/camerad/SConscript @@ -2,17 +2,13 @@ Import('env', 'arch', 'cereal', 'messaging', 'common', 'gpucommon', 'visionipc') libs = ['m', 'pthread', common, 'jpeg', 'OpenCL', 'yuv', cereal, messaging, 'zmq', 'capnp', 'kj', visionipc, gpucommon, 'atomic'] -cenv = env.Clone() -cenv['CPPPATH'].append('include/') - -camera_obj = cenv.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) -cenv.Program('camerad', [ +camera_obj = env.Object(['cameras/camera_qcom2.cc', 'cameras/camera_common.cc', 'cameras/camera_util.cc']) +env.Program('camerad', [ 'main.cc', camera_obj, ], LIBS=libs) if GetOption("test") and arch == "x86_64": - cenv.Program('test/ae_gray_test', [ - 'test/ae_gray_test.cc', - camera_obj, - ], LIBS=libs) + env.Program('test/ae_gray_test', + ['test/ae_gray_test.cc', camera_obj], + LIBS=libs) diff --git a/system/camerad/cameras/camera_common.cc b/system/camerad/cameras/camera_common.cc index 30e2810ec4..9ef5d89a4e 100644 --- a/system/camerad/cameras/camera_common.cc +++ b/system/camerad/cameras/camera_common.cc @@ -157,9 +157,6 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setHighConversionGain(frame_data.high_conversion_gain); framed.setMeasuredGreyFraction(frame_data.measured_grey_fraction); framed.setTargetGreyFraction(frame_data.target_grey_fraction); - framed.setLensPos(frame_data.lens_pos); - framed.setLensErr(frame_data.lens_err); - framed.setLensTruePos(frame_data.lens_true_pos); framed.setProcessingTime(frame_data.processing_time); const float ev = c->cur_ev[frame_data.frame_id % 3]; @@ -167,7 +164,7 @@ void fill_frame_data(cereal::FrameData::Builder &framed, const FrameMetadata &fr framed.setExposureValPercent(perc); if (c->camera_id == CAMERA_ID_AR0231) { - framed.setSensor(cereal::FrameData::ImageSensor::AR0321); + framed.setSensor(cereal::FrameData::ImageSensor::AR0231); } else if (c->camera_id == CAMERA_ID_OX03C10) { framed.setSensor(cereal::FrameData::ImageSensor::OX03C10); } diff --git a/system/camerad/cameras/camera_common.h b/system/camerad/cameras/camera_common.h index 088b9f7939..07d1291a2a 100644 --- a/system/camerad/cameras/camera_common.h +++ b/system/camerad/cameras/camera_common.h @@ -66,11 +66,6 @@ typedef struct FrameMetadata { float measured_grey_fraction; float target_grey_fraction; - // Focus - unsigned int lens_pos; - float lens_err; - float lens_true_pos; - float processing_time; } FrameMetadata; diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index 78848af5be..94116b11cd 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -60,15 +60,15 @@ CameraInfo cameras_supported[CAMERA_ID_MAX] = { const float DC_GAIN_AR0231 = 2.5; const float DC_GAIN_OX03C10 = 7.32; -const float DC_GAIN_ON_GREY_AR0231= 0.2; +const float DC_GAIN_ON_GREY_AR0231 = 0.2; const float DC_GAIN_OFF_GREY_AR0231 = 0.3; -const float DC_GAIN_ON_GREY_OX03C10= 0.25; -const float DC_GAIN_OFF_GREY_OX03C10 = 0.35; +const float DC_GAIN_ON_GREY_OX03C10 = 0.9; +const float DC_GAIN_OFF_GREY_OX03C10 = 1.0; const int DC_GAIN_MIN_WEIGHT_AR0231 = 0; const int DC_GAIN_MAX_WEIGHT_AR0231 = 1; -const int DC_GAIN_MIN_WEIGHT_OX03C10 = 16; -const int DC_GAIN_MAX_WEIGHT_OX03C10 = 32; +const int DC_GAIN_MIN_WEIGHT_OX03C10 = 1; // always on is fine +const int DC_GAIN_MAX_WEIGHT_OX03C10 = 1; const float TARGET_GREY_FACTOR_AR0231 = 1.0; const float TARGET_GREY_FACTOR_OX03C10 = 0.02; @@ -80,30 +80,32 @@ const float sensor_analog_gains_AR0231[] = { 7.0/2.0, 8.0/2.0, 8.0/1.0}; // 12, 13, 14, 15 = bypass const float sensor_analog_gains_OX03C10[] = { - 1.0, 1.125, 1.25, 1.3125, 1.5625, - 1.6875, 2.0, 2.25, 2.625, 3.125, - 3.625, 4.0, 4.5, 5.0, 5.5, - 6.0, 6.5, 7.0, 7.5, 8.0, - 8.5, 9.0, 9.5, 10.0, 10.5, - 11.0, 11.5, 12.0, 12.5, 13.0, - 13.5, 14.0, 14.5, 15.0, 15.5}; + 1.0, 1.0625, 1.125, 1.1875, 1.25, 1.3125, 1.375, 1.4375, 1.5, 1.5625, 1.6875, + 1.8125, 1.9375, 2.0, 2.125, 2.25, 2.375, 2.5, 2.625, 2.75, 2.875, 3.0, + 3.125, 3.375, 3.625, 3.875, 4.0, 4.25, 4.5, 4.75, 5.0, 5.25, 5.5, + 5.75, 6.0, 6.25, 6.5, 7.0, 7.5, 8.0, 8.5, 9.0, 9.5, 10.0, + 10.5, 11.0, 11.5, 12.0, 12.5, 13.0, 13.5, 14.0, 14.5, 15.0, 15.5}; const uint32_t ox03c10_analog_gains_reg[] = { - 0x100, 0x120, 0x140, 0x150, 0x190, - 0x1B0, 0x200, 0x240, 0x2A0, 0x320, - 0x3A0, 0x400, 0x480, 0x500, 0x580, - 0x600, 0x680, 0x700, 0x780, 0x800, - 0x880, 0x900, 0x980, 0xA00, 0xA80, - 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, - 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; + 0x100, 0x110, 0x120, 0x130, 0x140, 0x150, 0x160, 0x170, 0x180, 0x190, 0x1B0, + 0x1D0, 0x1F0, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2A0, 0x2C0, 0x2E0, 0x300, + 0x320, 0x360, 0x3A0, 0x3E0, 0x400, 0x440, 0x480, 0x4C0, 0x500, 0x540, 0x580, + 0x5C0, 0x600, 0x640, 0x680, 0x700, 0x780, 0x800, 0x880, 0x900, 0x980, 0xA00, + 0xA80, 0xB00, 0xB80, 0xC00, 0xC80, 0xD00, 0xD80, 0xE00, 0xE80, 0xF00, 0xF80}; const int ANALOG_GAIN_MIN_IDX_AR0231 = 0x1; // 0.25x const int ANALOG_GAIN_REC_IDX_AR0231 = 0x6; // 0.8x const int ANALOG_GAIN_MAX_IDX_AR0231 = 0xD; // 4.0x +const int ANALOG_GAIN_COST_DELTA_AR0231 = 0; +const float ANALOG_GAIN_COST_LOW_AR0231 = 0.1; +const float ANALOG_GAIN_COST_HIGH_AR0231 = 5.0; const int ANALOG_GAIN_MIN_IDX_OX03C10 = 0x0; -const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x6; // 2x -const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x22; +const int ANALOG_GAIN_REC_IDX_OX03C10 = 0x11; // 2.5x +const int ANALOG_GAIN_MAX_IDX_OX03C10 = 0x36; +const int ANALOG_GAIN_COST_DELTA_OX03C10 = -1; +const float ANALOG_GAIN_COST_LOW_OX03C10 = 0.4; +const float ANALOG_GAIN_COST_HIGH_OX03C10 = 6.4; const int EXPOSURE_TIME_MIN_AR0231 = 2; // with HDR, fastest ss const int EXPOSURE_TIME_MAX_AR0231 = 0x0855; // with HDR, slowest ss, 40ms @@ -536,6 +538,9 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_AR0231; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_AR0231; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_AR0231; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_AR0231; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_AR0231; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_AR0231; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_AR0231[i]; } @@ -552,6 +557,9 @@ void CameraState::camera_set_parameters() { analog_gain_min_idx = ANALOG_GAIN_MIN_IDX_OX03C10; analog_gain_rec_idx = ANALOG_GAIN_REC_IDX_OX03C10; analog_gain_max_idx = ANALOG_GAIN_MAX_IDX_OX03C10; + analog_gain_cost_delta = ANALOG_GAIN_COST_DELTA_OX03C10; + analog_gain_cost_low = ANALOG_GAIN_COST_LOW_OX03C10; + analog_gain_cost_high = ANALOG_GAIN_COST_HIGH_OX03C10; for (int i=0; i<=analog_gain_max_idx; i++) { sensor_analog_gains[i] = sensor_analog_gains_OX03C10[i]; } @@ -1033,6 +1041,30 @@ void CameraState::handle_camera_event(void *evdat) { } } +void CameraState::update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain) { + float score = 1e6; + if (camera_id == CAMERA_ID_AR0231) { + // Cost of ev diff + score = std::abs(desired_ev - (exp_t * exp_gain)) * 10; + // Cost of absolute gain + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + // Cost of changing gain + score += std::abs(exp_g_idx - gain_idx) * (score + 1.0) / 10.0; + } else if (camera_id == CAMERA_ID_OX03C10) { + score = std::abs(desired_ev - (exp_t * exp_gain)); + float m = exp_g_idx > analog_gain_rec_idx ? analog_gain_cost_high : analog_gain_cost_low; + score += std::abs(exp_g_idx - (int)analog_gain_rec_idx) * m; + score += ((1 - analog_gain_cost_delta) + analog_gain_cost_delta * (exp_g_idx - analog_gain_min_idx) / (analog_gain_max_idx - analog_gain_min_idx)) * std::abs(exp_g_idx - gain_idx) * 5.0; + } + + if (score < best_ev_score) { + new_exp_t = exp_t; + new_exp_g = exp_g_idx; + best_ev_score = score; + } +} + void CameraState::set_camera_exposure(float grey_frac) { if (!enabled) return; const float dt = 0.05; @@ -1058,9 +1090,9 @@ void CameraState::set_camera_exposure(float grey_frac) { float k = (1.0 - k_ev) / 3.0; desired_ev = (k * cur_ev[0]) + (k * cur_ev[1]) + (k * cur_ev[2]) + (k_ev * desired_ev); - float best_ev_score = 1e6; - int new_g = 0; - int new_t = 0; + best_ev_score = 1e6; + new_exp_g = 0; + new_exp_t = 0; // Hysteresis around high conversion gain // We usually want this on since it results in lower noise, but turn off in very bright day scenes @@ -1087,8 +1119,8 @@ void CameraState::set_camera_exposure(float grey_frac) { gain_idx = std::stoi(gain_bytes); exposure_time = std::stoi(time_bytes); - new_g = gain_idx; - new_t = exposure_time; + new_exp_g = gain_idx; + new_exp_t = exposure_time; enable_dc_gain = false; } else { // Simple brute force optimizer to choose sensor parameters @@ -1104,23 +1136,7 @@ void CameraState::set_camera_exposure(float grey_frac) { continue; } - // Compute error to desired ev - float score = std::abs(desired_ev - (t * gain)) * 10; - - // Going below recommended gain needs lower penalty to not overexpose - float m = g > analog_gain_rec_idx ? 5.0 : 0.1; - score += std::abs(g - (int)analog_gain_rec_idx) * m; - - // LOGE("cam: %d - gain: %d, t: %d (%.2f), score %.2f, score + gain %.2f, %.3f, %.3f", camera_num, g, t, desired_ev / gain, score, score + std::abs(g - gain_idx) * (score + 1.0) / 10.0, desired_ev, min_ev); - - // Small penalty on changing gain - score += std::abs(g - gain_idx) * (score + 1.0) / 10.0; - - if (score < best_ev_score) { - new_t = t; - new_g = g; - best_ev_score = score; - } + update_exposure_score(desired_ev, t, g, gain); } } @@ -1129,9 +1145,9 @@ void CameraState::set_camera_exposure(float grey_frac) { measured_grey_fraction = grey_frac; target_grey_fraction = target_grey; - analog_gain_frac = sensor_analog_gains[new_g]; - gain_idx = new_g; - exposure_time = new_t; + analog_gain_frac = sensor_analog_gains[new_exp_g]; + gain_idx = new_exp_g; + exposure_time = new_exp_t; dc_gain_enabled = enable_dc_gain; float gain = analog_gain_frac * (1 + dc_gain_weight * (dc_gain_factor-1) / dc_gain_max_weight); @@ -1148,7 +1164,7 @@ void CameraState::set_camera_exposure(float grey_frac) { // LOGE("ae - camera %d, cur_t %.5f, sof %.5f, dt %.5f", camera_num, 1e-9 * nanos_since_boot(), 1e-9 * buf.cur_frame_data.timestamp_sof, 1e-9 * (nanos_since_boot() - buf.cur_frame_data.timestamp_sof)); if (camera_id == CAMERA_ID_AR0231) { - uint16_t analog_gain_reg = 0xFF00 | (new_g << 4) | new_g; + uint16_t analog_gain_reg = 0xFF00 | (new_exp_g << 4) | new_exp_g; struct i2c_random_wr_payload exp_reg_array[] = { {0x3366, analog_gain_reg}, {0x3362, (uint16_t)(dc_gain_enabled ? 0x1 : 0x0)}, @@ -1156,15 +1172,16 @@ void CameraState::set_camera_exposure(float grey_frac) { }; sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, true); } else if (camera_id == CAMERA_ID_OX03C10) { - // t_HCG + t_LCG + t_VS on LPD, t_SPD on SPD - uint32_t hcg_time = std::max((dc_gain_weight * exposure_time / dc_gain_max_weight), 0); - uint32_t lcg_time = std::max(((dc_gain_max_weight - dc_gain_weight) * exposure_time / dc_gain_max_weight), 0); - // uint32_t spd_time = std::max(hcg_time / 16, (uint32_t)exposure_time_min); - uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 128, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - uint32_t spd_time = vs_time; + // t_HCG&t_LCG + t_VS on LPD, t_SPD on SPD + uint32_t hcg_time = exposure_time; + uint32_t lcg_time = hcg_time; + uint32_t spd_time = std::min(std::max((uint32_t)exposure_time, (exposure_time_max + VS_TIME_MAX_OX03C10) / 3), exposure_time_max + VS_TIME_MAX_OX03C10); + uint32_t vs_time = std::min(std::max((uint32_t)exposure_time / 40, VS_TIME_MIN_OX03C10), VS_TIME_MAX_OX03C10); - uint32_t real_gain = ox03c10_analog_gains_reg[new_g]; + uint32_t real_gain = ox03c10_analog_gains_reg[new_exp_g]; uint32_t min_gain = ox03c10_analog_gains_reg[0]; + uint32_t spd_gain = 0xF00; + struct i2c_random_wr_payload exp_reg_array[] = { {0x3501, hcg_time>>8}, {0x3502, hcg_time&0xFF}, {0x3581, lcg_time>>8}, {0x3582, lcg_time&0xFF}, @@ -1173,7 +1190,7 @@ void CameraState::set_camera_exposure(float grey_frac) { {0x3508, real_gain>>8}, {0x3509, real_gain&0xFF}, {0x3588, min_gain>>8}, {0x3589, min_gain&0xFF}, - {0x3548, min_gain>>8}, {0x3549, min_gain&0xFF}, + {0x3548, spd_gain>>8}, {0x3549, spd_gain&0xFF}, {0x35c8, min_gain>>8}, {0x35c9, min_gain&0xFF}, }; sensors_i2c(exp_reg_array, sizeof(exp_reg_array)/sizeof(struct i2c_random_wr_payload), CAM_SENSOR_PACKET_OPCODE_SENSOR_CONFIG, false); diff --git a/system/camerad/cameras/camera_qcom2.h b/system/camerad/cameras/camera_qcom2.h index 5023c82458..9e0109ab20 100644 --- a/system/camerad/cameras/camera_qcom2.h +++ b/system/camerad/cameras/camera_qcom2.h @@ -12,6 +12,7 @@ #include "common/util.h" #define FRAME_BUF_COUNT 4 +#define ANALOG_GAIN_MAX_CNT 55 class CameraState { public: @@ -36,13 +37,19 @@ public: float dc_gain_on_grey; float dc_gain_off_grey; - float sensor_analog_gains[35]; + float sensor_analog_gains[ANALOG_GAIN_MAX_CNT]; int analog_gain_min_idx; int analog_gain_max_idx; int analog_gain_rec_idx; + int analog_gain_cost_delta; + float analog_gain_cost_low; + float analog_gain_cost_high; float cur_ev[3]; float min_ev, max_ev; + float best_ev_score; + int new_exp_g; + int new_exp_t; float measured_grey_fraction; float target_grey_fraction; @@ -54,6 +61,7 @@ public: int camera_num; void handle_camera_event(void *evdat); + void update_exposure_score(float desired_ev, int exp_t, int exp_g_idx, float exp_gain); void set_camera_exposure(float grey_frac); void sensors_start(); diff --git a/system/camerad/cameras/real_debayer.cl b/system/camerad/cameras/real_debayer.cl index 59aa488653..cff5ae455b 100644 --- a/system/camerad/cameras/real_debayer.cl +++ b/system/camerad/cameras/real_debayer.cl @@ -72,7 +72,7 @@ float4 val4_from_12(uchar8 pvs, float gain) { float4 pv = {ox03c10_lut[parsed.s0], ox03c10_lut[parsed.s1], ox03c10_lut[parsed.s2], ox03c10_lut[parsed.s3]}; // it's a 24 bit signal, center in the middle 8 bits - return pv*256.0; + return clamp(pv*gain*256.0, 0.0, 1.0); #else // AR // normalize and scale float4 pv = (convert_float4(parsed) - 168.0) / (4096.0 - 168.0); diff --git a/system/camerad/cameras/sensor2_i2c.h b/system/camerad/cameras/sensor2_i2c.h index ab51059d9a..4db4bfd4d8 100644 --- a/system/camerad/cameras/sensor2_i2c.h +++ b/system/camerad/cameras/sensor2_i2c.h @@ -58,7 +58,7 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { // SC ctrl {0x3001, 0x03}, // io_pad_oen - {0x3002, 0xf8}, // io_pad_oen + {0x3002, 0xfc}, // io_pad_oen {0x3005, 0x80}, // io_pad_out {0x3007, 0x01}, // io_pad_sel {0x3008, 0x80}, // io_pad_sel @@ -85,6 +85,9 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3882, 0x8}, {0x3883, 0x0D}, {0x3836, 0x1F}, {0x3837, 0x40}, + {0x3892, 0x44}, + {0x3823, 0x48}, + {0x3012, 0x41}, // SC_PHY_CTRL = 4 lane MIPI {0x3020, 0x05}, // SC_CTRL_20 @@ -126,7 +129,7 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3219, 0x08}, {0x3506, 0x20}, {0x3507, 0x00}, // hcg fine exposure - {0x350a, 0x04}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain + {0x350a, 0x01}, {0x350b, 0x00}, {0x350c, 0x00}, // hcg digital gain {0x3586, 0x40}, {0x3587, 0x00}, // lcg fine exposure {0x358a, 0x01}, {0x358b, 0x00}, {0x358c, 0x00}, // lcg digital gain @@ -179,8 +182,8 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x3820, 0x04}, {0x3821, 0x19}, - {0x3832, 0x00}, - {0x3834, 0x00}, + {0x3832, 0xF0}, + {0x3834, 0xF0}, {0x384c, 0x02}, {0x384d, 0x0d}, {0x3850, 0x00}, @@ -711,11 +714,11 @@ struct i2c_random_wr_payload init_array_ox03c10[] = { {0x4221, 0x03}, // this is changed from 1 -> 3 // DCG exposure coarse - {0x3501, 0x01}, {0x3502, 0xc8}, + // {0x3501, 0x01}, {0x3502, 0xc8}, // SPD exposure coarse - {0x3541, 0x01}, {0x3542, 0xc8}, + // {0x3541, 0x01}, {0x3542, 0xc8}, // VS exposure coarse - {0x35c1, 0x00}, {0x35c2, 0x01}, + // {0x35c1, 0x00}, {0x35c2, 0x01}, // crc reference {0x420e, 0x66}, {0x420f, 0x5d}, {0x4210, 0xa8}, {0x4211, 0x55}, diff --git a/system/camerad/test/test_camerad.py b/system/camerad/test/test_camerad.py index 378d4b7058..f03c531b20 100755 --- a/system/camerad/test/test_camerad.py +++ b/system/camerad/test/test_camerad.py @@ -1,15 +1,20 @@ #!/usr/bin/env python3 import time import unittest +import numpy as np from collections import defaultdict import cereal.messaging as messaging +from cereal import log from cereal.services import service_list from selfdrive.manager.process_config import managed_processes from system.hardware import TICI TEST_TIMESPAN = 30 -LAG_FRAME_TOLERANCE = 0.5 # ms +LAG_FRAME_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 0.5, # ARs use synced pulses for frame starts + log.FrameData.ImageSensor.ox03c10: 1.0} # OXs react to out-of-sync at next frame +FRAME_DELTA_TOLERANCE = {log.FrameData.ImageSensor.ar0231: 1.0, + log.FrameData.ImageSensor.ox03c10: 1.0} CAMERAS = ('roadCameraState', 'driverCameraState', 'wideRoadCameraState') @@ -34,10 +39,16 @@ class TestCamerad(unittest.TestCase): managed_processes['camerad'].stop() cls.log_by_frame_id = defaultdict(list) + cls.sensor_type = None for cam, msgs in cls.logs.items(): + if cls.sensor_type is None: + cls.sensor_type = getattr(msgs[0], msgs[0].which()).sensor.raw expected_frames = service_list[cam].frequency * TEST_TIMESPAN assert expected_frames*0.95 < len(msgs) < expected_frames*1.05, f"unexpected frame count {cam}: {expected_frames=}, got {len(msgs)}" + dts = np.abs(np.diff([getattr(m, m.which()).timestampSof/1e6 for m in msgs]) - 1000/service_list[cam].frequency) + assert (dts < FRAME_DELTA_TOLERANCE[cls.sensor_type]).all(), f"{cam} dts(ms) out of spec: max diff {dts.max()}, 99 percentile {np.percentile(dts, 99)}" + for m in msgs: cls.log_by_frame_id[getattr(m, m.which()).frameId].append(m) @@ -65,12 +76,17 @@ class TestCamerad(unittest.TestCase): frame_times = {frame_id: [getattr(m, m.which()).timestampSof for m in msgs] for frame_id, msgs in self.log_by_frame_id.items()} diffs = {frame_id: (max(ts) - min(ts))/1e6 for frame_id, ts in frame_times.items()} - def get_desc(fid, diff): cam_times = [(m.which(), getattr(m, m.which()).timestampSof/1e6) for m in self.log_by_frame_id[fid]] - return f"{diff=} {cam_times=}" - laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE} - assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" + return (diff, cam_times) + laggy_frames = {k: get_desc(k, v) for k, v in diffs.items() if v > LAG_FRAME_TOLERANCE[self.sensor_type]} + + def in_tol(diff): + return 50 - LAG_FRAME_TOLERANCE[self.sensor_type] < diff and diff < 50 + LAG_FRAME_TOLERANCE[self.sensor_type] + if len(laggy_frames) != 0 and all( in_tol(laggy_frames[lf][0]) for lf in laggy_frames): + print("TODO: handle camera out of sync") + else: + assert len(laggy_frames) == 0, f"Frames not synced properly: {laggy_frames=}" if __name__ == "__main__": unittest.main() diff --git a/system/camerad/test/test_exposure.py b/system/camerad/test/test_exposure.py index 8cce7e7ffa..201b205c4f 100755 --- a/system/camerad/test/test_exposure.py +++ b/system/camerad/test/test_exposure.py @@ -6,16 +6,13 @@ import numpy as np from selfdrive.test.helpers import with_processes from system.camerad.snapshot.snapshot import get_snapshots -from system.hardware import TICI - TEST_TIME = 45 REPEAT = 5 class TestCamerad(unittest.TestCase): @classmethod def setUpClass(cls): - if not TICI: - raise unittest.SkipTest + pass def _numpy_rgb2gray(self, im): ret = np.clip(im[:,:,2] * 0.114 + im[:,:,1] * 0.587 + im[:,:,0] * 0.299, 0, 255).astype(np.uint8) @@ -37,13 +34,11 @@ class TestCamerad(unittest.TestCase): start = time.time() while time.time() - start < TEST_TIME and passed < REPEAT: rpic, dpic = get_snapshots(frame="roadCameraState", front_frame="driverCameraState") + wpic, _ = get_snapshots(frame="wideRoadCameraState") res = self._is_exposure_okay(rpic) res = res and self._is_exposure_okay(dpic) - - if TICI: - wpic, _ = get_snapshots(frame="wideRoadCameraState") - res = res and self._is_exposure_okay(wpic) + res = res and self._is_exposure_okay(wpic) if passed > 0 and not res: passed = -passed # fails test if any failure after first sus diff --git a/system/hardware/base.h b/system/hardware/base.h index b70948d482..6cfc1d8743 100644 --- a/system/hardware/base.h +++ b/system/hardware/base.h @@ -16,10 +16,13 @@ public: static int get_voltage() { return 0; }; static int get_current() { return 0; }; + static std::string get_serial() { return "cccccc"; } + static void reboot() {} static void poweroff() {} static void set_brightness(int percent) {} static void set_display_power(bool on) {} + static void set_volume(float volume) {} static bool get_ssh_enabled() { return false; } static void set_ssh_enabled(bool enabled) {} diff --git a/system/hardware/hw.h b/system/hardware/hw.h index f50e94abe1..5599e79186 100644 --- a/system/hardware/hw.h +++ b/system/hardware/hw.h @@ -7,15 +7,7 @@ #include "system/hardware/tici/hardware.h" #define Hardware HardwareTici #else -class HardwarePC : public HardwareNone { -public: - static std::string get_os_version() { return "openpilot for PC"; } - static std::string get_name() { return "pc"; }; - static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; - static bool PC() { return true; } - static bool TICI() { return util::getenv("TICI", 0) == 1; } - static bool AGNOS() { return util::getenv("TICI", 0) == 1; } -}; +#include "system/hardware/pc/hardware.h" #define Hardware HardwarePC #endif diff --git a/system/hardware/pc/hardware.h b/system/hardware/pc/hardware.h new file mode 100644 index 0000000000..529b4bfe9d --- /dev/null +++ b/system/hardware/pc/hardware.h @@ -0,0 +1,21 @@ +#pragma once + +#include "system/hardware/base.h" + +class HardwarePC : public HardwareNone { +public: + static std::string get_os_version() { return "openpilot for PC"; } + static std::string get_name() { return "pc"; }; + static cereal::InitData::DeviceType get_device_type() { return cereal::InitData::DeviceType::PC; }; + static bool PC() { return true; } + static bool TICI() { return util::getenv("TICI", 0) == 1; } + static bool AGNOS() { return util::getenv("TICI", 0) == 1; } + + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } +}; diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index 8534c8a978..7876b1af1f 100644 --- a/system/hardware/tici/agnos.json +++ b/system/hardware/tici/agnos.json @@ -1,9 +1,9 @@ [ { "name": "boot", - "url": "https://commadist.azureedge.net/agnosupdate/boot-57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136.img.xz", - "hash": "57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136", - "hash_raw": "57626d7737ab2fa1318e8707a202b1295b5da79ad2fa0a36377cc9481ad0d136", + "url": "https://commadist.azureedge.net/agnosupdate/boot-72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a.img.xz", + "hash": "72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a", + "hash_raw": "72662ec5d586c7a22659a1c8b140932d5472914176020fe76ba4204edbbb214a", "size": 14780416, "sparse": false, "full_check": true, @@ -41,9 +41,9 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5.img.xz", - "hash": "6e8fbcc21a265f7f58062abce7675dc05540e2b60cee2df56992a151ba64936f", - "hash_raw": "b40b08912576bb972907acba7c201c1399395cbc0cba06ce6e5e3f70ab565cb5", + "url": "https://commadist.azureedge.net/agnosupdate/system-9efdc9368f05e06008a7a1dbbee21b564e89988dc94d6ddee3a3a88e42268f0e.img.xz", + "hash": "48209ce7e8cc2fff4ec024f0cd82fc2e3e097b5c0629be2b292acf64e6701449", + "hash_raw": "9efdc9368f05e06008a7a1dbbee21b564e89988dc94d6ddee3a3a88e42268f0e", "size": 10737418240, "sparse": true, "full_check": false, diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index a8b2798630..9725cd689c 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python from smbus2 import SMBus from collections import namedtuple @@ -25,13 +24,10 @@ BASE_CONFIG = [ AmpConfig("MCLK prescaler", 0b01, 0x10, 4, 0b00110000), AmpConfig("PM: enable speakers", 0b11, 0x4D, 4, 0b00110000), AmpConfig("PM: enable DACs", 0b11, 0x4D, 0, 0b00000011), - AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), - AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), AmpConfig("Enable PLL1", 0b1, 0x12, 7, 0b10000000), AmpConfig("Enable PLL2", 0b1, 0x1A, 7, 0b10000000), AmpConfig("DAI1: I2S mode", 0b00100, 0x14, 2, 0b01111100), AmpConfig("DAI2: I2S mode", 0b00100, 0x1C, 2, 0b01111100), - AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111), AmpConfig("DAI1 Passband filtering: music mode", 0b1, 0x18, 7, 0b10000000), AmpConfig("DAI1 voice mode gain (DV1G)", 0b00, 0x2F, 4, 0b00110000), AmpConfig("DAI1 attenuation (DV1)", 0x0, 0x2F, 0, 0b00001111), @@ -42,7 +38,6 @@ BASE_CONFIG = [ AmpConfig("ALC/excursion limiter release time", 0b101, 0x43, 4, 0b01110000), AmpConfig("ALC multiband enable", 0b1, 0x43, 3, 0b00001000), AmpConfig("DAI1 EQ enable", 0b0, 0x49, 0, 0b00000001), - AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010), AmpConfig("DAI2 EQ clip detection disabled", 0b1, 0x32, 4, 0b00010000), AmpConfig("DAI2 EQ attenuation", 0x5, 0x32, 0, 0b00001111), AmpConfig("Excursion limiter upper corner freq", 0b100, 0x41, 4, 0b01110000), @@ -63,11 +58,44 @@ BASE_CONFIG = [ AmpConfig("Zero-crossing detection disabled", 0b0, 0x49, 5, 0b00100000), ] -BASE_CONFIG += configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)) -BASE_CONFIG += configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)) -BASE_CONFIG += configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)) -BASE_CONFIG += configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)) -BASE_CONFIG += configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)) +CONFIGS = { + "tici": [ + AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), + AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), + AmpConfig("Right speaker output volume", 0x1c, 0x3E, 0, 0b00011111), + AmpConfig("DAI2 EQ enable", 0b1, 0x49, 1, 0b00000010), + + *configs_from_eq_params(0x84, EQParams(0x274F, 0xC0FF, 0x3BF9, 0x0B3C, 0x1656)), + *configs_from_eq_params(0x8E, EQParams(0x1009, 0xC6BF, 0x2952, 0x1C97, 0x30DF)), + *configs_from_eq_params(0x98, EQParams(0x0F75, 0xCBE5, 0x0ED2, 0x2528, 0x3E42)), + *configs_from_eq_params(0xA2, EQParams(0x091F, 0x3D4C, 0xCE11, 0x1266, 0x2807)), + *configs_from_eq_params(0xAC, EQParams(0x0A9E, 0x3F20, 0xE573, 0x0A8B, 0x3A3B)), + ], + "tizi": [ + AmpConfig("Left speaker output from left DAC", 0b1, 0x2B, 0, 0b11111111), + AmpConfig("Right speaker output from right DAC", 0b1, 0x2C, 0, 0b11111111), + AmpConfig("Left Speaker Mixer Gain", 0b00, 0x2D, 0, 0b00000011), + AmpConfig("Right Speaker Mixer Gain", 0b00, 0x2D, 2, 0b00001100), + AmpConfig("Left speaker output volume", 0x17, 0x3D, 0, 0b00011111), + AmpConfig("Right speaker output volume", 0x17, 0x3E, 0, 0b00011111), + + AmpConfig("DAI2 EQ enable", 0b0, 0x49, 1, 0b00000010), + AmpConfig("DAI2: DC blocking", 0b0, 0x20, 0, 0b00000001), + AmpConfig("ALC enable", 0b0, 0x43, 7, 0b10000000), + AmpConfig("DAI2 EQ attenuation", 0x2, 0x32, 0, 0b00001111), + AmpConfig("Excursion limiter upper corner freq", 0b001, 0x41, 4, 0b01110000), + AmpConfig("Excursion limiter threshold", 0b100, 0x42, 0, 0b00001111), + AmpConfig("Distortion limit (THDCLP)", 0x0, 0x46, 4, 0b11110000), + AmpConfig("Distortion limiter release time constant", 0b1, 0x46, 0, 0b00000001), + AmpConfig("Left DAC input mixer: DAI1 left", 0b0, 0x22, 7, 0b10000000), + AmpConfig("Left DAC input mixer: DAI1 right", 0b0, 0x22, 6, 0b01000000), + AmpConfig("Left DAC input mixer: DAI2 left", 0b1, 0x22, 5, 0b00100000), + AmpConfig("Left DAC input mixer: DAI2 right", 0b0, 0x22, 4, 0b00010000), + AmpConfig("Right DAC input mixer: DAI2 left", 0b0, 0x22, 1, 0b00000010), + AmpConfig("Right DAC input mixer: DAI2 right", 0b1, 0x22, 0, 0b00000001), + AmpConfig("Volume adjustment smoothing disabled", 0b1, 0x49, 6, 0b01000000), + ], +} class Amplifier: AMP_I2C_BUS = 0 @@ -91,14 +119,22 @@ class Amplifier: def set_global_shutdown(self, amp_disabled): self.set_config(AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000)) - def initialize_configuration(self): + def initialize_configuration(self, model): self.set_global_shutdown(amp_disabled=True) for config in BASE_CONFIG: self.set_config(config) + for config in CONFIGS[model]: + self.set_config(config) + self.set_global_shutdown(amp_disabled=False) if __name__ == "__main__": - Amplifier(debug=True).initialize_configuration() + with open("/sys/firmware/devicetree/base/model") as f: + model = f.read().strip('\x00') + model = model.split('comma ')[-1] + + amp = Amplifier() + amp.initialize_configuration(model) diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index dcccb9f3d1..5f6fb2dc50 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -10,7 +10,7 @@ class HardwareTici : public HardwareNone { public: static constexpr float MAX_VOLUME = 0.9; - static constexpr float MIN_VOLUME = 0.2; + static constexpr float MIN_VOLUME = 0.1; static bool TICI() { return true; } static bool AGNOS() { return true; } static std::string get_os_version() { @@ -21,13 +21,32 @@ public: static int get_voltage() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/in1_input").c_str()); }; static int get_current() { return std::atoi(util::read_file("/sys/class/hwmon/hwmon1/curr1_input").c_str()); }; + static std::string get_serial() { + static std::string serial(""); + if (serial.empty()) { + std::ifstream stream("/proc/cmdline"); + std::string cmdline; + std::getline(stream, cmdline); + + auto start = cmdline.find("serialno="); + if (start == std::string::npos) { + serial = "cccccc"; + } else { + auto end = cmdline.find(" ", start + 9); + serial = cmdline.substr(start + 9, end - start - 9); + } + } + return serial; + } static void reboot() { std::system("sudo reboot"); }; static void poweroff() { std::system("sudo poweroff"); }; static void set_brightness(int percent) { + std::string max = util::read_file("/sys/class/backlight/panel0-backlight/max_brightness"); + std::ofstream brightness_control("/sys/class/backlight/panel0-backlight/brightness"); if (brightness_control.is_open()) { - brightness_control << (percent * (int)(1023/100.)) << "\n"; + brightness_control << (int)(percent * (std::stof(max)/100.)) << "\n"; brightness_control.close(); } }; @@ -38,6 +57,13 @@ public: bl_power_control.close(); } }; + static void set_volume(float volume) { + volume = util::map_val(volume, 0.f, 1.f, MIN_VOLUME, MAX_VOLUME); + + char volume_str[6]; + snprintf(volume_str, sizeof(volume_str), "%.3f", volume); + std::system(("pactl set-sink-volume @DEFAULT_SINK@ " + std::string(volume_str)).c_str()); + } static bool get_ssh_enabled() { return Params().getBool("SshEnabled"); }; static void set_ssh_enabled(bool enabled) { Params().putBool("SshEnabled", enabled); }; diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index e2fd20c1be..46db097da1 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -85,6 +85,16 @@ class Tici(HardwareBase): def amplifier(self): return Amplifier() + @cached_property + def model(self): + with open("/sys/firmware/devicetree/base/model") as f: + model = f.read().strip('\x00') + model = model.split('comma ')[-1] + # TODO: remove this with AGNOS 7+ + if model.startswith('Qualcomm'): + model = 'tici' + return model + def get_os_version(self): with open("/VERSION") as f: return f.read().strip() @@ -385,15 +395,22 @@ class Tici(HardwareBase): def set_screen_brightness(self, percentage): try: + with open("/sys/class/backlight/panel0-backlight/max_brightness") as f: + max_brightness = float(f.read().strip()) + + val = int(percentage * (max_brightness / 100.)) with open("/sys/class/backlight/panel0-backlight/brightness", "w") as f: - f.write(str(int(percentage * 10.23))) + f.write(str(val)) except Exception: pass def get_screen_brightness(self): try: + with open("/sys/class/backlight/panel0-backlight/max_brightness") as f: + max_brightness = float(f.read().strip()) + with open("/sys/class/backlight/panel0-backlight/brightness") as f: - return int(float(f.read()) / 10.23) + return int(float(f.read()) / (max_brightness / 100.)) except Exception: return 0 @@ -401,7 +418,7 @@ class Tici(HardwareBase): # amplifier, 100mW at idle self.amplifier.set_global_shutdown(amp_disabled=powersave_enabled) if not powersave_enabled: - self.amplifier.initialize_configuration() + self.amplifier.initialize_configuration(self.model) # *** CPU config *** @@ -416,6 +433,7 @@ class Tici(HardwareBase): # *** IRQ config *** affine_irq(5, 565) # kgsl-3d0 + affine_irq(4, 126) # SPI goes on boardd core affine_irq(4, 740) # xhci-hcd:usb1 goes on the boardd core affine_irq(4, 1069) # xhci-hcd:usb3 goes on the boardd core for irq in range(237, 246): @@ -429,11 +447,17 @@ class Tici(HardwareBase): return 0 def initialize_hardware(self): - self.amplifier.initialize_configuration() + self.amplifier.initialize_configuration(self.model) # Allow thermald to write engagement status to kmsg os.system("sudo chmod a+w /dev/kmsg") + # TODO: remove the if once agnos 7 ships + # Turn off fan, turned on by the ABL + if os.path.exists('/sys/class/gpio/gpio49/'): + gpio_init(GPIO.SOM_ST_IO, True) + gpio_set(GPIO.SOM_ST_IO, 0) + # *** IRQ config *** # move these off the default core diff --git a/system/hardware/tici/pins.py b/system/hardware/tici/pins.py index fe31b9311d..082a402f15 100644 --- a/system/hardware/tici/pins.py +++ b/system/hardware/tici/pins.py @@ -10,6 +10,8 @@ class GPIO: STM_RST_N = 124 STM_BOOT0 = 134 + SOM_ST_IO = 49 + LTE_RST_N = 50 LTE_PWRKEY = 116 LTE_BOOT = 52 diff --git a/system/hardware/tici/test_power_draw.py b/system/hardware/tici/test_power_draw.py index 2460152998..f563933285 100755 --- a/system/hardware/tici/test_power_draw.py +++ b/system/hardware/tici/test_power_draw.py @@ -3,6 +3,7 @@ import unittest import time import math from dataclasses import dataclass +from tabulate import tabulate from system.hardware import HARDWARE, TICI from system.hardware.tici.power_monitor import get_power @@ -20,7 +21,7 @@ class Proc: PROCS = [ Proc('camerad', 2.15), - Proc('modeld', 1.15, atol=0.2), + Proc('modeld', 0.93, atol=0.2), Proc('dmonitoringmodeld', 0.4), Proc('encoderd', 0.23), ] @@ -58,15 +59,16 @@ class TestPowerDraw(unittest.TestCase): manager_cleanup() - print("-"*35) - print(f"Baseline {baseline:.2f}W\n") + tab = [] + tab.append(['process', 'expected (W)', 'current (W)']) for proc in PROCS: cur = used[proc.name] expected = proc.power - print(f"{proc.name.ljust(20)} {expected:.2f}W {cur:.2f}W") + tab.append([proc.name, round(expected, 2), round(cur, 2)]) with self.subTest(proc=proc.name): self.assertTrue(math.isclose(cur, expected, rel_tol=proc.rtol, abs_tol=proc.atol)) - print("-"*35) + print(tabulate(tab)) + print(f"Baseline {baseline:.2f}W\n") if __name__ == "__main__": diff --git a/selfdrive/loggerd/.gitignore b/system/loggerd/.gitignore similarity index 100% rename from selfdrive/loggerd/.gitignore rename to system/loggerd/.gitignore diff --git a/selfdrive/loggerd/README.md b/system/loggerd/README.md similarity index 100% rename from selfdrive/loggerd/README.md rename to system/loggerd/README.md diff --git a/selfdrive/loggerd/SConscript b/system/loggerd/SConscript similarity index 93% rename from selfdrive/loggerd/SConscript rename to system/loggerd/SConscript index 92706c53ec..3b961bce6e 100644 --- a/selfdrive/loggerd/SConscript +++ b/system/loggerd/SConscript @@ -13,6 +13,8 @@ if arch == "Darwin": # fix OpenCL del libs[libs.index('OpenCL')] env['FRAMEWORKS'] = ['OpenCL'] + # exclude v4l + del src[src.index('encoder/v4l_encoder.cc')] logger_lib = env.Library('logger', src) libs.insert(0, logger_lib) diff --git a/selfdrive/sensord/__init__.py b/system/loggerd/__init__.py similarity index 100% rename from selfdrive/sensord/__init__.py rename to system/loggerd/__init__.py diff --git a/selfdrive/loggerd/bootlog.cc b/system/loggerd/bootlog.cc similarity index 86% rename from selfdrive/loggerd/bootlog.cc rename to system/loggerd/bootlog.cc index e882e4cf8d..becd293c02 100644 --- a/selfdrive/loggerd/bootlog.cc +++ b/system/loggerd/bootlog.cc @@ -2,8 +2,9 @@ #include #include "cereal/messaging/messaging.h" +#include "common/params.h" #include "common/swaglog.h" -#include "selfdrive/loggerd/logger.h" +#include "system/loggerd/logger.h" static kj::Array build_boot_log() { @@ -48,7 +49,8 @@ static kj::Array build_boot_log() { } int main(int argc, char** argv) { - const std::string path = LOG_ROOT + "/boot/" + logger_get_route_name(); + const std::string timestr = logger_get_route_name(); + const std::string path = LOG_ROOT + "/boot/" + timestr; LOGW("bootlog to %s", path.c_str()); // Open bootlog @@ -61,5 +63,8 @@ int main(int argc, char** argv) { // Write bootlog file.write(build_boot_log().asBytes()); + // Write out bootlog param to match routes with bootlog + Params().put("CurrentBootlog", timestr.c_str()); + return 0; } diff --git a/selfdrive/loggerd/config.py b/system/loggerd/config.py similarity index 100% rename from selfdrive/loggerd/config.py rename to system/loggerd/config.py diff --git a/selfdrive/loggerd/deleter.py b/system/loggerd/deleter.py similarity index 89% rename from selfdrive/loggerd/deleter.py rename to system/loggerd/deleter.py index 5606288024..5fb2b9eb41 100644 --- a/selfdrive/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -3,8 +3,8 @@ import os import shutil import threading from system.swaglog import cloudlog -from selfdrive.loggerd.config import ROOT, get_available_bytes, get_available_percent -from selfdrive.loggerd.uploader import listdir_by_creation +from system.loggerd.config import ROOT, get_available_bytes, get_available_percent +from system.loggerd.uploader import listdir_by_creation MIN_BYTES = 5 * 1024 * 1024 * 1024 MIN_PERCENT = 10 diff --git a/selfdrive/loggerd/encoder/encoder.cc b/system/loggerd/encoder/encoder.cc similarity index 98% rename from selfdrive/loggerd/encoder/encoder.cc rename to system/loggerd/encoder/encoder.cc index 943f37803d..08eaf0ff01 100644 --- a/selfdrive/loggerd/encoder/encoder.cc +++ b/system/loggerd/encoder/encoder.cc @@ -1,5 +1,5 @@ #include -#include "selfdrive/loggerd/encoder/encoder.h" +#include "system/loggerd/encoder/encoder.h" VideoEncoder::~VideoEncoder() {} diff --git a/selfdrive/loggerd/encoder/encoder.h b/system/loggerd/encoder/encoder.h similarity index 97% rename from selfdrive/loggerd/encoder/encoder.h rename to system/loggerd/encoder/encoder.h index 21ef65cf12..88dd5cee23 100644 --- a/selfdrive/loggerd/encoder/encoder.h +++ b/system/loggerd/encoder/encoder.h @@ -7,7 +7,7 @@ #include "cereal/messaging/messaging.h" #include "cereal/visionipc/visionipc.h" #include "common/queue.h" -#include "selfdrive/loggerd/video_writer.h" +#include "system/loggerd/video_writer.h" #include "system/camerad/cameras/camera_common.h" #define V4L2_BUF_FLAG_KEYFRAME 8 diff --git a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc b/system/loggerd/encoder/ffmpeg_encoder.cc similarity index 98% rename from selfdrive/loggerd/encoder/ffmpeg_encoder.cc rename to system/loggerd/encoder/ffmpeg_encoder.cc index 5f8d140e8b..275da34f18 100644 --- a/selfdrive/loggerd/encoder/ffmpeg_encoder.cc +++ b/system/loggerd/encoder/ffmpeg_encoder.cc @@ -1,6 +1,6 @@ #pragma clang diagnostic ignored "-Wdeprecated-declarations" -#include "selfdrive/loggerd/encoder/ffmpeg_encoder.h" +#include "system/loggerd/encoder/ffmpeg_encoder.h" #include #include diff --git a/selfdrive/loggerd/encoder/ffmpeg_encoder.h b/system/loggerd/encoder/ffmpeg_encoder.h similarity index 92% rename from selfdrive/loggerd/encoder/ffmpeg_encoder.h rename to system/loggerd/encoder/ffmpeg_encoder.h index 497a28b651..9095a6e815 100644 --- a/selfdrive/loggerd/encoder/ffmpeg_encoder.h +++ b/system/loggerd/encoder/ffmpeg_encoder.h @@ -11,8 +11,8 @@ extern "C" { #include } -#include "selfdrive/loggerd/encoder/encoder.h" -#include "selfdrive/loggerd/loggerd.h" +#include "system/loggerd/encoder/encoder.h" +#include "system/loggerd/loggerd.h" class FfmpegEncoder : public VideoEncoder { public: diff --git a/selfdrive/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc similarity index 99% rename from selfdrive/loggerd/encoder/v4l_encoder.cc rename to system/loggerd/encoder/v4l_encoder.cc index 88aeb21256..16e7246ff4 100644 --- a/selfdrive/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -2,7 +2,7 @@ #include #include -#include "selfdrive/loggerd/encoder/v4l_encoder.h" +#include "system/loggerd/encoder/v4l_encoder.h" #include "common/util.h" #include "common/timing.h" diff --git a/selfdrive/loggerd/encoder/v4l_encoder.h b/system/loggerd/encoder/v4l_encoder.h similarity index 95% rename from selfdrive/loggerd/encoder/v4l_encoder.h rename to system/loggerd/encoder/v4l_encoder.h index c2a53dd6ef..d4b0a1211d 100644 --- a/selfdrive/loggerd/encoder/v4l_encoder.h +++ b/system/loggerd/encoder/v4l_encoder.h @@ -1,7 +1,7 @@ #pragma once #include "common/queue.h" -#include "selfdrive/loggerd/encoder/encoder.h" +#include "system/loggerd/encoder/encoder.h" #define BUF_IN_COUNT 7 #define BUF_OUT_COUNT 6 diff --git a/selfdrive/loggerd/encoderd.cc b/system/loggerd/encoderd.cc similarity index 99% rename from selfdrive/loggerd/encoderd.cc rename to system/loggerd/encoderd.cc index db5f4b61ab..12b58b6591 100644 --- a/selfdrive/loggerd/encoderd.cc +++ b/system/loggerd/encoderd.cc @@ -1,4 +1,4 @@ -#include "selfdrive/loggerd/loggerd.h" +#include "system/loggerd/loggerd.h" ExitHandler do_exit; diff --git a/selfdrive/loggerd/logger.cc b/system/loggerd/logger.cc similarity index 99% rename from selfdrive/loggerd/logger.cc rename to system/loggerd/logger.cc index aaf267e523..0599aa1e54 100644 --- a/selfdrive/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -1,4 +1,4 @@ -#include "selfdrive/loggerd/logger.h" +#include "system/loggerd/logger.h" #include #include diff --git a/selfdrive/loggerd/logger.h b/system/loggerd/logger.h similarity index 100% rename from selfdrive/loggerd/logger.h rename to system/loggerd/logger.h diff --git a/selfdrive/loggerd/loggerd.cc b/system/loggerd/loggerd.cc similarity index 91% rename from selfdrive/loggerd/loggerd.cc rename to system/loggerd/loggerd.cc index 9beb3c3bf1..a7f7db4801 100644 --- a/selfdrive/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -1,5 +1,5 @@ -#include "selfdrive/loggerd/loggerd.h" -#include "selfdrive/loggerd/video_writer.h" +#include "system/loggerd/loggerd.h" +#include "system/loggerd/video_writer.h" ExitHandler do_exit; @@ -24,15 +24,25 @@ void logger_rotate(LoggerdState *s) { } void rotate_if_needed(LoggerdState *s) { - if (s->ready_to_rotate == s->max_waiting) { - logger_rotate(s); + // all encoders ready, trigger rotation + bool all_ready = s->ready_to_rotate == s->max_waiting; + + // fallback logic to prevent extremely long segments in the case of camera, encoder, etc. malfunctions + bool timed_out = false; + double tms = millis_since_boot(); + double seg_length_secs = (tms - s->last_rotate_tms) / 1000.; + if ((seg_length_secs > SEGMENT_LENGTH) && !LOGGERD_TEST) { + // TODO: might be nice to put these reasons in the sentinel + if ((tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE) { + timed_out = true; + LOGE("no camera packets seen. auto rotating"); + } else if (seg_length_secs > SEGMENT_LENGTH*1.2) { + timed_out = true; + LOGE("segment too long. auto rotating"); + } } - double tms = millis_since_boot(); - if ((tms - s->last_rotate_tms) > SEGMENT_LENGTH * 1000 && - (tms - s->last_camera_seen_tms) > NO_CAMERA_PATIENCE && - !LOGGERD_TEST) { - LOGW("no camera packet seen. auto rotating"); + if (all_ready || timed_out) { logger_rotate(s); } } diff --git a/selfdrive/loggerd/loggerd.h b/system/loggerd/loggerd.h similarity index 91% rename from selfdrive/loggerd/loggerd.h rename to system/loggerd/loggerd.h index 6eafbe08d0..1b8f9e0d2a 100644 --- a/selfdrive/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -10,6 +10,7 @@ #include #include #include +#include #include "cereal/messaging/messaging.h" #include "cereal/services.h" @@ -22,13 +23,13 @@ #include "common/util.h" #include "system/hardware/hw.h" -#include "selfdrive/loggerd/encoder/encoder.h" -#include "selfdrive/loggerd/logger.h" +#include "system/loggerd/encoder/encoder.h" +#include "system/loggerd/logger.h" #ifdef QCOM2 -#include "selfdrive/loggerd/encoder/v4l_encoder.h" +#include "system/loggerd/encoder/v4l_encoder.h" #define Encoder V4LEncoder #else -#include "selfdrive/loggerd/encoder/ffmpeg_encoder.h" +#include "system/loggerd/encoder/ffmpeg_encoder.h" #define Encoder FfmpegEncoder #endif diff --git a/selfdrive/sensord/tests/__init__.py b/system/loggerd/tests/__init__.py similarity index 100% rename from selfdrive/sensord/tests/__init__.py rename to system/loggerd/tests/__init__.py diff --git a/selfdrive/loggerd/tests/fill_eon.py b/system/loggerd/tests/fill_eon.py similarity index 79% rename from selfdrive/loggerd/tests/fill_eon.py rename to system/loggerd/tests/fill_eon.py index b40982fa9f..e0c52ea0d7 100755 --- a/selfdrive/loggerd/tests/fill_eon.py +++ b/system/loggerd/tests/fill_eon.py @@ -3,8 +3,8 @@ import os -from selfdrive.loggerd.config import ROOT, get_available_percent -from selfdrive.loggerd.tests.loggerd_tests_common import create_random_file +from system.loggerd.config import ROOT, get_available_percent +from system.loggerd.tests.loggerd_tests_common import create_random_file if __name__ == "__main__": diff --git a/selfdrive/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py similarity index 98% rename from selfdrive/loggerd/tests/loggerd_tests_common.py rename to system/loggerd/tests/loggerd_tests_common.py index 80cfb162f1..6aca83858b 100644 --- a/selfdrive/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -5,7 +5,7 @@ import random import tempfile import unittest -import selfdrive.loggerd.uploader as uploader +import system.loggerd.uploader as uploader def create_random_file(file_path, size_mb, lock=False): try: diff --git a/selfdrive/loggerd/tests/test_deleter.py b/system/loggerd/tests/test_deleter.py similarity index 96% rename from selfdrive/loggerd/tests/test_deleter.py rename to system/loggerd/tests/test_deleter.py index 80fb5c997f..5b54a43f3b 100755 --- a/selfdrive/loggerd/tests/test_deleter.py +++ b/system/loggerd/tests/test_deleter.py @@ -6,8 +6,8 @@ import unittest from collections import namedtuple from common.timeout import Timeout, TimeoutException -import selfdrive.loggerd.deleter as deleter -from selfdrive.loggerd.tests.loggerd_tests_common import UploaderTestCase +import system.loggerd.deleter as deleter +from system.loggerd.tests.loggerd_tests_common import UploaderTestCase Stats = namedtuple("Stats", ['f_bavail', 'f_blocks', 'f_frsize']) diff --git a/selfdrive/loggerd/tests/test_encoder.py b/system/loggerd/tests/test_encoder.py similarity index 99% rename from selfdrive/loggerd/tests/test_encoder.py rename to system/loggerd/tests/test_encoder.py index 1b9bcef2d7..81f4e9fb9d 100755 --- a/selfdrive/loggerd/tests/test_encoder.py +++ b/system/loggerd/tests/test_encoder.py @@ -14,7 +14,7 @@ from tqdm import trange from common.params import Params from common.timeout import Timeout from system.hardware import TICI -from selfdrive.loggerd.config import ROOT +from system.loggerd.config import ROOT from selfdrive.manager.process_config import managed_processes from tools.lib.logreader import LogReader diff --git a/selfdrive/loggerd/tests/test_logger.cc b/system/loggerd/tests/test_logger.cc similarity index 98% rename from selfdrive/loggerd/tests/test_logger.cc rename to system/loggerd/tests/test_logger.cc index 18a0e57df7..9c82299091 100644 --- a/selfdrive/loggerd/tests/test_logger.cc +++ b/system/loggerd/tests/test_logger.cc @@ -4,11 +4,12 @@ #include #include #include +#include #include "catch2/catch.hpp" #include "cereal/messaging/messaging.h" #include "common/util.h" -#include "selfdrive/loggerd/logger.h" +#include "system/loggerd/logger.h" #include "tools/replay/util.h" typedef cereal::Sentinel::SentinelType SentinelType; diff --git a/selfdrive/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py similarity index 96% rename from selfdrive/loggerd/tests/test_loggerd.py rename to system/loggerd/tests/test_loggerd.py index 9c3565d130..f39b8bb9d6 100755 --- a/selfdrive/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -15,7 +15,7 @@ from cereal.services import service_list from common.basedir import BASEDIR from common.params import Params from common.timeout import Timeout -from selfdrive.loggerd.config import ROOT +from system.loggerd.config import ROOT from selfdrive.manager.process_config import managed_processes from system.version import get_version from tools.lib.logreader import LogReader @@ -51,7 +51,7 @@ class TestLoggerd(unittest.TestCase): def _gen_bootlog(self): with Timeout(5): - out = subprocess.check_output("./bootlog", cwd=os.path.join(BASEDIR, "selfdrive/loggerd"), encoding='utf-8') + out = subprocess.check_output("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd"), encoding='utf-8') log_fn = self._get_log_fn(out) @@ -86,7 +86,7 @@ class TestLoggerd(unittest.TestCase): params.clear_all() for k, _, v in fake_params: params.put(k, v) - params.put("LaikadEphemeris", "abc") + params.put("LaikadEphemerisV3", "abc") lr = list(LogReader(str(self._gen_bootlog()))) initData = lr[0].initData @@ -103,14 +103,14 @@ class TestLoggerd(unittest.TestCase): # check params logged_params = {entry.key: entry.value for entry in initData.params.entries} - expected_params = set(k for k, _, __ in fake_params) | {'LaikadEphemeris'} + expected_params = set(k for k, _, __ in fake_params) | {'LaikadEphemerisV3'} assert set(logged_params.keys()) == expected_params, set(logged_params.keys()) ^ expected_params - assert logged_params['LaikadEphemeris'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['LaikadEphemeris'])}" + assert logged_params['LaikadEphemerisV3'] == b'', f"DONT_LOG param value was logged: {repr(logged_params['LaikadEphemerisV3'])}" for param_key, initData_key, v in fake_params: self.assertEqual(getattr(initData, initData_key), v) self.assertEqual(logged_params[param_key].decode(), v) - params.put("LaikadEphemeris", "") + params.put("LaikadEphemerisV3", "") def test_rotation(self): os.environ["LOGGERD_TEST"] = "1" diff --git a/selfdrive/loggerd/tests/test_runner.cc b/system/loggerd/tests/test_runner.cc similarity index 100% rename from selfdrive/loggerd/tests/test_runner.cc rename to system/loggerd/tests/test_runner.cc diff --git a/selfdrive/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py similarity index 97% rename from selfdrive/loggerd/tests/test_uploader.py rename to system/loggerd/tests/test_uploader.py index 6090bbe2aa..11b273cec8 100755 --- a/selfdrive/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -7,9 +7,9 @@ import logging import json from system.swaglog import cloudlog -import selfdrive.loggerd.uploader as uploader +import system.loggerd.uploader as uploader -from selfdrive.loggerd.tests.loggerd_tests_common import UploaderTestCase +from system.loggerd.tests.loggerd_tests_common import UploaderTestCase class TestLogHandler(logging.Handler): diff --git a/selfdrive/loggerd/tools/mark_all_uploaded.py b/system/loggerd/tools/mark_all_uploaded.py similarity index 60% rename from selfdrive/loggerd/tools/mark_all_uploaded.py rename to system/loggerd/tools/mark_all_uploaded.py index e60e6cfa2c..c963014748 100644 --- a/selfdrive/loggerd/tools/mark_all_uploaded.py +++ b/system/loggerd/tools/mark_all_uploaded.py @@ -1,7 +1,7 @@ import os -from selfdrive.loggerd.uploader import UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE +from system.loggerd.uploader import UPLOAD_ATTR_NAME, UPLOAD_ATTR_VALUE -from selfdrive.loggerd.config import ROOT +from system.loggerd.config import ROOT for folder in os.walk(ROOT): for file1 in folder[2]: full_path = os.path.join(folder[0], file1) diff --git a/selfdrive/loggerd/tools/mark_unuploaded.py b/system/loggerd/tools/mark_unuploaded.py similarity index 70% rename from selfdrive/loggerd/tools/mark_unuploaded.py rename to system/loggerd/tools/mark_unuploaded.py index 343805d5fc..3d1d4472b0 100755 --- a/selfdrive/loggerd/tools/mark_unuploaded.py +++ b/system/loggerd/tools/mark_unuploaded.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import os import sys -from selfdrive.loggerd.uploader import UPLOAD_ATTR_NAME +from system.loggerd.uploader import UPLOAD_ATTR_NAME for fn in sys.argv[1:]: print(f"unmarking {fn}") diff --git a/selfdrive/loggerd/uploader.py b/system/loggerd/uploader.py similarity index 98% rename from selfdrive/loggerd/uploader.py rename to system/loggerd/uploader.py index f97bafecb9..d4ed6dca28 100644 --- a/selfdrive/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -16,8 +16,8 @@ from common.api import Api from common.params import Params from common.realtime import set_core_affinity from system.hardware import TICI -from selfdrive.loggerd.xattr_cache import getxattr, setxattr -from selfdrive.loggerd.config import ROOT +from system.loggerd.xattr_cache import getxattr, setxattr +from system.loggerd.config import ROOT from system.swaglog import cloudlog NetworkType = log.DeviceState.NetworkType diff --git a/selfdrive/loggerd/video_writer.cc b/system/loggerd/video_writer.cc similarity index 98% rename from selfdrive/loggerd/video_writer.cc rename to system/loggerd/video_writer.cc index 4f79ccafc8..91bf09355f 100644 --- a/selfdrive/loggerd/video_writer.cc +++ b/system/loggerd/video_writer.cc @@ -2,7 +2,7 @@ #include #include -#include "selfdrive/loggerd/video_writer.h" +#include "system/loggerd/video_writer.h" #include "common/swaglog.h" #include "common/util.h" diff --git a/selfdrive/loggerd/video_writer.h b/system/loggerd/video_writer.h similarity index 100% rename from selfdrive/loggerd/video_writer.h rename to system/loggerd/video_writer.h diff --git a/selfdrive/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py similarity index 100% rename from selfdrive/loggerd/xattr_cache.py rename to system/loggerd/xattr_cache.py diff --git a/system/micd.py b/system/micd.py new file mode 100755 index 0000000000..97ba0c262e --- /dev/null +++ b/system/micd.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import numpy as np + +from cereal import messaging +from common.filter_simple import FirstOrderFilter +from common.realtime import Ratekeeper +from system.swaglog import cloudlog + +RATE = 10 +FFT_SAMPLES = 4096 +REFERENCE_SPL = 2e-5 # newtons/m^2 +SAMPLE_RATE = 44100 +FILTER_DT = 1. / (SAMPLE_RATE / FFT_SAMPLES) + + +def calculate_spl(measurements): + # https://www.engineeringtoolbox.com/sound-pressure-d_711.html + sound_pressure = np.sqrt(np.mean(measurements ** 2)) # RMS of amplitudes + if sound_pressure > 0: + sound_pressure_level = 20 * np.log10(sound_pressure / REFERENCE_SPL) # dB + else: + sound_pressure_level = 0 + return sound_pressure, sound_pressure_level + + +def apply_a_weighting(measurements: np.ndarray) -> np.ndarray: + # Generate a Hanning window of the same length as the audio measurements + measurements_windowed = measurements * np.hanning(len(measurements)) + + # Calculate the frequency axis for the signal + freqs = np.fft.fftfreq(measurements_windowed.size, d=1 / SAMPLE_RATE) + + # Calculate the A-weighting filter + # https://en.wikipedia.org/wiki/A-weighting + A = 12194 ** 2 * freqs ** 4 / ((freqs ** 2 + 20.6 ** 2) * (freqs ** 2 + 12194 ** 2) * np.sqrt((freqs ** 2 + 107.7 ** 2) * (freqs ** 2 + 737.9 ** 2))) + A /= np.max(A) # Normalize the filter + + # Apply the A-weighting filter to the signal + return np.abs(np.fft.ifft(np.fft.fft(measurements_windowed) * A)) + + +class Mic: + def __init__(self, pm): + self.pm = pm + self.rk = Ratekeeper(RATE) + + self.measurements = np.empty(0) + + self.sound_pressure = 0 + self.sound_pressure_weighted = 0 + self.sound_pressure_level_weighted = 0 + + self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) + + def update(self): + msg = messaging.new_message('microphone') + msg.microphone.soundPressure = float(self.sound_pressure) + msg.microphone.soundPressureWeighted = float(self.sound_pressure_weighted) + + msg.microphone.soundPressureWeightedDb = float(self.sound_pressure_level_weighted) + msg.microphone.filteredSoundPressureWeightedDb = float(self.spl_filter_weighted.x) + + self.pm.send('microphone', msg) + self.rk.keep_time() + + def callback(self, indata, frames, time, status): + """ + Using amplitude measurements, calculate an uncalibrated sound pressure and sound pressure level. + Then apply A-weighting to the raw amplitudes and run the same calculations again. + + Logged A-weighted equivalents are rough approximations of the human-perceived loudness. + """ + + self.measurements = np.concatenate((self.measurements, indata[:, 0])) + + while self.measurements.size >= FFT_SAMPLES: + measurements = self.measurements[:FFT_SAMPLES] + + self.sound_pressure, _ = calculate_spl(measurements) + measurements_weighted = apply_a_weighting(measurements) + self.sound_pressure_weighted, self.sound_pressure_level_weighted = calculate_spl(measurements_weighted) + self.spl_filter_weighted.update(self.sound_pressure_level_weighted) + + self.measurements = self.measurements[FFT_SAMPLES:] + + def micd_thread(self): + # sounddevice must be imported after forking processes + import sounddevice as sd # pylint: disable=import-outside-toplevel + + with sd.InputStream(channels=1, samplerate=SAMPLE_RATE, callback=self.callback) as stream: + cloudlog.info(f"micd stream started: {stream.samplerate=} {stream.channels=} {stream.dtype=} {stream.device=}") + while True: + self.update() + + +def main(pm=None): + if pm is None: + pm = messaging.PubMaster(['microphone']) + + mic = Mic(pm) + mic.micd_thread() + + +if __name__ == "__main__": + main() diff --git a/system/sensord/.gitignore b/system/sensord/.gitignore new file mode 100644 index 0000000000..e9b8071b4b --- /dev/null +++ b/system/sensord/.gitignore @@ -0,0 +1 @@ +_sensord diff --git a/selfdrive/sensord/SConscript b/system/sensord/SConscript similarity index 100% rename from selfdrive/sensord/SConscript rename to system/sensord/SConscript diff --git a/system/sensord/__init__.py b/system/sensord/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/sensord/pigeond.py b/system/sensord/pigeond.py similarity index 97% rename from selfdrive/sensord/pigeond.py rename to system/sensord/pigeond.py index f56af1c705..c9ad7ff22a 100755 --- a/selfdrive/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -123,7 +123,7 @@ class TTYPigeon(): init_baudrate(self) # clear configuration - self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b") + self.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\xd7") # clear flash memory (almanac backup) self.send_with_ack(b"\xB5\x62\x09\x14\x04\x00\x01\x00\x00\x00\x22\xf0") @@ -183,6 +183,7 @@ def initialize_pigeon(pigeon: TTYPigeon) -> bool: pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x02\x13\x01\x20\x6C") pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x0A\x09\x01\x1E\x70") pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x0A\x0B\x01\x20\x74") + pigeon.send_with_ack(b"\xB5\x62\x06\x01\x03\x00\x01\x35\x01\x41\xAD") cloudlog.debug("pigeon configured") # try restoring almanac backup @@ -303,8 +304,7 @@ def main(): pigeon, pm = create_pigeon() init_baudrate(pigeon) - r = initialize_pigeon(pigeon) - Params().put_bool("UbloxAvailable", r) + initialize_pigeon(pigeon) # start receiving data run_receiving(pigeon, pm) diff --git a/selfdrive/sensord/rawgps/compare.py b/system/sensord/rawgps/compare.py similarity index 100% rename from selfdrive/sensord/rawgps/compare.py rename to system/sensord/rawgps/compare.py diff --git a/selfdrive/sensord/rawgps/modemdiag.py b/system/sensord/rawgps/modemdiag.py similarity index 100% rename from selfdrive/sensord/rawgps/modemdiag.py rename to system/sensord/rawgps/modemdiag.py diff --git a/selfdrive/sensord/rawgps/rawgpsd.py b/system/sensord/rawgps/rawgpsd.py similarity index 90% rename from selfdrive/sensord/rawgps/rawgpsd.py rename to system/sensord/rawgps/rawgpsd.py index 5a6827a759..f75ceee7ed 100755 --- a/selfdrive/sensord/rawgps/rawgpsd.py +++ b/system/sensord/rawgps/rawgpsd.py @@ -11,10 +11,12 @@ from struct import unpack_from, calcsize, pack from cereal import log import cereal.messaging as messaging +from common.gpio import gpio_init, gpio_set from laika.gps_time import GPSTime +from system.hardware.tici.pins import GPIO from system.swaglog import cloudlog -from selfdrive.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv -from selfdrive.sensord.rawgps.structs import (dict_unpacker, position_report, relist, +from system.sensord.rawgps.modemdiag import ModemDiag, DIAG_LOG_F, setup_logs, send_recv +from system.sensord.rawgps.structs import (dict_unpacker, position_report, relist, gps_measurement_report, gps_measurement_report_sv, glonass_measurement_report, glonass_measurement_report_sv, oemdre_measurement_report, oemdre_measurement_report_sv, oemdre_svpoly_report, @@ -87,16 +89,24 @@ def try_setup_logs(diag, log_types): else: raise Exception(f"setup logs failed, {log_types=}") -def mmcli(cmd: str) -> None: +def at_cmd(cmd: str) -> None: for _ in range(5): try: - subprocess.check_call(f"mmcli -m any --timeout 30 {cmd}", shell=True) + subprocess.check_call(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True) break except subprocess.CalledProcessError: cloudlog.exception("rawgps.mmcli_command_failed") else: raise Exception(f"failed to execute mmcli command {cmd=}") + +def gps_enabled() -> bool: + try: + p = subprocess.check_output("mmcli -m any --command=\"AT+QGPS?\"", shell=True) + return b"QGPS: 1" in p + except subprocess.CalledProcessError as exc: + raise Exception("failed to execute QGPS mmcli command") from exc + def setup_quectel(diag: ModemDiag): # enable OEMDRE in the NV # TODO: it has to reboot for this to take effect @@ -108,11 +118,17 @@ def setup_quectel(diag: ModemDiag): setup_logs(diag, LOG_TYPES) + if gps_enabled(): + at_cmd("AT+QGPSEND") + # disable DPO power savings for more accuracy - mmcli("--command='AT+QGPSCFG=\"dpoenable\",0'") + at_cmd("AT+QGPSCFG=\"dpoenable\",0") # don't automatically turn on GNSS on powerup - mmcli("--command='AT+QGPSCFG=\"autogps\",0'") - mmcli("--location-enable-gps-raw --location-enable-gps-nmea") + at_cmd("AT+QGPSCFG=\"autogps\",0") + + at_cmd("AT+QGPSSUPLURL=\"supl.google.com:7275\"") + at_cmd("AT+QGPSCFG=\"outport\",\"usbnmea\"") + at_cmd("AT+QGPS=1") # enable OEMDRE mode DIAG_SUBSYS_CMD_F = 75 @@ -134,7 +150,9 @@ def setup_quectel(diag: ModemDiag): )) def teardown_quectel(diag): - mmcli("--location-disable-gps-raw --location-disable-gps-nmea") + at_cmd("AT+QGPSCFG=\"outport\",\"none\"") + if gps_enabled(): + at_cmd("AT+QGPSEND") try_setup_logs(diag, []) @@ -156,7 +174,7 @@ def main() -> NoReturn: # wait for ModemManager to come up cloudlog.warning("waiting for modem to come up") while True: - ret = subprocess.call("mmcli -m any --timeout 10 --location-status", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) + ret = subprocess.call("mmcli -m any --timeout 10 --command=\"AT+QGPS?\"", stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True) if ret == 0: break time.sleep(0.1) @@ -166,6 +184,7 @@ def main() -> NoReturn: def cleanup(sig, frame): cloudlog.warning(f"caught sig {sig}, disabling quectel gps") + gpio_set(GPIO.UBLOX_PWR_EN, False) teardown_quectel(diag) cloudlog.warning("quectel cleanup done") sys.exit(0) @@ -174,6 +193,8 @@ def main() -> NoReturn: setup_quectel(diag) cloudlog.warning("quectel setup done") + gpio_init(GPIO.UBLOX_PWR_EN, True) + gpio_set(GPIO.UBLOX_PWR_EN, True) pm = messaging.PubMaster(['qcomGnss', 'gpsLocation']) diff --git a/selfdrive/sensord/rawgps/structs.py b/system/sensord/rawgps/structs.py similarity index 100% rename from selfdrive/sensord/rawgps/structs.py rename to system/sensord/rawgps/structs.py diff --git a/selfdrive/sensord/rawgps/test_rawgps.py b/system/sensord/rawgps/test_rawgps.py similarity index 100% rename from selfdrive/sensord/rawgps/test_rawgps.py rename to system/sensord/rawgps/test_rawgps.py diff --git a/selfdrive/sensord/sensord b/system/sensord/sensord similarity index 100% rename from selfdrive/sensord/sensord rename to system/sensord/sensord diff --git a/selfdrive/sensord/sensors/bmx055_accel.cc b/system/sensord/sensors/bmx055_accel.cc similarity index 85% rename from selfdrive/sensord/sensors/bmx055_accel.cc rename to system/sensord/sensors/bmx055_accel.cc index 78b3ac526d..78f5d64e79 100644 --- a/selfdrive/sensord/sensors/bmx055_accel.cc +++ b/system/sensord/sensors/bmx055_accel.cc @@ -9,20 +9,8 @@ BMX055_Accel::BMX055_Accel(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Accel::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID}); + if (ret == -1) return -1; ret = set_register(BMX055_ACCEL_I2C_REG_PMU, BMX055_ACCEL_NORMAL_MODE); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/bmx055_accel.h b/system/sensord/sensors/bmx055_accel.h similarity index 96% rename from selfdrive/sensord/sensors/bmx055_accel.h rename to system/sensord/sensors/bmx055_accel.h index 8ef660a99f..2cc316e992 100644 --- a/selfdrive/sensord/sensors/bmx055_accel.h +++ b/system/sensord/sensors/bmx055_accel.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define BMX055_ACCEL_I2C_ADDR 0x18 diff --git a/selfdrive/sensord/sensors/bmx055_gyro.cc b/system/sensord/sensors/bmx055_gyro.cc similarity index 86% rename from selfdrive/sensord/sensors/bmx055_gyro.cc rename to system/sensord/sensors/bmx055_gyro.cc index 9d70b9e431..2a938044f3 100644 --- a/selfdrive/sensord/sensors/bmx055_gyro.cc +++ b/system/sensord/sensors/bmx055_gyro.cc @@ -12,20 +12,8 @@ BMX055_Gyro::BMX055_Gyro(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Gyro::init() { - int ret = 0; - uint8_t buffer[1]; - - ret =read_register(BMX055_GYRO_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_GYRO_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_GYRO_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(BMX055_GYRO_I2C_REG_ID, {BMX055_GYRO_CHIP_ID}); + if (ret == -1) return -1; ret = set_register(BMX055_GYRO_I2C_REG_LPM1, BMX055_GYRO_NORMAL_MODE); if (ret < 0) { diff --git a/selfdrive/sensord/sensors/bmx055_gyro.h b/system/sensord/sensors/bmx055_gyro.h similarity index 95% rename from selfdrive/sensord/sensors/bmx055_gyro.h rename to system/sensord/sensors/bmx055_gyro.h index 80b93f128c..7be3e56563 100644 --- a/selfdrive/sensord/sensors/bmx055_gyro.h +++ b/system/sensord/sensors/bmx055_gyro.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define BMX055_GYRO_I2C_ADDR 0x68 diff --git a/selfdrive/sensord/sensors/bmx055_magn.cc b/system/sensord/sensors/bmx055_magn.cc similarity index 95% rename from selfdrive/sensord/sensors/bmx055_magn.cc rename to system/sensord/sensors/bmx055_magn.cc index 394b1e83d3..2f0d10412c 100644 --- a/selfdrive/sensord/sensors/bmx055_magn.cc +++ b/system/sensord/sensors/bmx055_magn.cc @@ -66,8 +66,6 @@ static int16_t compensate_z(trim_data_t trim_data, int16_t mag_data_z, uint16_t BMX055_Magn::BMX055_Magn(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Magn::init() { - int ret; - uint8_t buffer[1]; uint8_t trim_x1y1[2] = {0}; uint8_t trim_x2y2[2] = {0}; uint8_t trim_xy1xy2[2] = {0}; @@ -78,25 +76,18 @@ int BMX055_Magn::init() { uint8_t trim_xyz1[2] = {0}; // suspend -> sleep - ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); + int ret = set_register(BMX055_MAGN_I2C_REG_PWR_0, 0x01); if(ret < 0) { LOGE("Enabling power failed: %d", ret); goto fail; } util::sleep_for(5); // wait until the chip is powered on - // read chip ID - ret = read_register(BMX055_MAGN_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); + ret = verify_chip_id(BMX055_MAGN_I2C_REG_ID, {BMX055_MAGN_CHIP_ID}); + if (ret == -1) { goto fail; } - if(buffer[0] != BMX055_MAGN_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_MAGN_CHIP_ID); - return -1; - } - // Load magnetometer trim ret = read_register(BMX055_MAGN_I2C_REG_DIG_X1, trim_x1y1, 2); if(ret < 0) goto fail; diff --git a/selfdrive/sensord/sensors/bmx055_magn.h b/system/sensord/sensors/bmx055_magn.h similarity index 97% rename from selfdrive/sensord/sensors/bmx055_magn.h rename to system/sensord/sensors/bmx055_magn.h index e4a79bc7e0..15c4e734b9 100644 --- a/selfdrive/sensord/sensors/bmx055_magn.h +++ b/system/sensord/sensors/bmx055_magn.h @@ -1,7 +1,7 @@ #pragma once #include -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define BMX055_MAGN_I2C_ADDR 0x10 diff --git a/selfdrive/sensord/sensors/bmx055_temp.cc b/system/sensord/sensors/bmx055_temp.cc similarity index 65% rename from selfdrive/sensord/sensors/bmx055_temp.cc rename to system/sensord/sensors/bmx055_temp.cc index bdb34f1508..68ee0da1d6 100644 --- a/selfdrive/sensord/sensors/bmx055_temp.cc +++ b/system/sensord/sensors/bmx055_temp.cc @@ -2,30 +2,14 @@ #include -#include "selfdrive/sensord/sensors/bmx055_accel.h" +#include "system/sensord/sensors/bmx055_accel.h" #include "common/swaglog.h" #include "common/timing.h" BMX055_Temp::BMX055_Temp(I2CBus *bus) : I2CSensor(bus) {} int BMX055_Temp::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(BMX055_ACCEL_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != BMX055_ACCEL_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], BMX055_ACCEL_CHIP_ID); - ret = -1; - goto fail; - } - -fail: - return ret; + return verify_chip_id(BMX055_ACCEL_I2C_REG_ID, {BMX055_ACCEL_CHIP_ID}) == -1 ? -1 : 0; } bool BMX055_Temp::get_event(MessageBuilder &msg, uint64_t ts) { diff --git a/selfdrive/sensord/sensors/bmx055_temp.h b/system/sensord/sensors/bmx055_temp.h similarity index 71% rename from selfdrive/sensord/sensors/bmx055_temp.h rename to system/sensord/sensors/bmx055_temp.h index 0b6802deaa..a2eabae395 100644 --- a/selfdrive/sensord/sensors/bmx055_temp.h +++ b/system/sensord/sensors/bmx055_temp.h @@ -1,7 +1,7 @@ #pragma once -#include "selfdrive/sensord/sensors/bmx055_accel.h" -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/bmx055_accel.h" +#include "system/sensord/sensors/i2c_sensor.h" class BMX055_Temp : public I2CSensor { uint8_t get_device_address() {return BMX055_ACCEL_I2C_ADDR;} diff --git a/selfdrive/sensord/sensors/constants.h b/system/sensord/sensors/constants.h similarity index 100% rename from selfdrive/sensord/sensors/constants.h rename to system/sensord/sensors/constants.h diff --git a/selfdrive/sensord/sensors/file_sensor.cc b/system/sensord/sensors/file_sensor.cc similarity index 100% rename from selfdrive/sensord/sensors/file_sensor.cc rename to system/sensord/sensors/file_sensor.cc diff --git a/selfdrive/sensord/sensors/file_sensor.h b/system/sensord/sensors/file_sensor.h similarity index 88% rename from selfdrive/sensord/sensors/file_sensor.h rename to system/sensord/sensors/file_sensor.h index 39d695167d..07d7e8f946 100644 --- a/selfdrive/sensord/sensors/file_sensor.h +++ b/system/sensord/sensors/file_sensor.h @@ -4,7 +4,7 @@ #include #include "cereal/gen/cpp/log.capnp.h" -#include "selfdrive/sensord/sensors/sensor.h" +#include "system/sensord/sensors/sensor.h" class FileSensor : public Sensor { protected: diff --git a/selfdrive/sensord/sensors/i2c_sensor.cc b/system/sensord/sensors/i2c_sensor.cc similarity index 100% rename from selfdrive/sensord/sensors/i2c_sensor.cc rename to system/sensord/sensors/i2c_sensor.cc diff --git a/selfdrive/sensord/sensors/i2c_sensor.h b/system/sensord/sensors/i2c_sensor.h similarity index 59% rename from selfdrive/sensord/sensors/i2c_sensor.h rename to system/sensord/sensors/i2c_sensor.h index 0de2a98738..ccac526c12 100644 --- a/selfdrive/sensord/sensors/i2c_sensor.h +++ b/system/sensord/sensors/i2c_sensor.h @@ -2,14 +2,14 @@ #include #include - #include "cereal/gen/cpp/log.capnp.h" #include "common/i2c.h" #include "common/gpio.h" -#include "selfdrive/sensord/sensors/constants.h" -#include "selfdrive/sensord/sensors/sensor.h" +#include "common/swaglog.h" +#include "system/sensord/sensors/constants.h" +#include "system/sensord/sensors/sensor.h" int16_t read_12_bit(uint8_t lsb, uint8_t msb); int16_t read_16_bit(uint8_t lsb, uint8_t msb); @@ -33,4 +33,18 @@ public: virtual int init() = 0; virtual bool get_event(MessageBuilder &msg, uint64_t ts = 0) = 0; virtual int shutdown() = 0; + + int verify_chip_id(uint8_t address, const std::vector &expected_ids) { + uint8_t chip_id = 0; + int ret = read_register(address, &chip_id, 1); + if (ret < 0) { + LOGE("Reading chip ID failed: %d", ret); + return -1; + } + for (int i = 0; i < expected_ids.size(); ++i) { + if (chip_id == expected_ids[i]) return chip_id; + } + LOGE("Chip ID wrong. Got: %d, Expected %d", chip_id, expected_ids[0]); + return -1; + } }; diff --git a/selfdrive/sensord/sensors/light_sensor.cc b/system/sensord/sensors/light_sensor.cc similarity index 92% rename from selfdrive/sensord/sensors/light_sensor.cc rename to system/sensord/sensors/light_sensor.cc index 58c602ea39..99e321b47d 100644 --- a/selfdrive/sensord/sensors/light_sensor.cc +++ b/system/sensord/sensors/light_sensor.cc @@ -3,7 +3,7 @@ #include #include "common/timing.h" -#include "selfdrive/sensord/sensors/constants.h" +#include "system/sensord/sensors/constants.h" LightSensor::LightSensor(std::string filename) : FileSensor(filename) {} diff --git a/selfdrive/sensord/sensors/light_sensor.h b/system/sensord/sensors/light_sensor.h similarity index 100% rename from selfdrive/sensord/sensors/light_sensor.h rename to system/sensord/sensors/light_sensor.h diff --git a/system/sensord/sensors/lsm6ds3_accel.cc b/system/sensord/sensors/lsm6ds3_accel.cc new file mode 100644 index 0000000000..c8eeb6e5dc --- /dev/null +++ b/system/sensord/sensors/lsm6ds3_accel.cc @@ -0,0 +1,250 @@ +#include "lsm6ds3_accel.h" + +#include +#include +#include + +#include "common/swaglog.h" +#include "common/timing.h" +#include "common/util.h" + +LSM6DS3_Accel::LSM6DS3_Accel(I2CBus *bus, int gpio_nr, bool shared_gpio) : + I2CSensor(bus, gpio_nr, shared_gpio) {} + +void LSM6DS3_Accel::wait_for_data_ready() { + uint8_t drdy = 0; + uint8_t buffer[6]; + + do { + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_ACCEL_DRDY_XLDA; + } while (drdy == 0); + + read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); +} + +void LSM6DS3_Accel::read_and_avg_data(float* out_buf) { + uint8_t drdy = 0; + uint8_t buffer[6]; + + float scaling = 0.061f; + if (source == cereal::SensorEventData::SensorSource::LSM6DS3TRC) { + scaling = 0.122f; + } + + for (int i = 0; i < 5; i++) { + do { + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_ACCEL_DRDY_XLDA; + } while (drdy == 0); + + int len = read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + for (int j = 0; j < 3; j++) { + out_buf[j] += (float)read_16_bit(buffer[j*2], buffer[j*2+1]) * scaling; + } + } + + for (int i = 0; i < 3; i++) { + out_buf[i] /= 5.0f; + } +} + +int LSM6DS3_Accel::self_test(int test_type) { + float val_st_off[3] = {0}; + float val_st_on[3] = {0}; + float test_val[3] = {0}; + uint8_t ODR_FS_MO = LSM6DS3_ACCEL_ODR_52HZ; // full scale: +-2g, ODR: 52Hz + + // prepare sensor for self-test + + // enable block data update and automatic increment + int ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL3_C, LSM6DS3_ACCEL_IF_INC_BDU); + if (ret < 0) { + return ret; + } + + if (source == cereal::SensorEventData::SensorSource::LSM6DS3TRC) { + ODR_FS_MO = LSM6DS3_ACCEL_FS_4G | LSM6DS3_ACCEL_ODR_52HZ; + } + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, ODR_FS_MO); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(100); + wait_for_data_ready(); + read_and_avg_data(val_st_off); + + // enable Self Test positive (or negative) + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL5_C, test_type); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(100); + wait_for_data_ready(); + read_and_avg_data(val_st_on); + + // disable sensor + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, 0); + if (ret < 0) { + return ret; + } + + // disable self test + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL5_C, 0); + if (ret < 0) { + return ret; + } + + // calculate the mg values for self test + for (int i = 0; i < 3; i++) { + test_val[i] = fabs(val_st_on[i] - val_st_off[i]); + } + + // verify test result + for (int i = 0; i < 3; i++) { + if ((LSM6DS3_ACCEL_MIN_ST_LIMIT_mg > test_val[i]) || + (test_val[i] > LSM6DS3_ACCEL_MAX_ST_LIMIT_mg)) { + return -1; + } + } + + return ret; +} + +int LSM6DS3_Accel::init() { + uint8_t value = 0; + bool do_self_test = false; + + const char* env_lsm_selftest =env_lsm_selftest = std::getenv("LSM_SELF_TEST"); + if (env_lsm_selftest != nullptr && strncmp(env_lsm_selftest, "1", 1) == 0) { + do_self_test = true; + } + + int ret = verify_chip_id(LSM6DS3_ACCEL_I2C_REG_ID, {LSM6DS3_ACCEL_CHIP_ID, LSM6DS3TRC_ACCEL_CHIP_ID}); + if (ret == -1) return -1; + + if (ret == LSM6DS3TRC_ACCEL_CHIP_ID) { + source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; + } + + ret = self_test(LSM6DS3_ACCEL_POSITIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 accel positive self-test failed!"); + if (do_self_test) goto fail; + } + + ret = self_test(LSM6DS3_ACCEL_NEGATIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 accel negative self-test failed!"); + if (do_self_test) goto fail; + } + + ret = init_gpio(); + if (ret < 0) { + goto fail; + } + + // enable continuous update, and automatic increase + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL3_C, LSM6DS3_ACCEL_IF_INC); + if (ret < 0) { + goto fail; + } + + // TODO: set scale and bandwidth. Default is +- 2G, 50 Hz + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, LSM6DS3_ACCEL_ODR_104HZ); + if (ret < 0) { + goto fail; + } + + ret = set_register(LSM6DS3_ACCEL_I2C_REG_DRDY_CFG, LSM6DS3_ACCEL_DRDY_PULSE_MODE); + if (ret < 0) { + goto fail; + } + + // enable data ready interrupt for accel on INT1 + // (without resetting existing interrupts) + ret = read_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, &value, 1); + if (ret < 0) { + goto fail; + } + + value |= LSM6DS3_ACCEL_INT1_DRDY_XL; + ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value); + +fail: + return ret; +} + +int LSM6DS3_Accel::shutdown() { + int ret = 0; + + // disable data ready interrupt for accel on INT1 + uint8_t value = 0; + ret = read_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, &value, 1); + if (ret < 0) { + goto fail; + } + + value &= ~(LSM6DS3_ACCEL_INT1_DRDY_XL); + ret = set_register(LSM6DS3_ACCEL_I2C_REG_INT1_CTRL, value); + if (ret < 0) { + LOGE("Could not disable lsm6ds3 acceleration interrupt!") + goto fail; + } + + // enable power-down mode + value = 0; + ret = read_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, &value, 1); + if (ret < 0) { + goto fail; + } + + value &= 0x0F; + ret = set_register(LSM6DS3_ACCEL_I2C_REG_CTRL1_XL, value); + if (ret < 0) { + LOGE("Could not power-down lsm6ds3 accelerometer!") + goto fail; + } + +fail: + return ret; +} + +bool LSM6DS3_Accel::get_event(MessageBuilder &msg, uint64_t ts) { + + // INT1 shared with gyro, check STATUS_REG who triggered + uint8_t status_reg = 0; + read_register(LSM6DS3_ACCEL_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); + if ((status_reg & LSM6DS3_ACCEL_DRDY_XLDA) == 0) { + return false; + } + + uint8_t buffer[6]; + int len = read_register(LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + float scale = 9.81 * 2.0f / (1 << 15); + float x = read_16_bit(buffer[0], buffer[1]) * scale; + float y = read_16_bit(buffer[2], buffer[3]) * scale; + float z = read_16_bit(buffer[4], buffer[5]) * scale; + + auto event = msg.initEvent().initAccelerometer(); + event.setSource(source); + event.setVersion(1); + event.setSensor(SENSOR_ACCELEROMETER); + event.setType(SENSOR_TYPE_ACCELEROMETER); + event.setTimestamp(ts); + + float xyz[] = {y, -x, z}; + auto svec = event.initAcceleration(); + svec.setV(xyz); + svec.setStatus(true); + + return true; +} diff --git a/selfdrive/sensord/sensors/lsm6ds3_accel.h b/system/sensord/sensors/lsm6ds3_accel.h similarity index 58% rename from selfdrive/sensord/sensors/lsm6ds3_accel.h rename to system/sensord/sensors/lsm6ds3_accel.h index 84084fc916..69667cb759 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_accel.h +++ b/system/sensord/sensors/lsm6ds3_accel.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define LSM6DS3_ACCEL_I2C_ADDR 0x6A @@ -10,21 +10,37 @@ #define LSM6DS3_ACCEL_I2C_REG_ID 0x0F #define LSM6DS3_ACCEL_I2C_REG_INT1_CTRL 0x0D #define LSM6DS3_ACCEL_I2C_REG_CTRL1_XL 0x10 +#define LSM6DS3_ACCEL_I2C_REG_CTRL3_C 0x12 +#define LSM6DS3_ACCEL_I2C_REG_CTRL5_C 0x14 +#define LSM6DS3_ACCEL_I2C_REG_CTR9_XL 0x18 #define LSM6DS3_ACCEL_I2C_REG_STAT_REG 0x1E #define LSM6DS3_ACCEL_I2C_REG_OUTX_L_XL 0x28 // Constants #define LSM6DS3_ACCEL_CHIP_ID 0x69 #define LSM6DS3TRC_ACCEL_CHIP_ID 0x6A +#define LSM6DS3_ACCEL_FS_4G (0b10 << 2) +#define LSM6DS3_ACCEL_ODR_52HZ (0b0011 << 4) #define LSM6DS3_ACCEL_ODR_104HZ (0b0100 << 4) #define LSM6DS3_ACCEL_INT1_DRDY_XL 0b1 #define LSM6DS3_ACCEL_DRDY_XLDA 0b1 #define LSM6DS3_ACCEL_DRDY_PULSE_MODE (1 << 7) - +#define LSM6DS3_ACCEL_IF_INC 0b00000100 +#define LSM6DS3_ACCEL_IF_INC_BDU 0b01000100 +#define LSM6DS3_ACCEL_XYZ_DEN 0b11100000 +#define LSM6DS3_ACCEL_POSITIVE_TEST 0b01 +#define LSM6DS3_ACCEL_NEGATIVE_TEST 0b10 +#define LSM6DS3_ACCEL_MIN_ST_LIMIT_mg 90.0f +#define LSM6DS3_ACCEL_MAX_ST_LIMIT_mg 1700.0f class LSM6DS3_Accel : public I2CSensor { uint8_t get_device_address() {return LSM6DS3_ACCEL_I2C_ADDR;} cereal::SensorEventData::SensorSource source = cereal::SensorEventData::SensorSource::LSM6DS3; + + // self test functions + int self_test(int test_type); + void wait_for_data_ready(); + void read_and_avg_data(float* val_st_off); public: LSM6DS3_Accel(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); diff --git a/system/sensord/sensors/lsm6ds3_gyro.cc b/system/sensord/sensors/lsm6ds3_gyro.cc new file mode 100644 index 0000000000..d9f5b4cb6a --- /dev/null +++ b/system/sensord/sensors/lsm6ds3_gyro.cc @@ -0,0 +1,233 @@ +#include "lsm6ds3_gyro.h" + +#include +#include +#include + +#include "common/swaglog.h" +#include "common/timing.h" +#include "common/util.h" + +#define DEG2RAD(x) ((x) * M_PI / 180.0) + +LSM6DS3_Gyro::LSM6DS3_Gyro(I2CBus *bus, int gpio_nr, bool shared_gpio) : + I2CSensor(bus, gpio_nr, shared_gpio) {} + +void LSM6DS3_Gyro::wait_for_data_ready() { + uint8_t drdy = 0; + uint8_t buffer[6]; + + do { + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_GYRO_DRDY_GDA; + } while (drdy == 0); + + read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); +} + +void LSM6DS3_Gyro::read_and_avg_data(float* out_buf) { + uint8_t drdy = 0; + uint8_t buffer[6]; + + for (int i = 0; i < 5; i++) { + do { + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &drdy, sizeof(drdy)); + drdy &= LSM6DS3_GYRO_DRDY_GDA; + } while (drdy == 0); + + int len = read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + for (int j = 0; j < 3; j++) { + out_buf[j] += (float)read_16_bit(buffer[j*2], buffer[j*2+1]) * 70.0f; + } + } + + // calculate the mg average values + for (int i = 0; i < 3; i++) { + out_buf[i] /= 5.0f; + } +} + +int LSM6DS3_Gyro::self_test(int test_type) { + float val_st_off[3] = {0}; + float val_st_on[3] = {0}; + float test_val[3] = {0}; + + // prepare sensor for self-test + + // full scale: 2000dps, ODR: 208Hz + int ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, LSM6DS3_GYRO_ODR_208HZ | LSM6DS3_GYRO_FS_2000dps); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(150); + wait_for_data_ready(); + read_and_avg_data(val_st_off); + + // enable Self Test positive (or negative) + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL5_C, test_type); + if (ret < 0) { + return ret; + } + + // wait for stable output, and discard first values + util::sleep_for(50); + wait_for_data_ready(); + read_and_avg_data(val_st_on); + + // disable sensor + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, 0); + if (ret < 0) { + return ret; + } + + // disable self test + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL5_C, 0); + if (ret < 0) { + return ret; + } + + // calculate the mg values for self test + for (int i = 0; i < 3; i++) { + test_val[i] = fabs(val_st_on[i] - val_st_off[i]); + } + + // verify test result + for (int i = 0; i < 3; i++) { + if ((LSM6DS3_GYRO_MIN_ST_LIMIT_mdps > test_val[i]) || + (test_val[i] > LSM6DS3_GYRO_MAX_ST_LIMIT_mdps)) { + return -1; + } + } + + return ret; +} + +int LSM6DS3_Gyro::init() { + uint8_t value = 0; + bool do_self_test = false; + + const char* env_lsm_selftest =env_lsm_selftest = std::getenv("LSM_SELF_TEST"); + if (env_lsm_selftest != nullptr && strncmp(env_lsm_selftest, "1", 1) == 0) { + do_self_test = true; + } + + int ret = verify_chip_id(LSM6DS3_GYRO_I2C_REG_ID, {LSM6DS3_GYRO_CHIP_ID, LSM6DS3TRC_GYRO_CHIP_ID}); + if (ret == -1) return -1; + + if (ret == LSM6DS3TRC_GYRO_CHIP_ID) { + source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; + } + + ret = init_gpio(); + if (ret < 0) { + goto fail; + } + + ret = self_test(LSM6DS3_GYRO_POSITIVE_TEST); + if (ret < 0 ) { + LOGE("LSM6DS3 gyro positive self-test failed!"); + if (do_self_test) goto fail; + } + + ret = self_test(LSM6DS3_GYRO_NEGATIVE_TEST); + if (ret < 0) { + LOGE("LSM6DS3 gyro negative self-test failed!"); + if (do_self_test) goto fail; + } + + // TODO: set scale. Default is +- 250 deg/s + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, LSM6DS3_GYRO_ODR_104HZ); + if (ret < 0) { + goto fail; + } + + ret = set_register(LSM6DS3_GYRO_I2C_REG_DRDY_CFG, LSM6DS3_GYRO_DRDY_PULSE_MODE); + if (ret < 0) { + goto fail; + } + + // enable data ready interrupt for gyro on INT1 + // (without resetting existing interrupts) + ret = read_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, &value, 1); + if (ret < 0) { + goto fail; + } + + value |= LSM6DS3_GYRO_INT1_DRDY_G; + ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value); + +fail: + return ret; +} + +int LSM6DS3_Gyro::shutdown() { + int ret = 0; + + // disable data ready interrupt for gyro on INT1 + uint8_t value = 0; + ret = read_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, &value, 1); + if (ret < 0) { + goto fail; + } + + value &= ~(LSM6DS3_GYRO_INT1_DRDY_G); + ret = set_register(LSM6DS3_GYRO_I2C_REG_INT1_CTRL, value); + if (ret < 0) { + LOGE("Could not disable lsm6ds3 gyroscope interrupt!") + goto fail; + } + + // enable power-down mode + value = 0; + ret = read_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, &value, 1); + if (ret < 0) { + goto fail; + } + + value &= 0x0F; + ret = set_register(LSM6DS3_GYRO_I2C_REG_CTRL2_G, value); + if (ret < 0) { + LOGE("Could not power-down lsm6ds3 gyroscope!") + goto fail; + } + +fail: + return ret; +} + +bool LSM6DS3_Gyro::get_event(MessageBuilder &msg, uint64_t ts) { + + // INT1 shared with accel, check STATUS_REG who triggered + uint8_t status_reg = 0; + read_register(LSM6DS3_GYRO_I2C_REG_STAT_REG, &status_reg, sizeof(status_reg)); + if ((status_reg & LSM6DS3_GYRO_DRDY_GDA) == 0) { + return false; + } + + uint8_t buffer[6]; + int len = read_register(LSM6DS3_GYRO_I2C_REG_OUTX_L_G, buffer, sizeof(buffer)); + assert(len == sizeof(buffer)); + + float scale = 8.75 / 1000.0; + float x = DEG2RAD(read_16_bit(buffer[0], buffer[1]) * scale); + float y = DEG2RAD(read_16_bit(buffer[2], buffer[3]) * scale); + float z = DEG2RAD(read_16_bit(buffer[4], buffer[5]) * scale); + + auto event = msg.initEvent().initGyroscope(); + event.setSource(source); + event.setVersion(2); + event.setSensor(SENSOR_GYRO_UNCALIBRATED); + event.setType(SENSOR_TYPE_GYROSCOPE_UNCALIBRATED); + event.setTimestamp(ts); + + float xyz[] = {y, -x, z}; + auto svec = event.initGyroUncalibrated(); + svec.setV(xyz); + svec.setStatus(true); + + return true; +} diff --git a/selfdrive/sensord/sensors/lsm6ds3_gyro.h b/system/sensord/sensors/lsm6ds3_gyro.h similarity index 66% rename from selfdrive/sensord/sensors/lsm6ds3_gyro.h rename to system/sensord/sensors/lsm6ds3_gyro.h index 6c61ffcef2..adaae62dd2 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_gyro.h +++ b/system/sensord/sensors/lsm6ds3_gyro.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define LSM6DS3_GYRO_I2C_ADDR 0x6A @@ -10,21 +10,33 @@ #define LSM6DS3_GYRO_I2C_REG_ID 0x0F #define LSM6DS3_GYRO_I2C_REG_INT1_CTRL 0x0D #define LSM6DS3_GYRO_I2C_REG_CTRL2_G 0x11 +#define LSM6DS3_GYRO_I2C_REG_CTRL5_C 0x14 #define LSM6DS3_GYRO_I2C_REG_STAT_REG 0x1E #define LSM6DS3_GYRO_I2C_REG_OUTX_L_G 0x22 +#define LSM6DS3_GYRO_POSITIVE_TEST (0b01 << 2) +#define LSM6DS3_GYRO_NEGATIVE_TEST (0b11 << 2) // Constants #define LSM6DS3_GYRO_CHIP_ID 0x69 #define LSM6DS3TRC_GYRO_CHIP_ID 0x6A +#define LSM6DS3_GYRO_FS_2000dps (0b11 << 2) #define LSM6DS3_GYRO_ODR_104HZ (0b0100 << 4) +#define LSM6DS3_GYRO_ODR_208HZ (0b0101 << 4) #define LSM6DS3_GYRO_INT1_DRDY_G 0b10 #define LSM6DS3_GYRO_DRDY_GDA 0b10 #define LSM6DS3_GYRO_DRDY_PULSE_MODE (1 << 7) +#define LSM6DS3_GYRO_MIN_ST_LIMIT_mdps 150000.0f +#define LSM6DS3_GYRO_MAX_ST_LIMIT_mdps 700000.0f class LSM6DS3_Gyro : public I2CSensor { uint8_t get_device_address() {return LSM6DS3_GYRO_I2C_ADDR;} cereal::SensorEventData::SensorSource source = cereal::SensorEventData::SensorSource::LSM6DS3; + + // self test functions + int self_test(int test_type); + void wait_for_data_ready(); + void read_and_avg_data(float* val_st_off); public: LSM6DS3_Gyro(I2CBus *bus, int gpio_nr = 0, bool shared_gpio = false); int init(); diff --git a/selfdrive/sensord/sensors/lsm6ds3_temp.cc b/system/sensord/sensors/lsm6ds3_temp.cc similarity index 67% rename from selfdrive/sensord/sensors/lsm6ds3_temp.cc rename to system/sensord/sensors/lsm6ds3_temp.cc index 7e1c4d4c81..c2e2c83c1d 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_temp.cc +++ b/system/sensord/sensors/lsm6ds3_temp.cc @@ -8,27 +8,13 @@ LSM6DS3_Temp::LSM6DS3_Temp(I2CBus *bus) : I2CSensor(bus) {} int LSM6DS3_Temp::init() { - int ret = 0; - uint8_t buffer[1]; + int ret = verify_chip_id(LSM6DS3_TEMP_I2C_REG_ID, {LSM6DS3_TEMP_CHIP_ID, LSM6DS3TRC_TEMP_CHIP_ID}); + if (ret == -1) return -1; - ret = read_register(LSM6DS3_TEMP_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != LSM6DS3_TEMP_CHIP_ID && buffer[0] != LSM6DS3TRC_TEMP_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], LSM6DS3_TEMP_CHIP_ID); - ret = -1; - goto fail; - } - - if (buffer[0] == LSM6DS3TRC_TEMP_CHIP_ID) { + if (ret == LSM6DS3TRC_TEMP_CHIP_ID) { source = cereal::SensorEventData::SensorSource::LSM6DS3TRC; } - -fail: - return ret; + return 0; } bool LSM6DS3_Temp::get_event(MessageBuilder &msg, uint64_t ts) { diff --git a/selfdrive/sensord/sensors/lsm6ds3_temp.h b/system/sensord/sensors/lsm6ds3_temp.h similarity index 92% rename from selfdrive/sensord/sensors/lsm6ds3_temp.h rename to system/sensord/sensors/lsm6ds3_temp.h index 1d6bcc228a..1b5b621814 100644 --- a/selfdrive/sensord/sensors/lsm6ds3_temp.h +++ b/system/sensord/sensors/lsm6ds3_temp.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define LSM6DS3_TEMP_I2C_ADDR 0x6A diff --git a/selfdrive/sensord/sensors/mmc5603nj_magn.cc b/system/sensord/sensors/mmc5603nj_magn.cc similarity index 87% rename from selfdrive/sensord/sensors/mmc5603nj_magn.cc rename to system/sensord/sensors/mmc5603nj_magn.cc index 7a9b7a298b..cdb82a4a12 100644 --- a/selfdrive/sensord/sensors/mmc5603nj_magn.cc +++ b/system/sensord/sensors/mmc5603nj_magn.cc @@ -8,20 +8,8 @@ MMC5603NJ_Magn::MMC5603NJ_Magn(I2CBus *bus) : I2CSensor(bus) {} int MMC5603NJ_Magn::init() { - int ret = 0; - uint8_t buffer[1]; - - ret = read_register(MMC5603NJ_I2C_REG_ID, buffer, 1); - if(ret < 0) { - LOGE("Reading chip ID failed: %d", ret); - goto fail; - } - - if(buffer[0] != MMC5603NJ_CHIP_ID) { - LOGE("Chip ID wrong. Got: %d, Expected %d", buffer[0], MMC5603NJ_CHIP_ID); - ret = -1; - goto fail; - } + int ret = verify_chip_id(MMC5603NJ_I2C_REG_ID, {MMC5603NJ_CHIP_ID}); + if (ret == -1) return -1; // Set 100 Hz ret = set_register(MMC5603NJ_I2C_REG_ODR, 100); diff --git a/selfdrive/sensord/sensors/mmc5603nj_magn.h b/system/sensord/sensors/mmc5603nj_magn.h similarity index 94% rename from selfdrive/sensord/sensors/mmc5603nj_magn.h rename to system/sensord/sensors/mmc5603nj_magn.h index a364c7c37a..fce3f3fecb 100644 --- a/selfdrive/sensord/sensors/mmc5603nj_magn.h +++ b/system/sensord/sensors/mmc5603nj_magn.h @@ -1,6 +1,6 @@ #pragma once -#include "selfdrive/sensord/sensors/i2c_sensor.h" +#include "system/sensord/sensors/i2c_sensor.h" // Address of the chip on the bus #define MMC5603NJ_I2C_ADDR 0x30 diff --git a/selfdrive/sensord/sensors/sensor.h b/system/sensord/sensors/sensor.h similarity index 100% rename from selfdrive/sensord/sensors/sensor.h rename to system/sensord/sensors/sensor.h diff --git a/selfdrive/sensord/sensors_qcom2.cc b/system/sensord/sensors_qcom2.cc similarity index 90% rename from selfdrive/sensord/sensors_qcom2.cc rename to system/sensord/sensors_qcom2.cc index 2279cf2532..349c67f498 100644 --- a/selfdrive/sensord/sensors_qcom2.cc +++ b/system/sensord/sensors_qcom2.cc @@ -12,22 +12,21 @@ #include "common/swaglog.h" #include "common/timing.h" #include "common/util.h" -#include "selfdrive/sensord/sensors/bmx055_accel.h" -#include "selfdrive/sensord/sensors/bmx055_gyro.h" -#include "selfdrive/sensord/sensors/bmx055_magn.h" -#include "selfdrive/sensord/sensors/bmx055_temp.h" -#include "selfdrive/sensord/sensors/constants.h" -#include "selfdrive/sensord/sensors/light_sensor.h" -#include "selfdrive/sensord/sensors/lsm6ds3_accel.h" -#include "selfdrive/sensord/sensors/lsm6ds3_gyro.h" -#include "selfdrive/sensord/sensors/lsm6ds3_temp.h" -#include "selfdrive/sensord/sensors/mmc5603nj_magn.h" -#include "selfdrive/sensord/sensors/sensor.h" +#include "system/sensord/sensors/bmx055_accel.h" +#include "system/sensord/sensors/bmx055_gyro.h" +#include "system/sensord/sensors/bmx055_magn.h" +#include "system/sensord/sensors/bmx055_temp.h" +#include "system/sensord/sensors/constants.h" +#include "system/sensord/sensors/light_sensor.h" +#include "system/sensord/sensors/lsm6ds3_accel.h" +#include "system/sensord/sensors/lsm6ds3_gyro.h" +#include "system/sensord/sensors/lsm6ds3_temp.h" +#include "system/sensord/sensors/mmc5603nj_magn.h" +#include "system/sensord/sensors/sensor.h" #define I2C_BUS_IMU 1 ExitHandler do_exit; -std::mutex pm_mutex; uint64_t init_ts = 0; void interrupt_loop(std::vector& sensors, diff --git a/system/sensord/tests/__init__.py b/system/sensord/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/sensord/tests/test_pigeond.py b/system/sensord/tests/test_pigeond.py similarity index 100% rename from selfdrive/sensord/tests/test_pigeond.py rename to system/sensord/tests/test_pigeond.py diff --git a/selfdrive/sensord/tests/test_sensord.py b/system/sensord/tests/test_sensord.py similarity index 98% rename from selfdrive/sensord/tests/test_sensord.py rename to system/sensord/tests/test_sensord.py index 82bd28446b..c6fe33129a 100755 --- a/selfdrive/sensord/tests/test_sensord.py +++ b/system/sensord/tests/test_sensord.py @@ -104,6 +104,9 @@ class TestSensord(unittest.TestCase): # make sure gpiochip0 is readable HARDWARE.initialize_hardware() + # enable LSM self test + os.environ["LSM_SELF_TEST"] = "1" + # read initial sensor values every test case can use os.system("pkill -f ./_sensord") try: @@ -118,6 +121,8 @@ class TestSensord(unittest.TestCase): @classmethod def tearDownClass(cls): managed_processes["sensord"].stop() + if "LSM_SELF_TEST" in os.environ: + del os.environ['LSM_SELF_TEST'] def tearDown(self): managed_processes["sensord"].stop() diff --git a/selfdrive/sensord/tests/ttff_test.py b/system/sensord/tests/ttff_test.py similarity index 100% rename from selfdrive/sensord/tests/ttff_test.py rename to system/sensord/tests/ttff_test.py diff --git a/system/ubloxd/.gitignore b/system/ubloxd/.gitignore new file mode 100644 index 0000000000..05263ff67c --- /dev/null +++ b/system/ubloxd/.gitignore @@ -0,0 +1,2 @@ +ubloxd +tests/test_glonass_runner diff --git a/system/ubloxd/SConscript b/system/ubloxd/SConscript new file mode 100644 index 0000000000..fff0986efd --- /dev/null +++ b/system/ubloxd/SConscript @@ -0,0 +1,20 @@ +Import('env', 'common', 'cereal', 'messaging') + +loc_libs = [cereal, messaging, 'zmq', common, 'capnp', 'kj', 'kaitai', 'pthread'] + +if GetOption('kaitai'): + generated = Dir('generated').srcnode().abspath + cmd = f"kaitai-struct-compiler --target cpp_stl --outdir {generated} $SOURCES" + env.Command(['generated/ubx.cpp', 'generated/ubx.h'], 'ubx.ksy', cmd) + env.Command(['generated/gps.cpp', 'generated/gps.h'], 'gps.ksy', cmd) + glonass = env.Command(['generated/glonass.cpp', 'generated/glonass.h'], 'glonass.ksy', cmd) + + # kaitai issue: https://github.com/kaitai-io/kaitai_struct/issues/910 + patch = env.Command(None, 'glonass_fix.patch', 'git apply $SOURCES') + env.Depends(patch, glonass) + +glonass_obj = env.Object('generated/glonass.cpp') +env.Program("ubloxd", ["ubloxd.cc", "ublox_msg.cc", "generated/ubx.cpp", "generated/gps.cpp", glonass_obj], LIBS=loc_libs) + +if GetOption('test'): + env.Program("tests/test_glonass_runner", ['tests/test_glonass_runner.cc', 'tests/test_glonass_kaitai.cc', glonass_obj], LIBS=[loc_libs]) \ No newline at end of file diff --git a/system/ubloxd/generated/glonass.cpp b/system/ubloxd/generated/glonass.cpp new file mode 100644 index 0000000000..cd0f96ab68 --- /dev/null +++ b/system/ubloxd/generated/glonass.cpp @@ -0,0 +1,353 @@ +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "glonass.h" + +glonass_t::glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = this; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::_read() { + m_idle_chip = m__io->read_bits_int_be(1); + m_string_number = m__io->read_bits_int_be(4); + //m__io->align_to_byte(); + switch (string_number()) { + case 4: { + m_data = new string_4_t(m__io, this, m__root); + break; + } + case 1: { + m_data = new string_1_t(m__io, this, m__root); + break; + } + case 3: { + m_data = new string_3_t(m__io, this, m__root); + break; + } + case 5: { + m_data = new string_5_t(m__io, this, m__root); + break; + } + case 2: { + m_data = new string_2_t(m__io, this, m__root); + break; + } + default: { + m_data = new string_non_immediate_t(m__io, this, m__root); + break; + } + } + m_hamming_code = m__io->read_bits_int_be(8); + m_pad_1 = m__io->read_bits_int_be(11); + m_superframe_number = m__io->read_bits_int_be(16); + m_pad_2 = m__io->read_bits_int_be(8); + m_frame_number = m__io->read_bits_int_be(8); +} + +glonass_t::~glonass_t() { + _clean_up(); +} + +void glonass_t::_clean_up() { + if (m_data) { + delete m_data; m_data = 0; + } +} + +glonass_t::string_4_t::string_4_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_tau_n = false; + f_delta_tau_n = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_4_t::_read() { + m_tau_n_sign = m__io->read_bits_int_be(1); + m_tau_n_value = m__io->read_bits_int_be(21); + m_delta_tau_n_sign = m__io->read_bits_int_be(1); + m_delta_tau_n_value = m__io->read_bits_int_be(4); + m_e_n = m__io->read_bits_int_be(5); + m_not_used_1 = m__io->read_bits_int_be(14); + m_p4 = m__io->read_bits_int_be(1); + m_f_t = m__io->read_bits_int_be(4); + m_not_used_2 = m__io->read_bits_int_be(3); + m_n_t = m__io->read_bits_int_be(11); + m_n = m__io->read_bits_int_be(5); + m_m = m__io->read_bits_int_be(2); +} + +glonass_t::string_4_t::~string_4_t() { + _clean_up(); +} + +void glonass_t::string_4_t::_clean_up() { +} + +int32_t glonass_t::string_4_t::tau_n() { + if (f_tau_n) + return m_tau_n; + m_tau_n = ((tau_n_sign()) ? ((tau_n_value() * -1)) : (tau_n_value())); + f_tau_n = true; + return m_tau_n; +} + +int32_t glonass_t::string_4_t::delta_tau_n() { + if (f_delta_tau_n) + return m_delta_tau_n; + m_delta_tau_n = ((delta_tau_n_sign()) ? ((delta_tau_n_value() * -1)) : (delta_tau_n_value())); + f_delta_tau_n = true; + return m_delta_tau_n; +} + +glonass_t::string_non_immediate_t::string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_non_immediate_t::_read() { + m_data_1 = m__io->read_bits_int_be(64); + m_data_2 = m__io->read_bits_int_be(8); +} + +glonass_t::string_non_immediate_t::~string_non_immediate_t() { + _clean_up(); +} + +void glonass_t::string_non_immediate_t::_clean_up() { +} + +glonass_t::string_5_t::string_5_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_5_t::_read() { + m_n_a = m__io->read_bits_int_be(11); + m_tau_c = m__io->read_bits_int_be(32); + m_not_used = m__io->read_bits_int_be(1); + m_n_4 = m__io->read_bits_int_be(5); + m_tau_gps = m__io->read_bits_int_be(22); + m_l_n = m__io->read_bits_int_be(1); +} + +glonass_t::string_5_t::~string_5_t() { + _clean_up(); +} + +void glonass_t::string_5_t::_clean_up() { +} + +glonass_t::string_1_t::string_1_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_x_vel = false; + f_x_accel = false; + f_x = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_1_t::_read() { + m_not_used = m__io->read_bits_int_be(2); + m_p1 = m__io->read_bits_int_be(2); + m_t_k = m__io->read_bits_int_be(12); + m_x_vel_sign = m__io->read_bits_int_be(1); + m_x_vel_value = m__io->read_bits_int_be(23); + m_x_accel_sign = m__io->read_bits_int_be(1); + m_x_accel_value = m__io->read_bits_int_be(4); + m_x_sign = m__io->read_bits_int_be(1); + m_x_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_1_t::~string_1_t() { + _clean_up(); +} + +void glonass_t::string_1_t::_clean_up() { +} + +int32_t glonass_t::string_1_t::x_vel() { + if (f_x_vel) + return m_x_vel; + m_x_vel = ((x_vel_sign()) ? ((x_vel_value() * -1)) : (x_vel_value())); + f_x_vel = true; + return m_x_vel; +} + +int32_t glonass_t::string_1_t::x_accel() { + if (f_x_accel) + return m_x_accel; + m_x_accel = ((x_accel_sign()) ? ((x_accel_value() * -1)) : (x_accel_value())); + f_x_accel = true; + return m_x_accel; +} + +int32_t glonass_t::string_1_t::x() { + if (f_x) + return m_x; + m_x = ((x_sign()) ? ((x_value() * -1)) : (x_value())); + f_x = true; + return m_x; +} + +glonass_t::string_2_t::string_2_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_y_vel = false; + f_y_accel = false; + f_y = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_2_t::_read() { + m_b_n = m__io->read_bits_int_be(3); + m_p2 = m__io->read_bits_int_be(1); + m_t_b = m__io->read_bits_int_be(7); + m_not_used = m__io->read_bits_int_be(5); + m_y_vel_sign = m__io->read_bits_int_be(1); + m_y_vel_value = m__io->read_bits_int_be(23); + m_y_accel_sign = m__io->read_bits_int_be(1); + m_y_accel_value = m__io->read_bits_int_be(4); + m_y_sign = m__io->read_bits_int_be(1); + m_y_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_2_t::~string_2_t() { + _clean_up(); +} + +void glonass_t::string_2_t::_clean_up() { +} + +int32_t glonass_t::string_2_t::y_vel() { + if (f_y_vel) + return m_y_vel; + m_y_vel = ((y_vel_sign()) ? ((y_vel_value() * -1)) : (y_vel_value())); + f_y_vel = true; + return m_y_vel; +} + +int32_t glonass_t::string_2_t::y_accel() { + if (f_y_accel) + return m_y_accel; + m_y_accel = ((y_accel_sign()) ? ((y_accel_value() * -1)) : (y_accel_value())); + f_y_accel = true; + return m_y_accel; +} + +int32_t glonass_t::string_2_t::y() { + if (f_y) + return m_y; + m_y = ((y_sign()) ? ((y_value() * -1)) : (y_value())); + f_y = true; + return m_y; +} + +glonass_t::string_3_t::string_3_t(kaitai::kstream* p__io, glonass_t* p__parent, glonass_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + f_gamma_n = false; + f_z_vel = false; + f_z_accel = false; + f_z = false; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void glonass_t::string_3_t::_read() { + m_p3 = m__io->read_bits_int_be(1); + m_gamma_n_sign = m__io->read_bits_int_be(1); + m_gamma_n_value = m__io->read_bits_int_be(10); + m_not_used = m__io->read_bits_int_be(1); + m_p = m__io->read_bits_int_be(2); + m_l_n = m__io->read_bits_int_be(1); + m_z_vel_sign = m__io->read_bits_int_be(1); + m_z_vel_value = m__io->read_bits_int_be(23); + m_z_accel_sign = m__io->read_bits_int_be(1); + m_z_accel_value = m__io->read_bits_int_be(4); + m_z_sign = m__io->read_bits_int_be(1); + m_z_value = m__io->read_bits_int_be(26); +} + +glonass_t::string_3_t::~string_3_t() { + _clean_up(); +} + +void glonass_t::string_3_t::_clean_up() { +} + +int32_t glonass_t::string_3_t::gamma_n() { + if (f_gamma_n) + return m_gamma_n; + m_gamma_n = ((gamma_n_sign()) ? ((gamma_n_value() * -1)) : (gamma_n_value())); + f_gamma_n = true; + return m_gamma_n; +} + +int32_t glonass_t::string_3_t::z_vel() { + if (f_z_vel) + return m_z_vel; + m_z_vel = ((z_vel_sign()) ? ((z_vel_value() * -1)) : (z_vel_value())); + f_z_vel = true; + return m_z_vel; +} + +int32_t glonass_t::string_3_t::z_accel() { + if (f_z_accel) + return m_z_accel; + m_z_accel = ((z_accel_sign()) ? ((z_accel_value() * -1)) : (z_accel_value())); + f_z_accel = true; + return m_z_accel; +} + +int32_t glonass_t::string_3_t::z() { + if (f_z) + return m_z; + m_z = ((z_sign()) ? ((z_value() * -1)) : (z_value())); + f_z = true; + return m_z; +} diff --git a/system/ubloxd/generated/glonass.h b/system/ubloxd/generated/glonass.h new file mode 100644 index 0000000000..19867ba22b --- /dev/null +++ b/system/ubloxd/generated/glonass.h @@ -0,0 +1,375 @@ +#ifndef GLONASS_H_ +#define GLONASS_H_ + +// This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild + +#include "kaitai/kaitaistruct.h" +#include + +#if KAITAI_STRUCT_VERSION < 9000L +#error "Incompatible Kaitai Struct C++/STL API: version 0.9 or later is required" +#endif + +class glonass_t : public kaitai::kstruct { + +public: + class string_4_t; + class string_non_immediate_t; + class string_5_t; + class string_1_t; + class string_2_t; + class string_3_t; + + glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, glonass_t* p__root = 0); + +private: + void _read(); + void _clean_up(); + +public: + ~glonass_t(); + + class string_4_t : public kaitai::kstruct { + + public: + + string_4_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_4_t(); + + private: + bool f_tau_n; + int32_t m_tau_n; + + public: + int32_t tau_n(); + + private: + bool f_delta_tau_n; + int32_t m_delta_tau_n; + + public: + int32_t delta_tau_n(); + + private: + bool m_tau_n_sign; + uint64_t m_tau_n_value; + bool m_delta_tau_n_sign; + uint64_t m_delta_tau_n_value; + uint64_t m_e_n; + uint64_t m_not_used_1; + bool m_p4; + uint64_t m_f_t; + uint64_t m_not_used_2; + uint64_t m_n_t; + uint64_t m_n; + uint64_t m_m; + glonass_t* m__root; + glonass_t* m__parent; + + public: + bool tau_n_sign() const { return m_tau_n_sign; } + uint64_t tau_n_value() const { return m_tau_n_value; } + bool delta_tau_n_sign() const { return m_delta_tau_n_sign; } + uint64_t delta_tau_n_value() const { return m_delta_tau_n_value; } + uint64_t e_n() const { return m_e_n; } + uint64_t not_used_1() const { return m_not_used_1; } + bool p4() const { return m_p4; } + uint64_t f_t() const { return m_f_t; } + uint64_t not_used_2() const { return m_not_used_2; } + uint64_t n_t() const { return m_n_t; } + uint64_t n() const { return m_n; } + uint64_t m() const { return m_m; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_non_immediate_t : public kaitai::kstruct { + + public: + + string_non_immediate_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_non_immediate_t(); + + private: + uint64_t m_data_1; + uint64_t m_data_2; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t data_1() const { return m_data_1; } + uint64_t data_2() const { return m_data_2; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_5_t : public kaitai::kstruct { + + public: + + string_5_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_5_t(); + + private: + uint64_t m_n_a; + uint64_t m_tau_c; + bool m_not_used; + uint64_t m_n_4; + uint64_t m_tau_gps; + bool m_l_n; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t n_a() const { return m_n_a; } + uint64_t tau_c() const { return m_tau_c; } + bool not_used() const { return m_not_used; } + uint64_t n_4() const { return m_n_4; } + uint64_t tau_gps() const { return m_tau_gps; } + bool l_n() const { return m_l_n; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_1_t : public kaitai::kstruct { + + public: + + string_1_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_1_t(); + + private: + bool f_x_vel; + int32_t m_x_vel; + + public: + int32_t x_vel(); + + private: + bool f_x_accel; + int32_t m_x_accel; + + public: + int32_t x_accel(); + + private: + bool f_x; + int32_t m_x; + + public: + int32_t x(); + + private: + uint64_t m_not_used; + uint64_t m_p1; + uint64_t m_t_k; + bool m_x_vel_sign; + uint64_t m_x_vel_value; + bool m_x_accel_sign; + uint64_t m_x_accel_value; + bool m_x_sign; + uint64_t m_x_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t not_used() const { return m_not_used; } + uint64_t p1() const { return m_p1; } + uint64_t t_k() const { return m_t_k; } + bool x_vel_sign() const { return m_x_vel_sign; } + uint64_t x_vel_value() const { return m_x_vel_value; } + bool x_accel_sign() const { return m_x_accel_sign; } + uint64_t x_accel_value() const { return m_x_accel_value; } + bool x_sign() const { return m_x_sign; } + uint64_t x_value() const { return m_x_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_2_t : public kaitai::kstruct { + + public: + + string_2_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_2_t(); + + private: + bool f_y_vel; + int32_t m_y_vel; + + public: + int32_t y_vel(); + + private: + bool f_y_accel; + int32_t m_y_accel; + + public: + int32_t y_accel(); + + private: + bool f_y; + int32_t m_y; + + public: + int32_t y(); + + private: + uint64_t m_b_n; + bool m_p2; + uint64_t m_t_b; + uint64_t m_not_used; + bool m_y_vel_sign; + uint64_t m_y_vel_value; + bool m_y_accel_sign; + uint64_t m_y_accel_value; + bool m_y_sign; + uint64_t m_y_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + uint64_t b_n() const { return m_b_n; } + bool p2() const { return m_p2; } + uint64_t t_b() const { return m_t_b; } + uint64_t not_used() const { return m_not_used; } + bool y_vel_sign() const { return m_y_vel_sign; } + uint64_t y_vel_value() const { return m_y_vel_value; } + bool y_accel_sign() const { return m_y_accel_sign; } + uint64_t y_accel_value() const { return m_y_accel_value; } + bool y_sign() const { return m_y_sign; } + uint64_t y_value() const { return m_y_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + + class string_3_t : public kaitai::kstruct { + + public: + + string_3_t(kaitai::kstream* p__io, glonass_t* p__parent = 0, glonass_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~string_3_t(); + + private: + bool f_gamma_n; + int32_t m_gamma_n; + + public: + int32_t gamma_n(); + + private: + bool f_z_vel; + int32_t m_z_vel; + + public: + int32_t z_vel(); + + private: + bool f_z_accel; + int32_t m_z_accel; + + public: + int32_t z_accel(); + + private: + bool f_z; + int32_t m_z; + + public: + int32_t z(); + + private: + bool m_p3; + bool m_gamma_n_sign; + uint64_t m_gamma_n_value; + bool m_not_used; + uint64_t m_p; + bool m_l_n; + bool m_z_vel_sign; + uint64_t m_z_vel_value; + bool m_z_accel_sign; + uint64_t m_z_accel_value; + bool m_z_sign; + uint64_t m_z_value; + glonass_t* m__root; + glonass_t* m__parent; + + public: + bool p3() const { return m_p3; } + bool gamma_n_sign() const { return m_gamma_n_sign; } + uint64_t gamma_n_value() const { return m_gamma_n_value; } + bool not_used() const { return m_not_used; } + uint64_t p() const { return m_p; } + bool l_n() const { return m_l_n; } + bool z_vel_sign() const { return m_z_vel_sign; } + uint64_t z_vel_value() const { return m_z_vel_value; } + bool z_accel_sign() const { return m_z_accel_sign; } + uint64_t z_accel_value() const { return m_z_accel_value; } + bool z_sign() const { return m_z_sign; } + uint64_t z_value() const { return m_z_value; } + glonass_t* _root() const { return m__root; } + glonass_t* _parent() const { return m__parent; } + }; + +private: + bool m_idle_chip; + uint64_t m_string_number; + kaitai::kstruct* m_data; + uint64_t m_hamming_code; + uint64_t m_pad_1; + uint64_t m_superframe_number; + uint64_t m_pad_2; + uint64_t m_frame_number; + glonass_t* m__root; + kaitai::kstruct* m__parent; + +public: + bool idle_chip() const { return m_idle_chip; } + uint64_t string_number() const { return m_string_number; } + kaitai::kstruct* data() const { return m_data; } + uint64_t hamming_code() const { return m_hamming_code; } + uint64_t pad_1() const { return m_pad_1; } + uint64_t superframe_number() const { return m_superframe_number; } + uint64_t pad_2() const { return m_pad_2; } + uint64_t frame_number() const { return m_frame_number; } + glonass_t* _root() const { return m__root; } + kaitai::kstruct* _parent() const { return m__parent; } +}; + +#endif // GLONASS_H_ diff --git a/selfdrive/locationd/generated/gps.cpp b/system/ubloxd/generated/gps.cpp similarity index 97% rename from selfdrive/locationd/generated/gps.cpp rename to system/ubloxd/generated/gps.cpp index 9b020735bb..8e1cb85b95 100644 --- a/selfdrive/locationd/generated/gps.cpp +++ b/system/ubloxd/generated/gps.cpp @@ -274,9 +274,9 @@ gps_t::tlm_t::tlm_t(kaitai::kstream* p__io, gps_t* p__parent, gps_t* p__root) : } void gps_t::tlm_t::_read() { - m_magic = m__io->read_bytes(1); - if (!(magic() == std::string("\x8B", 1))) { - throw kaitai::validation_not_equal_error(std::string("\x8B", 1), magic(), _io(), std::string("/types/tlm/seq/0")); + m_preamble = m__io->read_bytes(1); + if (!(preamble() == std::string("\x8B", 1))) { + throw kaitai::validation_not_equal_error(std::string("\x8B", 1), preamble(), _io(), std::string("/types/tlm/seq/0")); } m_tlm = m__io->read_bits_int_be(14); m_integrity_status = m__io->read_bits_int_be(1); diff --git a/selfdrive/locationd/generated/gps.h b/system/ubloxd/generated/gps.h similarity index 99% rename from selfdrive/locationd/generated/gps.h rename to system/ubloxd/generated/gps.h index 293e2e4a05..9dfc5031f5 100644 --- a/selfdrive/locationd/generated/gps.h +++ b/system/ubloxd/generated/gps.h @@ -273,7 +273,7 @@ public: ~tlm_t(); private: - std::string m_magic; + std::string m_preamble; uint64_t m_tlm; bool m_integrity_status; bool m_reserved; @@ -281,7 +281,7 @@ public: gps_t* m__parent; public: - std::string magic() const { return m_magic; } + std::string preamble() const { return m_preamble; } uint64_t tlm() const { return m_tlm; } bool integrity_status() const { return m_integrity_status; } bool reserved() const { return m_reserved; } diff --git a/selfdrive/locationd/generated/ubx.cpp b/system/ubloxd/generated/ubx.cpp similarity index 67% rename from selfdrive/locationd/generated/ubx.cpp rename to system/ubloxd/generated/ubx.cpp index 5e743e1ee7..81b82ccafc 100644 --- a/selfdrive/locationd/generated/ubx.cpp +++ b/system/ubloxd/generated/ubx.cpp @@ -40,6 +40,11 @@ void ubx_t::_read() { m_body = new rxm_sfrbx_t(m__io, this, m__root); break; } + case 309: { + n_body = false; + m_body = new nav_sat_t(m__io, this, m__root); + break; + } case 2571: { n_body = false; m_body = new mon_hw2_t(m__io, this, m__root); @@ -70,9 +75,9 @@ void ubx_t::_clean_up() { ubx_t::rxm_rawx_t::rxm_rawx_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; - m_measurements = 0; - m__raw_measurements = 0; - m__io__raw_measurements = 0; + m_meas = 0; + m__raw_meas = 0; + m__io__raw_meas = 0; try { _read(); @@ -89,18 +94,15 @@ void ubx_t::rxm_rawx_t::_read() { m_num_meas = m__io->read_u1(); m_rec_stat = m__io->read_u1(); m_reserved1 = m__io->read_bytes(3); - int l_measurements = num_meas(); - m__raw_measurements = new std::vector(); - m__raw_measurements->reserve(l_measurements); - m__io__raw_measurements = new std::vector(); - m__io__raw_measurements->reserve(l_measurements); - m_measurements = new std::vector(); - m_measurements->reserve(l_measurements); - for (int i = 0; i < l_measurements; i++) { - m__raw_measurements->push_back(m__io->read_bytes(32)); - kaitai::kstream* io__raw_measurements = new kaitai::kstream(m__raw_measurements->at(m__raw_measurements->size() - 1)); - m__io__raw_measurements->push_back(io__raw_measurements); - m_measurements->push_back(new meas_t(io__raw_measurements, this, m__root)); + m__raw_meas = new std::vector(); + m__io__raw_meas = new std::vector(); + m_meas = new std::vector(); + const int l_meas = num_meas(); + for (int i = 0; i < l_meas; i++) { + m__raw_meas->push_back(m__io->read_bytes(32)); + kaitai::kstream* io__raw_meas = new kaitai::kstream(m__raw_meas->at(m__raw_meas->size() - 1)); + m__io__raw_meas->push_back(io__raw_meas); + m_meas->push_back(new measurement_t(io__raw_meas, this, m__root)); } } @@ -109,24 +111,24 @@ ubx_t::rxm_rawx_t::~rxm_rawx_t() { } void ubx_t::rxm_rawx_t::_clean_up() { - if (m__raw_measurements) { - delete m__raw_measurements; m__raw_measurements = 0; + if (m__raw_meas) { + delete m__raw_meas; m__raw_meas = 0; } - if (m__io__raw_measurements) { - for (std::vector::iterator it = m__io__raw_measurements->begin(); it != m__io__raw_measurements->end(); ++it) { + if (m__io__raw_meas) { + for (std::vector::iterator it = m__io__raw_meas->begin(); it != m__io__raw_meas->end(); ++it) { delete *it; } - delete m__io__raw_measurements; m__io__raw_measurements = 0; + delete m__io__raw_meas; m__io__raw_meas = 0; } - if (m_measurements) { - for (std::vector::iterator it = m_measurements->begin(); it != m_measurements->end(); ++it) { + if (m_meas) { + for (std::vector::iterator it = m_meas->begin(); it != m_meas->end(); ++it) { delete *it; } - delete m_measurements; m_measurements = 0; + delete m_meas; m_meas = 0; } } -ubx_t::rxm_rawx_t::meas_t::meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { +ubx_t::rxm_rawx_t::measurement_t::measurement_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; @@ -138,7 +140,7 @@ ubx_t::rxm_rawx_t::meas_t::meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__ } } -void ubx_t::rxm_rawx_t::meas_t::_read() { +void ubx_t::rxm_rawx_t::measurement_t::_read() { m_pr_mes = m__io->read_f8le(); m_cp_mes = m__io->read_f8le(); m_do_mes = m__io->read_f4le(); @@ -155,11 +157,11 @@ void ubx_t::rxm_rawx_t::meas_t::_read() { m_reserved3 = m__io->read_bytes(1); } -ubx_t::rxm_rawx_t::meas_t::~meas_t() { +ubx_t::rxm_rawx_t::measurement_t::~measurement_t() { _clean_up(); } -void ubx_t::rxm_rawx_t::meas_t::_clean_up() { +void ubx_t::rxm_rawx_t::measurement_t::_clean_up() { } ubx_t::rxm_sfrbx_t::rxm_sfrbx_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { @@ -184,9 +186,8 @@ void ubx_t::rxm_sfrbx_t::_read() { m_reserved2 = m__io->read_bytes(1); m_version = m__io->read_u1(); m_reserved3 = m__io->read_bytes(1); - int l_body = num_words(); m_body = new std::vector(); - m_body->reserve(l_body); + const int l_body = num_words(); for (int i = 0; i < l_body; i++) { m_body->push_back(m__io->read_u4le()); } @@ -202,6 +203,89 @@ void ubx_t::rxm_sfrbx_t::_clean_up() { } } +ubx_t::nav_sat_t::nav_sat_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + m_svs = 0; + m__raw_svs = 0; + m__io__raw_svs = 0; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ubx_t::nav_sat_t::_read() { + m_itow = m__io->read_u4le(); + m_version = m__io->read_u1(); + m_num_svs = m__io->read_u1(); + m_reserved = m__io->read_bytes(2); + m__raw_svs = new std::vector(); + m__io__raw_svs = new std::vector(); + m_svs = new std::vector(); + const int l_svs = num_svs(); + for (int i = 0; i < l_svs; i++) { + m__raw_svs->push_back(m__io->read_bytes(12)); + kaitai::kstream* io__raw_svs = new kaitai::kstream(m__raw_svs->at(m__raw_svs->size() - 1)); + m__io__raw_svs->push_back(io__raw_svs); + m_svs->push_back(new nav_t(io__raw_svs, this, m__root)); + } +} + +ubx_t::nav_sat_t::~nav_sat_t() { + _clean_up(); +} + +void ubx_t::nav_sat_t::_clean_up() { + if (m__raw_svs) { + delete m__raw_svs; m__raw_svs = 0; + } + if (m__io__raw_svs) { + for (std::vector::iterator it = m__io__raw_svs->begin(); it != m__io__raw_svs->end(); ++it) { + delete *it; + } + delete m__io__raw_svs; m__io__raw_svs = 0; + } + if (m_svs) { + for (std::vector::iterator it = m_svs->begin(); it != m_svs->end(); ++it) { + delete *it; + } + delete m_svs; m_svs = 0; + } +} + +ubx_t::nav_sat_t::nav_t::nav_t(kaitai::kstream* p__io, ubx_t::nav_sat_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { + m__parent = p__parent; + m__root = p__root; + + try { + _read(); + } catch(...) { + _clean_up(); + throw; + } +} + +void ubx_t::nav_sat_t::nav_t::_read() { + m_gnss_id = static_cast(m__io->read_u1()); + m_sv_id = m__io->read_u1(); + m_cno = m__io->read_u1(); + m_elev = m__io->read_s1(); + m_azim = m__io->read_s2le(); + m_pr_res = m__io->read_s2le(); + m_flags = m__io->read_u4le(); +} + +ubx_t::nav_sat_t::nav_t::~nav_t() { + _clean_up(); +} + +void ubx_t::nav_sat_t::nav_t::_clean_up() { +} + ubx_t::nav_pvt_t::nav_pvt_t(kaitai::kstream* p__io, ubx_t* p__parent, ubx_t* p__root) : kaitai::kstruct(p__io) { m__parent = p__parent; m__root = p__root; diff --git a/selfdrive/locationd/generated/ubx.h b/system/ubloxd/generated/ubx.h similarity index 80% rename from selfdrive/locationd/generated/ubx.h rename to system/ubloxd/generated/ubx.h index 6be4ce8c4b..022108489f 100644 --- a/selfdrive/locationd/generated/ubx.h +++ b/system/ubloxd/generated/ubx.h @@ -16,6 +16,7 @@ class ubx_t : public kaitai::kstruct { public: class rxm_rawx_t; class rxm_sfrbx_t; + class nav_sat_t; class nav_pvt_t; class mon_hw2_t; class mon_hw_t; @@ -42,7 +43,7 @@ public: class rxm_rawx_t : public kaitai::kstruct { public: - class meas_t; + class measurement_t; rxm_rawx_t(kaitai::kstream* p__io, ubx_t* p__parent = 0, ubx_t* p__root = 0); @@ -53,18 +54,18 @@ public: public: ~rxm_rawx_t(); - class meas_t : public kaitai::kstruct { + class measurement_t : public kaitai::kstruct { public: - meas_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent = 0, ubx_t* p__root = 0); + measurement_t(kaitai::kstream* p__io, ubx_t::rxm_rawx_t* p__parent = 0, ubx_t* p__root = 0); private: void _read(); void _clean_up(); public: - ~meas_t(); + ~measurement_t(); private: double m_pr_mes; @@ -110,11 +111,11 @@ public: uint8_t m_num_meas; uint8_t m_rec_stat; std::string m_reserved1; - std::vector* m_measurements; + std::vector* m_meas; ubx_t* m__root; ubx_t* m__parent; - std::vector* m__raw_measurements; - std::vector* m__io__raw_measurements; + std::vector* m__raw_meas; + std::vector* m__io__raw_meas; public: double rcv_tow() const { return m_rcv_tow; } @@ -123,11 +124,11 @@ public: uint8_t num_meas() const { return m_num_meas; } uint8_t rec_stat() const { return m_rec_stat; } std::string reserved1() const { return m_reserved1; } - std::vector* measurements() const { return m_measurements; } + std::vector* meas() const { return m_meas; } ubx_t* _root() const { return m__root; } ubx_t* _parent() const { return m__parent; } - std::vector* _raw_measurements() const { return m__raw_measurements; } - std::vector* _io__raw_measurements() const { return m__io__raw_measurements; } + std::vector* _raw_meas() const { return m__raw_meas; } + std::vector* _io__raw_meas() const { return m__io__raw_meas; } }; class rxm_sfrbx_t : public kaitai::kstruct { @@ -170,6 +171,79 @@ public: ubx_t* _parent() const { return m__parent; } }; + class nav_sat_t : public kaitai::kstruct { + + public: + class nav_t; + + nav_sat_t(kaitai::kstream* p__io, ubx_t* p__parent = 0, ubx_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nav_sat_t(); + + class nav_t : public kaitai::kstruct { + + public: + + nav_t(kaitai::kstream* p__io, ubx_t::nav_sat_t* p__parent = 0, ubx_t* p__root = 0); + + private: + void _read(); + void _clean_up(); + + public: + ~nav_t(); + + private: + gnss_type_t m_gnss_id; + uint8_t m_sv_id; + uint8_t m_cno; + int8_t m_elev; + int16_t m_azim; + int16_t m_pr_res; + uint32_t m_flags; + ubx_t* m__root; + ubx_t::nav_sat_t* m__parent; + + public: + gnss_type_t gnss_id() const { return m_gnss_id; } + uint8_t sv_id() const { return m_sv_id; } + uint8_t cno() const { return m_cno; } + int8_t elev() const { return m_elev; } + int16_t azim() const { return m_azim; } + int16_t pr_res() const { return m_pr_res; } + uint32_t flags() const { return m_flags; } + ubx_t* _root() const { return m__root; } + ubx_t::nav_sat_t* _parent() const { return m__parent; } + }; + + private: + uint32_t m_itow; + uint8_t m_version; + uint8_t m_num_svs; + std::string m_reserved; + std::vector* m_svs; + ubx_t* m__root; + ubx_t* m__parent; + std::vector* m__raw_svs; + std::vector* m__io__raw_svs; + + public: + uint32_t itow() const { return m_itow; } + uint8_t version() const { return m_version; } + uint8_t num_svs() const { return m_num_svs; } + std::string reserved() const { return m_reserved; } + std::vector* svs() const { return m_svs; } + ubx_t* _root() const { return m__root; } + ubx_t* _parent() const { return m__parent; } + std::vector* _raw_svs() const { return m__raw_svs; } + std::vector* _io__raw_svs() const { return m__io__raw_svs; } + }; + class nav_pvt_t : public kaitai::kstruct { public: diff --git a/system/ubloxd/glonass.ksy b/system/ubloxd/glonass.ksy new file mode 100644 index 0000000000..be99f6e497 --- /dev/null +++ b/system/ubloxd/glonass.ksy @@ -0,0 +1,176 @@ +# http://gauss.gge.unb.ca/GLONASS.ICD.pdf +# some variables are misprinted but good in the old doc +# https://www.unavco.org/help/glossary/docs/ICD_GLONASS_4.0_(1998)_en.pdf +meta: + id: glonass + endian: be + bit-endian: be +seq: + - id: idle_chip + type: b1 + - id: string_number + type: b4 + - id: data + type: + switch-on: string_number + cases: + 1: string_1 + 2: string_2 + 3: string_3 + 4: string_4 + 5: string_5 + _: string_non_immediate + - id: hamming_code + type: b8 + - id: pad_1 + type: b11 + - id: superframe_number + type: b16 + - id: pad_2 + type: b8 + - id: frame_number + type: b8 + +types: + string_1: + seq: + - id: not_used + type: b2 + - id: p1 + type: b2 + - id: t_k + type: b12 + - id: x_vel_sign + type: b1 + - id: x_vel_value + type: b23 + - id: x_accel_sign + type: b1 + - id: x_accel_value + type: b4 + - id: x_sign + type: b1 + - id: x_value + type: b26 + instances: + x_vel: + value: 'x_vel_sign ? (x_vel_value * (-1)) : x_vel_value' + x_accel: + value: 'x_accel_sign ? (x_accel_value * (-1)) : x_accel_value' + x: + value: 'x_sign ? (x_value * (-1)) : x_value' + string_2: + seq: + - id: b_n + type: b3 + - id: p2 + type: b1 + - id: t_b + type: b7 + - id: not_used + type: b5 + - id: y_vel_sign + type: b1 + - id: y_vel_value + type: b23 + - id: y_accel_sign + type: b1 + - id: y_accel_value + type: b4 + - id: y_sign + type: b1 + - id: y_value + type: b26 + instances: + y_vel: + value: 'y_vel_sign ? (y_vel_value * (-1)) : y_vel_value' + y_accel: + value: 'y_accel_sign ? (y_accel_value * (-1)) : y_accel_value' + y: + value: 'y_sign ? (y_value * (-1)) : y_value' + string_3: + seq: + - id: p3 + type: b1 + - id: gamma_n_sign + type: b1 + - id: gamma_n_value + type: b10 + - id: not_used + type: b1 + - id: p + type: b2 + - id: l_n + type: b1 + - id: z_vel_sign + type: b1 + - id: z_vel_value + type: b23 + - id: z_accel_sign + type: b1 + - id: z_accel_value + type: b4 + - id: z_sign + type: b1 + - id: z_value + type: b26 + instances: + gamma_n: + value: 'gamma_n_sign ? (gamma_n_value * (-1)) : gamma_n_value' + z_vel: + value: 'z_vel_sign ? (z_vel_value * (-1)) : z_vel_value' + z_accel: + value: 'z_accel_sign ? (z_accel_value * (-1)) : z_accel_value' + z: + value: 'z_sign ? (z_value * (-1)) : z_value' + string_4: + seq: + - id: tau_n_sign + type: b1 + - id: tau_n_value + type: b21 + - id: delta_tau_n_sign + type: b1 + - id: delta_tau_n_value + type: b4 + - id: e_n + type: b5 + - id: not_used_1 + type: b14 + - id: p4 + type: b1 + - id: f_t + type: b4 + - id: not_used_2 + type: b3 + - id: n_t + type: b11 + - id: n + type: b5 + - id: m + type: b2 + instances: + tau_n: + value: 'tau_n_sign ? (tau_n_value * (-1)) : tau_n_value' + delta_tau_n: + value: 'delta_tau_n_sign ? (delta_tau_n_value * (-1)) : delta_tau_n_value' + string_5: + seq: + - id: n_a + type: b11 + - id: tau_c + type: b32 + - id: not_used + type: b1 + - id: n_4 + type: b5 + - id: tau_gps + type: b22 + - id: l_n + type: b1 + string_non_immediate: + seq: + - id: data_1 + type: b64 + - id: data_2 + type: b8 diff --git a/system/ubloxd/glonass_fix.patch b/system/ubloxd/glonass_fix.patch new file mode 100644 index 0000000000..7eb973a348 --- /dev/null +++ b/system/ubloxd/glonass_fix.patch @@ -0,0 +1,13 @@ +diff --git a/system/ubloxd/generated/glonass.cpp b/system/ubloxd/generated/glonass.cpp +index 5b17bc327..b5c6aa610 100644 +--- a/system/ubloxd/generated/glonass.cpp ++++ b/system/ubloxd/generated/glonass.cpp +@@ -17,7 +17,7 @@ glonass_t::glonass_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, glonass + void glonass_t::_read() { + m_idle_chip = m__io->read_bits_int_be(1); + m_string_number = m__io->read_bits_int_be(4); +- m__io->align_to_byte(); ++ //m__io->align_to_byte(); + switch (string_number()) { + case 4: { + m_data = new string_4_t(m__io, this, m__root); diff --git a/selfdrive/locationd/gps.ksy b/system/ubloxd/gps.ksy similarity index 99% rename from selfdrive/locationd/gps.ksy rename to system/ubloxd/gps.ksy index 6f5cde316b..893ad1b25b 100644 --- a/selfdrive/locationd/gps.ksy +++ b/system/ubloxd/gps.ksy @@ -19,7 +19,7 @@ seq: types: tlm: seq: - - id: magic + - id: preamble contents: [0x8b] - id: tlm type: b14 diff --git a/selfdrive/locationd/test/print_gps_stats.py b/system/ubloxd/tests/print_gps_stats.py similarity index 100% rename from selfdrive/locationd/test/print_gps_stats.py rename to system/ubloxd/tests/print_gps_stats.py diff --git a/system/ubloxd/tests/test_glonass_kaitai.cc b/system/ubloxd/tests/test_glonass_kaitai.cc new file mode 100644 index 0000000000..5ad274142a --- /dev/null +++ b/system/ubloxd/tests/test_glonass_kaitai.cc @@ -0,0 +1,360 @@ +#include +#include +#include +#include +#include +#include + +#include "catch2/catch.hpp" +#include "system/ubloxd/generated/glonass.h" + +typedef std::vector> string_data; + +#define IDLE_CHIP_IDX 0 +#define STRING_NUMBER_IDX 1 +// string data 1-5 +#define HC_IDX 0 +#define PAD1_IDX 1 +#define SUPERFRAME_IDX 2 +#define PAD2_IDX 3 +#define FRAME_IDX 4 + +// Indexes for string number 1 +#define ST1_NU_IDX 2 +#define ST1_P1_IDX 3 +#define ST1_T_K_IDX 4 +#define ST1_X_VEL_S_IDX 5 +#define ST1_X_VEL_V_IDX 6 +#define ST1_X_ACCEL_S_IDX 7 +#define ST1_X_ACCEL_V_IDX 8 +#define ST1_X_S_IDX 9 +#define ST1_X_V_IDX 10 +#define ST1_HC_OFF 11 + +// Indexes for string number 2 +#define ST2_BN_IDX 2 +#define ST2_P2_IDX 3 +#define ST2_TB_IDX 4 +#define ST2_NU_IDX 5 +#define ST2_Y_VEL_S_IDX 6 +#define ST2_Y_VEL_V_IDX 7 +#define ST2_Y_ACCEL_S_IDX 8 +#define ST2_Y_ACCEL_V_IDX 9 +#define ST2_Y_S_IDX 10 +#define ST2_Y_V_IDX 11 +#define ST2_HC_OFF 12 + +// Indexes for string number 3 +#define ST3_P3_IDX 2 +#define ST3_GAMMA_N_S_IDX 3 +#define ST3_GAMMA_N_V_IDX 4 +#define ST3_NU_1_IDX 5 +#define ST3_P_IDX 6 +#define ST3_L_N_IDX 7 +#define ST3_Z_VEL_S_IDX 8 +#define ST3_Z_VEL_V_IDX 9 +#define ST3_Z_ACCEL_S_IDX 10 +#define ST3_Z_ACCEL_V_IDX 11 +#define ST3_Z_S_IDX 12 +#define ST3_Z_V_IDX 13 +#define ST3_HC_OFF 14 + +// Indexes for string number 4 +#define ST4_TAU_N_S_IDX 2 +#define ST4_TAU_N_V_IDX 3 +#define ST4_DELTA_TAU_N_S_IDX 4 +#define ST4_DELTA_TAU_N_V_IDX 5 +#define ST4_E_N_IDX 6 +#define ST4_NU_1_IDX 7 +#define ST4_P4_IDX 8 +#define ST4_F_T_IDX 9 +#define ST4_NU_2_IDX 10 +#define ST4_N_T_IDX 11 +#define ST4_N_IDX 12 +#define ST4_M_IDX 13 +#define ST4_HC_OFF 14 + +// Indexes for string number 5 +#define ST5_N_A_IDX 2 +#define ST5_TAU_C_IDX 3 +#define ST5_NU_IDX 4 +#define ST5_N_4_IDX 5 +#define ST5_TAU_GPS_IDX 6 +#define ST5_L_N_IDX 7 +#define ST5_HC_OFF 8 + +// Indexes for non immediate +#define ST6_DATA_1_IDX 2 +#define ST6_DATA_2_IDX 3 +#define ST6_HC_OFF 4 + + +std::string generate_inp_data(string_data& data) { + std::string inp_data = ""; + for (auto& [b, v] : data) { + std::string tmp = std::bitset<64>(v).to_string(); + inp_data += tmp.substr(64-b, b); + } + assert(inp_data.size() == 128); + + std::string string_data; + string_data.reserve(16); + for (int i = 0; i < 128; i+=8) { + std::string substr = inp_data.substr(i, 8); + string_data.push_back( (uint8_t)std::stoi(substr.c_str(), 0, 2)); + } + + return string_data; +} + +string_data generate_string_data(uint8_t string_number) { + + srand((unsigned)time(0)); + string_data data; // + data.push_back({1, 0}); // idle chip + data.push_back({4, string_number}); // string number + + if (string_number == 1) { + data.push_back({2, 3}); // not_used + data.push_back({2, 1}); // p1 + data.push_back({12, 113}); // t_k + data.push_back({1, rand() & 1}); // x_vel_sign + data.push_back({23, 7122}); // x_vel_value + data.push_back({1, rand() & 1}); // x_accel_sign + data.push_back({4, 3}); // x_accel_value + data.push_back({1, rand() & 1}); // x_sign + data.push_back({26, 33554431}); // x_value + } else if (string_number == 2) { + data.push_back({3, 3}); // b_n + data.push_back({1, 1}); // p2 + data.push_back({7, 123}); // t_b + data.push_back({5, 31}); // not_used + data.push_back({1, rand() & 1}); // y_vel_sign + data.push_back({23, 7422}); // y_vel_value + data.push_back({1, rand() & 1}); // y_accel_sign + data.push_back({4, 3}); // y_accel_value + data.push_back({1, rand() & 1}); // y_sign + data.push_back({26, 67108863}); // y_value + } else if (string_number == 3) { + data.push_back({1, 0}); // p3 + data.push_back({1, 1}); // gamma_n_sign + data.push_back({10, 123}); // gamma_n_value + data.push_back({1, 0}); // not_used + data.push_back({2, 2}); // p + data.push_back({1, 1}); // l_n + data.push_back({1, rand() & 1}); // z_vel_sign + data.push_back({23, 1337}); // z_vel_value + data.push_back({1, rand() & 1}); // z_accel_sign + data.push_back({4, 9}); // z_accel_value + data.push_back({1, rand() & 1}); // z_sign + data.push_back({26, 100023}); // z_value + } else if (string_number == 4) { + data.push_back({1, rand() & 1}); // tau_n_sign + data.push_back({21, 197152}); // tau_n_value + data.push_back({1, rand() & 1}); // delta_tau_n_sign + data.push_back({4, 4}); // delta_tau_n_value + data.push_back({5, 0}); // e_n + data.push_back({14, 2}); // not_used_1 + data.push_back({1, 1}); // p4 + data.push_back({4, 9}); // f_t + data.push_back({3, 3}); // not_used_2 + data.push_back({11, 2047}); // n_t + data.push_back({5, 2}); // n + data.push_back({2, 1}); // m + } else if (string_number == 5) { + data.push_back({11, 2047}); // n_a + data.push_back({32, 4294767295}); // tau_c + data.push_back({1, 0}); // not_used_1 + data.push_back({5, 2}); // n_4 + data.push_back({22, 4114304}); // tau_gps + data.push_back({1, 0}); // l_n + } else { // non-immediate data is not parsed + data.push_back({64, rand()}); // data_1 + data.push_back({8, 6}); // data_2 + } + + data.push_back({8, rand() & 0xFF}); // hamming code + data.push_back({11, rand() & 0x7FF}); // pad + data.push_back({16, rand() & 0xFFFF}); // superframe + data.push_back({8, rand() & 0xFF}); // pad + data.push_back({8, rand() & 0xFF}); // frame + return data; +} + +TEST_CASE("parse_string_number_1"){ + string_data data = generate_string_data(1); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST1_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST1_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST1_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST1_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST1_HC_OFF + FRAME_IDX].second); + + kaitai::kstream str1(inp_data); + glonass_t str1_data(&str1); + glonass_t::string_1_t* s1 = static_cast(str1_data.data()); + + REQUIRE(s1->not_used() == data[ST1_NU_IDX].second); + REQUIRE(s1->p1() == data[ST1_P1_IDX].second); + REQUIRE(s1->t_k() == data[ST1_T_K_IDX].second); + + int mul = s1->x_vel_sign() ? (-1) : 1; + REQUIRE(s1->x_vel() == (data[ST1_X_VEL_V_IDX].second * mul)); + mul = s1->x_accel_sign() ? (-1) : 1; + REQUIRE(s1->x_accel() == (data[ST1_X_ACCEL_V_IDX].second * mul)); + mul = s1->x_sign() ? (-1) : 1; + REQUIRE(s1->x() == (data[ST1_X_V_IDX].second * mul)); +} + +TEST_CASE("parse_string_number_2"){ + string_data data = generate_string_data(2); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST2_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST2_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST2_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST2_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST2_HC_OFF + FRAME_IDX].second); + + kaitai::kstream str2(inp_data); + glonass_t str2_data(&str2); + glonass_t::string_2_t* s2 = static_cast(str2_data.data()); + + REQUIRE(s2->b_n() == data[ST2_BN_IDX].second); + REQUIRE(s2->not_used() == data[ST2_NU_IDX].second); + REQUIRE(s2->p2() == data[ST2_P2_IDX].second); + REQUIRE(s2->t_b() == data[ST2_TB_IDX].second); + int mul = s2->y_vel_sign() ? (-1) : 1; + REQUIRE(s2->y_vel() == (data[ST2_Y_VEL_V_IDX].second * mul)); + mul = s2->y_accel_sign() ? (-1) : 1; + REQUIRE(s2->y_accel() == (data[ST2_Y_ACCEL_V_IDX].second * mul)); + mul = s2->y_sign() ? (-1) : 1; + REQUIRE(s2->y() == (data[ST2_Y_V_IDX].second * mul)); +} + +TEST_CASE("parse_string_number_3"){ + string_data data = generate_string_data(3); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST3_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST3_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST3_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST3_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST3_HC_OFF + FRAME_IDX].second); + + kaitai::kstream str3(inp_data); + glonass_t str3_data(&str3); + glonass_t::string_3_t* s3 = static_cast(str3_data.data()); + + REQUIRE(s3->p3() == data[ST3_P3_IDX].second); + int mul = s3->gamma_n_sign() ? (-1) : 1; + REQUIRE(s3->gamma_n() == (data[ST3_GAMMA_N_V_IDX].second * mul)); + REQUIRE(s3->not_used() == data[ST3_NU_1_IDX].second); + REQUIRE(s3->p() == data[ST3_P_IDX].second); + REQUIRE(s3->l_n() == data[ST3_L_N_IDX].second); + mul = s3->z_vel_sign() ? (-1) : 1; + REQUIRE(s3->z_vel() == (data[ST3_Z_VEL_V_IDX].second * mul)); + mul = s3->z_accel_sign() ? (-1) : 1; + REQUIRE(s3->z_accel() == (data[ST3_Z_ACCEL_V_IDX].second * mul)); + mul = s3->z_sign() ? (-1) : 1; + REQUIRE(s3->z() == (data[ST3_Z_V_IDX].second * mul)); +} + +TEST_CASE("parse_string_number_4"){ + string_data data = generate_string_data(4); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST4_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST4_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST4_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST4_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST4_HC_OFF + FRAME_IDX].second); + + kaitai::kstream str4(inp_data); + glonass_t str4_data(&str4); + glonass_t::string_4_t* s4 = static_cast(str4_data.data()); + + int mul = s4->tau_n_sign() ? (-1) : 1; + REQUIRE(s4->tau_n() == (data[ST4_TAU_N_V_IDX].second * mul)); + mul = s4->delta_tau_n_sign() ? (-1) : 1; + REQUIRE(s4->delta_tau_n() == (data[ST4_DELTA_TAU_N_V_IDX].second * mul)); + REQUIRE(s4->e_n() == data[ST4_E_N_IDX].second); + REQUIRE(s4->not_used_1() == data[ST4_NU_1_IDX].second); + REQUIRE(s4->p4() == data[ST4_P4_IDX].second); + REQUIRE(s4->f_t() == data[ST4_F_T_IDX].second); + REQUIRE(s4->not_used_2() == data[ST4_NU_2_IDX].second); + REQUIRE(s4->n_t() == data[ST4_N_T_IDX].second); + REQUIRE(s4->n() == data[ST4_N_IDX].second); + REQUIRE(s4->m() == data[ST4_M_IDX].second); +} + +TEST_CASE("parse_string_number_5"){ + string_data data = generate_string_data(5); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST5_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST5_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST5_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST5_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST5_HC_OFF + FRAME_IDX].second); + + kaitai::kstream str5(inp_data); + glonass_t str5_data(&str5); + glonass_t::string_5_t* s5 = static_cast(str5_data.data()); + + REQUIRE(s5->n_a() == data[ST5_N_A_IDX].second); + REQUIRE(s5->tau_c() == data[ST5_TAU_C_IDX].second); + REQUIRE(s5->not_used() == data[ST5_NU_IDX].second); + REQUIRE(s5->n_4() == data[ST5_N_4_IDX].second); + REQUIRE(s5->tau_gps() == data[ST5_TAU_GPS_IDX].second); + REQUIRE(s5->l_n() == data[ST5_L_N_IDX].second); +} + +TEST_CASE("parse_string_number_NI"){ + string_data data = generate_string_data((rand() % 10) + 6); + std::string inp_data = generate_inp_data(data); + + kaitai::kstream stream(inp_data); + glonass_t gl_string(&stream); + + REQUIRE(gl_string.idle_chip() == data[IDLE_CHIP_IDX].second); + REQUIRE(gl_string.string_number() == data[STRING_NUMBER_IDX].second); + REQUIRE(gl_string.hamming_code() == data[ST6_HC_OFF + HC_IDX].second); + REQUIRE(gl_string.pad_1() == data[ST6_HC_OFF + PAD1_IDX].second); + REQUIRE(gl_string.superframe_number() == data[ST6_HC_OFF + SUPERFRAME_IDX].second); + REQUIRE(gl_string.pad_2() == data[ST6_HC_OFF + PAD2_IDX].second); + REQUIRE(gl_string.frame_number() == data[ST6_HC_OFF + FRAME_IDX].second); + + kaitai::kstream strni(inp_data); + glonass_t strni_data(&strni); + glonass_t::string_non_immediate_t* sni = static_cast(strni_data.data()); + + REQUIRE(sni->data_1() == data[ST6_DATA_1_IDX].second); + REQUIRE(sni->data_2() == data[ST6_DATA_2_IDX].second); +} diff --git a/system/ubloxd/tests/test_glonass_runner.cc b/system/ubloxd/tests/test_glonass_runner.cc new file mode 100644 index 0000000000..62bf7476a1 --- /dev/null +++ b/system/ubloxd/tests/test_glonass_runner.cc @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" diff --git a/selfdrive/locationd/test/test_ublox_processing.py b/system/ubloxd/tests/test_ublox_processing.py similarity index 65% rename from selfdrive/locationd/test/test_ublox_processing.py rename to system/ubloxd/tests/test_ublox_processing.py index 427003b24c..cd4ce0de04 100755 --- a/selfdrive/locationd/test/test_ublox_processing.py +++ b/system/ubloxd/tests/test_ublox_processing.py @@ -1,13 +1,15 @@ import unittest - +import time import numpy as np from laika import AstroDog from laika.helpers import ConstellationId -from laika.raw_gnss import calc_pos_fix, correct_measurements, process_measurements, read_raw_ublox +from laika.raw_gnss import correct_measurements, process_measurements, read_raw_ublox +from laika.opt import calc_pos_fix from selfdrive.test.openpilotci import get_url from tools.lib.logreader import LogReader - +from selfdrive.test.helpers import with_processes +import cereal.messaging as messaging def get_gnss_measurements(log_reader): gnss_measurements = [] @@ -20,6 +22,12 @@ def get_gnss_measurements(log_reader): gnss_measurements.append(read_raw_ublox(report)) return gnss_measurements +def get_ublox_raw(log_reader): + ublox_raw = [] + for msg in log_reader: + if msg.which() == "ubloxRaw": + ublox_raw.append(msg) + return ublox_raw class TestUbloxProcessing(unittest.TestCase): NUM_TEST_PROCESS_MEAS = 10 @@ -29,6 +37,10 @@ class TestUbloxProcessing(unittest.TestCase): lr = LogReader(get_url("4cf7a6ad03080c90|2021-09-29--13-46-36", 0)) cls.gnss_measurements = get_gnss_measurements(lr) + # test gps ephemeris continuity check (drive has ephemeris issues with cutover data) + lr = LogReader(get_url("37b6542f3211019a|2023-01-15--23-45-10", 14)) + cls.ublox_raw = get_ublox_raw(lr) + def test_read_ublox_raw(self): count_gps = 0 count_glonass = 0 @@ -54,14 +66,14 @@ class TestUbloxProcessing(unittest.TestCase): processed_meas = process_measurements(measurements, dog) count_processed_measurements += len(processed_meas) pos_fix = calc_pos_fix(processed_meas) - if len(pos_fix) > 0 and all(pos_fix[0] != 0): + if len(pos_fix) > 0 and all(p != 0 for p in pos_fix[0]): position_fix_found += 1 corrected_meas = correct_measurements(processed_meas, pos_fix[0][:3], dog) count_corrected_measurements += len(corrected_meas) pos_fix = calc_pos_fix(corrected_meas) - if len(pos_fix) > 0 and all(pos_fix[0] != 0): + if len(pos_fix) > 0 and all(p != 0 for p in pos_fix[0]): pos_ests.append(pos_fix[0]) position_fix_found_after_correcting += 1 @@ -75,6 +87,29 @@ class TestUbloxProcessing(unittest.TestCase): self.assertEqual(count_processed_measurements, 69) self.assertEqual(count_corrected_measurements, 69) + @with_processes(['ubloxd']) + def test_ublox_gps_cutover(self): + time.sleep(2) + ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) + ur_pm = messaging.PubMaster(['ubloxRaw']) + + def replay_segment(): + rcv_msgs = [] + for msg in self.ublox_raw: + ur_pm.send(msg.which(), msg.as_builder()) + time.sleep(0.01) + rcv_msgs += messaging.drain_sock(ugs) + + time.sleep(0.1) + rcv_msgs += messaging.drain_sock(ugs) + return rcv_msgs + + # replay twice to enforce cutover data on rewind + rcv_msgs = replay_segment() + rcv_msgs += replay_segment() + + ephems_cnt = sum(m.ubloxGnss.which() == 'ephemeris' for m in rcv_msgs) + self.assertEqual(ephems_cnt, 15) if __name__ == "__main__": unittest.main() diff --git a/selfdrive/locationd/test/ublox.py b/system/ubloxd/tests/ublox.py similarity index 99% rename from selfdrive/locationd/test/ublox.py rename to system/ubloxd/tests/ublox.py index 9cffbeac40..0d271ad785 100644 --- a/selfdrive/locationd/test/ublox.py +++ b/system/ubloxd/tests/ublox.py @@ -48,12 +48,14 @@ MSG_NAV_TIMEGPS = 0x20 MSG_NAV_TIMEUTC = 0x21 MSG_NAV_CLOCK = 0x22 MSG_NAV_SVINFO = 0x30 +MSG_NAV_SAT = 0x35 MSG_NAV_AOPSTATUS = 0x60 MSG_NAV_DGPS = 0x31 MSG_NAV_DOP = 0x04 MSG_NAV_EKFSTATUS = 0x40 MSG_NAV_SBAS = 0x32 MSG_NAV_SOL = 0x06 +MSG_NAV_SAT = 0x35 # RXM messages MSG_RXM_RAW = 0x15 diff --git a/selfdrive/locationd/test/ubloxd.py b/system/ubloxd/tests/ubloxd.py similarity index 98% rename from selfdrive/locationd/test/ubloxd.py rename to system/ubloxd/tests/ubloxd.py index 82aa502ceb..7c7e68b23b 100755 --- a/selfdrive/locationd/test/ubloxd.py +++ b/system/ubloxd/tests/ubloxd.py @@ -60,6 +60,7 @@ def configure_ublox(dev): dev.configure_message_rate(ublox.CLASS_RXM, ublox.MSG_RXM_SFRBX, 1) dev.configure_message_rate(ublox.CLASS_MON, ublox.MSG_MON_HW, 1) dev.configure_message_rate(ublox.CLASS_MON, ublox.MSG_MON_HW2, 1) + dev.configure_message_rate(ublox.CLASS_NAV, ublox.MSG_NAV_SAT, 1) # Query the backup restore status print("backup restore polling message (implement custom response handler!):") diff --git a/system/ubloxd/ublox_msg.cc b/system/ubloxd/ublox_msg.cc new file mode 100644 index 0000000000..a01187d560 --- /dev/null +++ b/system/ubloxd/ublox_msg.cc @@ -0,0 +1,524 @@ +#include "ublox_msg.h" + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "common/swaglog.h" + +const double gpsPi = 3.1415926535898; +#define UBLOX_MSG_SIZE(hdr) (*(uint16_t *)&hdr[4]) + +inline static bool bit_to_bool(uint8_t val, int shifts) { + return (bool)(val & (1 << shifts)); +} + +inline int UbloxMsgParser::needed_bytes() { + // Msg header incomplete? + if(bytes_in_parse_buf < ublox::UBLOX_HEADER_SIZE) + return ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE - bytes_in_parse_buf; + uint16_t needed = UBLOX_MSG_SIZE(msg_parse_buf) + ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE; + // too much data + if(needed < (uint16_t)bytes_in_parse_buf) + return -1; + return needed - (uint16_t)bytes_in_parse_buf; +} + +inline bool UbloxMsgParser::valid_cheksum() { + uint8_t ck_a = 0, ck_b = 0; + for(int i = 2; i < bytes_in_parse_buf - ublox::UBLOX_CHECKSUM_SIZE;i++) { + ck_a = (ck_a + msg_parse_buf[i]) & 0xFF; + ck_b = (ck_b + ck_a) & 0xFF; + } + if(ck_a != msg_parse_buf[bytes_in_parse_buf - 2]) { + LOGD("Checksum a mismatch: %02X, %02X", ck_a, msg_parse_buf[6]); + return false; + } + if(ck_b != msg_parse_buf[bytes_in_parse_buf - 1]) { + LOGD("Checksum b mismatch: %02X, %02X", ck_b, msg_parse_buf[7]); + return false; + } + return true; +} + +inline bool UbloxMsgParser::valid() { + return bytes_in_parse_buf >= ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_CHECKSUM_SIZE && + needed_bytes() == 0 && valid_cheksum(); +} + +inline bool UbloxMsgParser::valid_so_far() { + if(bytes_in_parse_buf > 0 && msg_parse_buf[0] != ublox::PREAMBLE1) { + return false; + } + if(bytes_in_parse_buf > 1 && msg_parse_buf[1] != ublox::PREAMBLE2) { + return false; + } + if(needed_bytes() == 0 && !valid()) { + return false; + } + return true; +} + +bool UbloxMsgParser::add_data(float log_time, const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed) { + last_log_time = log_time; + int needed = needed_bytes(); + if(needed > 0) { + bytes_consumed = std::min((uint32_t)needed, incoming_data_len ); + // Add data to buffer + memcpy(msg_parse_buf + bytes_in_parse_buf, incoming_data, bytes_consumed); + bytes_in_parse_buf += bytes_consumed; + } else { + bytes_consumed = incoming_data_len; + } + + // Validate msg format, detect invalid header and invalid checksum. + while(!valid_so_far() && bytes_in_parse_buf != 0) { + // Corrupted msg, drop a byte. + bytes_in_parse_buf -= 1; + if(bytes_in_parse_buf > 0) + memmove(&msg_parse_buf[0], &msg_parse_buf[1], bytes_in_parse_buf); + } + + // There is redundant data at the end of buffer, reset the buffer. + if(needed_bytes() == -1) { + bytes_in_parse_buf = 0; + } + return valid(); +} + + +std::pair> UbloxMsgParser::gen_msg() { + std::string dat = data(); + kaitai::kstream stream(dat); + + ubx_t ubx_message(&stream); + auto body = ubx_message.body(); + + switch (ubx_message.msg_type()) { + case 0x0107: + return {"gpsLocationExternal", gen_nav_pvt(static_cast(body))}; + case 0x0213: // UBX-RXM-SFRB (Broadcast Navigation Data Subframe) + return {"ubloxGnss", gen_rxm_sfrbx(static_cast(body))}; + case 0x0215: // UBX-RXM-RAW (Multi-GNSS Raw Measurement Data) + return {"ubloxGnss", gen_rxm_rawx(static_cast(body))}; + case 0x0a09: + return {"ubloxGnss", gen_mon_hw(static_cast(body))}; + case 0x0a0b: + return {"ubloxGnss", gen_mon_hw2(static_cast(body))}; + case 0x0135: + // TODO return {"ubloxGnss", gen_nav_sat(static_cast(body))}; + return {"ubloxGnss", kj::Array()}; + default: + LOGE("Unknown message type %x", ubx_message.msg_type()); + return {"ubloxGnss", kj::Array()}; + } +} + + +kj::Array UbloxMsgParser::gen_nav_pvt(ubx_t::nav_pvt_t *msg) { + MessageBuilder msg_builder; + auto gpsLoc = msg_builder.initEvent().initGpsLocationExternal(); + gpsLoc.setSource(cereal::GpsLocationData::SensorSource::UBLOX); + gpsLoc.setFlags(msg->flags()); + gpsLoc.setLatitude(msg->lat() * 1e-07); + gpsLoc.setLongitude(msg->lon() * 1e-07); + gpsLoc.setAltitude(msg->height() * 1e-03); + gpsLoc.setSpeed(msg->g_speed() * 1e-03); + gpsLoc.setBearingDeg(msg->head_mot() * 1e-5); + gpsLoc.setAccuracy(msg->h_acc() * 1e-03); + std::tm timeinfo = std::tm(); + timeinfo.tm_year = msg->year() - 1900; + timeinfo.tm_mon = msg->month() - 1; + timeinfo.tm_mday = msg->day(); + timeinfo.tm_hour = msg->hour(); + timeinfo.tm_min = msg->min(); + timeinfo.tm_sec = msg->sec(); + + std::time_t utc_tt = timegm(&timeinfo); + gpsLoc.setUnixTimestampMillis(utc_tt * 1e+03 + msg->nano() * 1e-06); + float f[] = { msg->vel_n() * 1e-03f, msg->vel_e() * 1e-03f, msg->vel_d() * 1e-03f }; + gpsLoc.setVNED(f); + gpsLoc.setVerticalAccuracy(msg->v_acc() * 1e-03); + gpsLoc.setSpeedAccuracy(msg->s_acc() * 1e-03); + gpsLoc.setBearingAccuracyDeg(msg->head_acc() * 1e-05); + return capnp::messageToFlatArray(msg_builder); +} + +kj::Array UbloxMsgParser::parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg) { + // GPS subframes are packed into 10x 4 bytes, each containing 3 actual bytes + // We will first need to separate the data from the padding and parity + auto body = *msg->body(); + assert(body.size() == 10); + + std::string subframe_data; + subframe_data.reserve(30); + for (uint32_t word : body) { + word = word >> 6; // TODO: Verify parity + subframe_data.push_back(word >> 16); + subframe_data.push_back(word >> 8); + subframe_data.push_back(word >> 0); + } + + // Collect subframes in map and parse when we have all the parts + { + kaitai::kstream stream(subframe_data); + gps_t subframe(&stream); + + int subframe_id = subframe.how()->subframe_id(); + if (subframe_id > 3 || subframe_id < 1) { + // dont parse almanac subframes + return kj::Array(); + } + gps_subframes[msg->sv_id()][subframe_id] = subframe_data; + } + + // publish if subframes 1-3 have been collected + if (gps_subframes[msg->sv_id()].size() == 3) { + MessageBuilder msg_builder; + auto eph = msg_builder.initEvent().initUbloxGnss().initEphemeris(); + eph.setSvId(msg->sv_id()); + + int iode_s2 = 0; + int iode_s3 = 0; + int iodc_lsb = 0; + int week; + + // Subframe 1 + { + kaitai::kstream stream(gps_subframes[msg->sv_id()][1]); + gps_t subframe(&stream); + gps_t::subframe_1_t* subframe_1 = static_cast(subframe.body()); + + // Each message is incremented to be greater or equal than week 1877 (2015-12-27). + // To skip this use the current_time argument + week = subframe_1->week_no(); + week += 1024; + if (week < 1877) { + week += 1024; + } + //eph.setGpsWeek(subframe_1->week_no()); + eph.setTgd(subframe_1->t_gd() * pow(2, -31)); + eph.setToc(subframe_1->t_oc() * pow(2, 4)); + eph.setAf2(subframe_1->af_2() * pow(2, -55)); + eph.setAf1(subframe_1->af_1() * pow(2, -43)); + eph.setAf0(subframe_1->af_0() * pow(2, -31)); + eph.setSvHealth(subframe_1->sv_health()); + eph.setTowCount(subframe.how()->tow_count()); + iodc_lsb = subframe_1->iodc_lsb(); + } + + // Subframe 2 + { + kaitai::kstream stream(gps_subframes[msg->sv_id()][2]); + gps_t subframe(&stream); + gps_t::subframe_2_t* subframe_2 = static_cast(subframe.body()); + + // GPS week refers to current week, the ephemeris can be valid for the next + // if toe equals 0, this can be verified by the TOW count if it is within the + // last 2 hours of the week (gps ephemeris valid for 4hours) + if (subframe_2->t_oe() == 0 and subframe.how()->tow_count()*6 >= (SECS_IN_WEEK - 2*SECS_IN_HR)){ + week += 1; + } + eph.setCrs(subframe_2->c_rs() * pow(2, -5)); + eph.setDeltaN(subframe_2->delta_n() * pow(2, -43) * gpsPi); + eph.setM0(subframe_2->m_0() * pow(2, -31) * gpsPi); + eph.setCuc(subframe_2->c_uc() * pow(2, -29)); + eph.setEcc(subframe_2->e() * pow(2, -33)); + eph.setCus(subframe_2->c_us() * pow(2, -29)); + eph.setA(pow(subframe_2->sqrt_a() * pow(2, -19), 2.0)); + eph.setToe(subframe_2->t_oe() * pow(2, 4)); + iode_s2 = subframe_2->iode(); + } + + // Subframe 3 + { + kaitai::kstream stream(gps_subframes[msg->sv_id()][3]); + gps_t subframe(&stream); + gps_t::subframe_3_t* subframe_3 = static_cast(subframe.body()); + + eph.setCic(subframe_3->c_ic() * pow(2, -29)); + eph.setOmega0(subframe_3->omega_0() * pow(2, -31) * gpsPi); + eph.setCis(subframe_3->c_is() * pow(2, -29)); + eph.setI0(subframe_3->i_0() * pow(2, -31) * gpsPi); + eph.setCrc(subframe_3->c_rc() * pow(2, -5)); + eph.setOmega(subframe_3->omega() * pow(2, -31) * gpsPi); + eph.setOmegaDot(subframe_3->omega_dot() * pow(2, -43) * gpsPi); + eph.setIode(subframe_3->iode()); + eph.setIDot(subframe_3->idot() * pow(2, -43) * gpsPi); + iode_s3 = subframe_3->iode(); + } + + eph.setToeWeek(week); + eph.setTocWeek(week); + + gps_subframes[msg->sv_id()].clear(); + if (iodc_lsb != iode_s2 || iodc_lsb != iode_s3) { + // data set cutover, reject ephemeris + return kj::Array(); + } + return capnp::messageToFlatArray(msg_builder); + } + return kj::Array(); +} + +kj::Array UbloxMsgParser::parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg) { + // This parser assumes that no 2 satellites of the same frequency + // can be in view at the same time + auto body = *msg->body(); + assert(body.size() == 4); + { + std::string string_data; + string_data.reserve(16); + for (uint32_t word : body) { + for (int i = 3; i >= 0; i--) + string_data.push_back(word >> 8*i); + } + + kaitai::kstream stream(string_data); + glonass_t gl_string(&stream); + int string_number = gl_string.string_number(); + if (string_number < 1 || string_number > 5 || gl_string.idle_chip()) { + // dont parse non immediate data, idle_chip == 0 + return kj::Array(); + } + + // Check if new string either has same superframe_id or log transmission times make sense + bool superframe_unknown = false; + bool needs_clear = false; + for (int i = 1; i <= 5; i++) { + if (glonass_strings[msg->freq_id()].find(i) == glonass_strings[msg->freq_id()].end()) + continue; + if (glonass_string_superframes[msg->freq_id()][i] == 0 || gl_string.superframe_number() == 0) { + superframe_unknown = true; + } + else if (glonass_string_superframes[msg->freq_id()][i] != gl_string.superframe_number()) { + needs_clear = true; + } + // Check if string times add up to being from the same frame + // If superframe is known this is redundant + // Strings are sent 2s apart and frames are 30s apart + if (superframe_unknown && + std::abs((glonass_string_times[msg->freq_id()][i] - 2.0 * i) - (last_log_time - 2.0 * string_number)) > 10) + needs_clear = true; + } + if (needs_clear) { + glonass_strings[msg->freq_id()].clear(); + glonass_string_superframes[msg->freq_id()].clear(); + glonass_string_times[msg->freq_id()].clear(); + } + glonass_strings[msg->freq_id()][string_number] = string_data; + glonass_string_superframes[msg->freq_id()][string_number] = gl_string.superframe_number(); + glonass_string_times[msg->freq_id()][string_number] = last_log_time; + } + if (msg->sv_id() == 255) { + // data can be decoded before identifying the SV number, in this case 255 + // is returned, which means "unknown" (ublox p32) + return kj::Array(); + } + + // publish if strings 1-5 have been collected + if (glonass_strings[msg->freq_id()].size() != 5) { + return kj::Array(); + } + + MessageBuilder msg_builder; + auto eph = msg_builder.initEvent().initUbloxGnss().initGlonassEphemeris(); + eph.setSvId(msg->sv_id()); + eph.setFreqNum(msg->freq_id() - 7); + + uint16_t current_day = 0; + uint16_t tk = 0; + + // string number 1 + { + kaitai::kstream stream(glonass_strings[msg->freq_id()][1]); + glonass_t gl_stream(&stream); + glonass_t::string_1_t* data = static_cast(gl_stream.data()); + + eph.setP1(data->p1()); + tk = data->t_k(); + eph.setTkDEPRECATED(tk); + eph.setXVel(data->x_vel() * pow(2, -20)); + eph.setXAccel(data->x_accel() * pow(2, -30)); + eph.setX(data->x() * pow(2, -11)); + } + + // string number 2 + { + kaitai::kstream stream(glonass_strings[msg->freq_id()][2]); + glonass_t gl_stream(&stream); + glonass_t::string_2_t* data = static_cast(gl_stream.data()); + + eph.setSvHealth(data->b_n()>>2); // MSB indicates health + eph.setP2(data->p2()); + eph.setTb(data->t_b()); + eph.setYVel(data->y_vel() * pow(2, -20)); + eph.setYAccel(data->y_accel() * pow(2, -30)); + eph.setY(data->y() * pow(2, -11)); + } + + // string number 3 + { + kaitai::kstream stream(glonass_strings[msg->freq_id()][3]); + glonass_t gl_stream(&stream); + glonass_t::string_3_t* data = static_cast(gl_stream.data()); + + eph.setP3(data->p3()); + eph.setGammaN(data->gamma_n() * pow(2, -40)); + eph.setSvHealth(eph.getSvHealth() | data->l_n()); + eph.setZVel(data->z_vel() * pow(2, -20)); + eph.setZAccel(data->z_accel() * pow(2, -30)); + eph.setZ(data->z() * pow(2, -11)); + } + + // string number 4 + { + kaitai::kstream stream(glonass_strings[msg->freq_id()][4]); + glonass_t gl_stream(&stream); + glonass_t::string_4_t* data = static_cast(gl_stream.data()); + + current_day = data->n_t(); + eph.setNt(current_day); + eph.setTauN(data->tau_n() * pow(2, -30)); + eph.setDeltaTauN(data->delta_tau_n() * pow(2, -30)); + eph.setAge(data->e_n()); + eph.setP4(data->p4()); + eph.setSvURA(glonass_URA_lookup.at(data->f_t())); + if (msg->sv_id() != data->n()) { + LOGE("SV_ID != SLOT_NUMBER: %d %d", msg->sv_id(), data->n()) + } + eph.setSvType(data->m()); + } + + // string number 5 + { + kaitai::kstream stream(glonass_strings[msg->freq_id()][5]); + glonass_t gl_stream(&stream); + glonass_t::string_5_t* data = static_cast(gl_stream.data()); + + // string5 parsing is only needed to get the year, this can be removed and + // the year can be fetched later in laika (note rollovers and leap year) + eph.setN4(data->n_4()); + int tk_seconds = SECS_IN_HR * ((tk>>7) & 0x1F) + SECS_IN_MIN * ((tk>>1) & 0x3F) + (tk & 0x1) * 30; + eph.setTkSeconds(tk_seconds); + } + + glonass_strings[msg->freq_id()].clear(); + return capnp::messageToFlatArray(msg_builder); +} + + +kj::Array UbloxMsgParser::gen_rxm_sfrbx(ubx_t::rxm_sfrbx_t *msg) { + switch (msg->gnss_id()) { + case ubx_t::gnss_type_t::GNSS_TYPE_GPS: + return parse_gps_ephemeris(msg); + case ubx_t::gnss_type_t::GNSS_TYPE_GLONASS: + return parse_glonass_ephemeris(msg); + default: + return kj::Array(); + } +} + +kj::Array UbloxMsgParser::gen_rxm_rawx(ubx_t::rxm_rawx_t *msg) { + MessageBuilder msg_builder; + auto mr = msg_builder.initEvent().initUbloxGnss().initMeasurementReport(); + mr.setRcvTow(msg->rcv_tow()); + mr.setGpsWeek(msg->week()); + mr.setLeapSeconds(msg->leap_s()); + mr.setGpsWeek(msg->week()); + + auto mb = mr.initMeasurements(msg->num_meas()); + auto measurements = *msg->meas(); + for(int8_t i = 0; i < msg->num_meas(); i++) { + mb[i].setSvId(measurements[i]->sv_id()); + mb[i].setPseudorange(measurements[i]->pr_mes()); + mb[i].setCarrierCycles(measurements[i]->cp_mes()); + mb[i].setDoppler(measurements[i]->do_mes()); + mb[i].setGnssId(measurements[i]->gnss_id()); + mb[i].setGlonassFrequencyIndex(measurements[i]->freq_id()); + mb[i].setLocktime(measurements[i]->lock_time()); + mb[i].setCno(measurements[i]->cno()); + mb[i].setPseudorangeStdev(0.01 * (pow(2, (measurements[i]->pr_stdev() & 15)))); // weird scaling, might be wrong + mb[i].setCarrierPhaseStdev(0.004 * (measurements[i]->cp_stdev() & 15)); + mb[i].setDopplerStdev(0.002 * (pow(2, (measurements[i]->do_stdev() & 15)))); // weird scaling, might be wrong + + auto ts = mb[i].initTrackingStatus(); + auto trk_stat = measurements[i]->trk_stat(); + ts.setPseudorangeValid(bit_to_bool(trk_stat, 0)); + ts.setCarrierPhaseValid(bit_to_bool(trk_stat, 1)); + ts.setHalfCycleValid(bit_to_bool(trk_stat, 2)); + ts.setHalfCycleSubtracted(bit_to_bool(trk_stat, 3)); + } + + mr.setNumMeas(msg->num_meas()); + auto rs = mr.initReceiverStatus(); + rs.setLeapSecValid(bit_to_bool(msg->rec_stat(), 0)); + rs.setClkReset(bit_to_bool(msg->rec_stat(), 2)); + return capnp::messageToFlatArray(msg_builder); +} + +kj::Array UbloxMsgParser::gen_nav_sat(ubx_t::nav_sat_t *msg) { + MessageBuilder msg_builder; + auto sr = msg_builder.initEvent().initUbloxGnss().initSatReport(); + sr.setITow(msg->itow()); + + auto svs = sr.initSvs(msg->num_svs()); + auto svs_data = *msg->svs(); + for(int8_t i = 0; i < msg->num_svs(); i++) { + svs[i].setSvId(svs_data[i]->sv_id()); + svs[i].setGnssId(svs_data[i]->gnss_id()); + svs[i].setFlagsBitfield(svs_data[i]->flags()); + } + + return capnp::messageToFlatArray(msg_builder); +} + +kj::Array UbloxMsgParser::gen_mon_hw(ubx_t::mon_hw_t *msg) { + MessageBuilder msg_builder; + auto hwStatus = msg_builder.initEvent().initUbloxGnss().initHwStatus(); + hwStatus.setNoisePerMS(msg->noise_per_ms()); + hwStatus.setFlags(msg->flags()); + hwStatus.setAgcCnt(msg->agc_cnt()); + hwStatus.setAStatus((cereal::UbloxGnss::HwStatus::AntennaSupervisorState) msg->a_status()); + hwStatus.setAPower((cereal::UbloxGnss::HwStatus::AntennaPowerStatus) msg->a_power()); + hwStatus.setJamInd(msg->jam_ind()); + return capnp::messageToFlatArray(msg_builder); +} + +kj::Array UbloxMsgParser::gen_mon_hw2(ubx_t::mon_hw2_t *msg) { + MessageBuilder msg_builder; + auto hwStatus = msg_builder.initEvent().initUbloxGnss().initHwStatus2(); + hwStatus.setOfsI(msg->ofs_i()); + hwStatus.setMagI(msg->mag_i()); + hwStatus.setOfsQ(msg->ofs_q()); + hwStatus.setMagQ(msg->mag_q()); + + switch (msg->cfg_source()) { + case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_ROM: + hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::ROM); + break; + case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_OTP: + hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::OTP); + break; + case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_CONFIG_PINS: + hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::CONFIGPINS); + break; + case ubx_t::mon_hw2_t::config_source_t::CONFIG_SOURCE_FLASH: + hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::FLASH); + break; + default: + hwStatus.setCfgSource(cereal::UbloxGnss::HwStatus2::ConfigSource::UNDEFINED); + break; + } + + hwStatus.setLowLevCfg(msg->low_lev_cfg()); + hwStatus.setPostStatus(msg->post_status()); + + return capnp::messageToFlatArray(msg_builder); +} diff --git a/selfdrive/locationd/ublox_msg.h b/system/ubloxd/ublox_msg.h similarity index 70% rename from selfdrive/locationd/ublox_msg.h rename to system/ubloxd/ublox_msg.h index 542e72816b..a52a7db3e5 100644 --- a/selfdrive/locationd/ublox_msg.h +++ b/system/ubloxd/ublox_msg.h @@ -9,11 +9,17 @@ #include "cereal/messaging/messaging.h" #include "common/util.h" -#include "selfdrive/locationd/generated/gps.h" -#include "selfdrive/locationd/generated/ubx.h" +#include "system/ubloxd/generated/gps.h" +#include "system/ubloxd/generated/glonass.h" +#include "system/ubloxd/generated/ubx.h" using namespace std::string_literals; +const int SECS_IN_MIN = 60; +const int SECS_IN_HR = 60 * SECS_IN_MIN; +const int SECS_IN_DAY = 24 * SECS_IN_HR; +const int SECS_IN_WEEK = 7 * SECS_IN_DAY; + // protocol constants namespace ublox { const uint8_t PREAMBLE1 = 0xb5; @@ -85,7 +91,7 @@ namespace ublox { class UbloxMsgParser { public: - bool add_data(const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed); + bool add_data(float log_time, const uint8_t *incoming_data, uint32_t incoming_data_len, size_t &bytes_consumed); inline void reset() {bytes_in_parse_buf = 0;} inline int needed_bytes(); inline std::string data() {return std::string((const char*)msg_parse_buf, bytes_in_parse_buf);} @@ -96,16 +102,29 @@ class UbloxMsgParser { kj::Array gen_rxm_rawx(ubx_t::rxm_rawx_t *msg); kj::Array gen_mon_hw(ubx_t::mon_hw_t *msg); kj::Array gen_mon_hw2(ubx_t::mon_hw2_t *msg); + kj::Array gen_nav_sat(ubx_t::nav_sat_t *msg); private: inline bool valid_cheksum(); inline bool valid(); inline bool valid_so_far(); + kj::Array parse_gps_ephemeris(ubx_t::rxm_sfrbx_t *msg); + kj::Array parse_glonass_ephemeris(ubx_t::rxm_sfrbx_t *msg); + std::unordered_map> gps_subframes; + float last_log_time = 0.0; size_t bytes_in_parse_buf = 0; uint8_t msg_parse_buf[ublox::UBLOX_HEADER_SIZE + ublox::UBLOX_MAX_MSG_SIZE]; -}; + // user range accuracy in meters + const std::unordered_map glonass_URA_lookup = + {{ 0, 1}, { 1, 2}, { 2, 2.5}, { 3, 4}, { 4, 5}, {5, 7}, + { 6, 10}, { 7, 12}, { 8, 14}, { 9, 16}, {10, 32}, + {11, 64}, {12, 128}, {13, 256}, {14, 512}, {15, 1024}}; + std::unordered_map> glonass_strings; + std::unordered_map> glonass_string_times; + std::unordered_map> glonass_string_superframes; +}; diff --git a/selfdrive/locationd/ubloxd.cc b/system/ubloxd/ubloxd.cc similarity index 88% rename from selfdrive/locationd/ubloxd.cc rename to system/ubloxd/ubloxd.cc index d9b3e7647d..1dae6dc866 100644 --- a/selfdrive/locationd/ubloxd.cc +++ b/system/ubloxd/ubloxd.cc @@ -5,7 +5,7 @@ #include "cereal/messaging/messaging.h" #include "common/swaglog.h" #include "common/util.h" -#include "selfdrive/locationd/ublox_msg.h" +#include "system/ubloxd/ublox_msg.h" ExitHandler do_exit; using namespace ublox; @@ -35,6 +35,7 @@ int main() { capnp::FlatArrayMessageReader cmsg(aligned_buf.align(msg.get())); cereal::Event::Reader event = cmsg.getRoot(); auto ubloxRaw = event.getUbloxRaw(); + float log_time = 1e-9 * event.getLogMonoTime(); const uint8_t *data = ubloxRaw.begin(); size_t len = ubloxRaw.size(); @@ -42,7 +43,7 @@ int main() { while(bytes_consumed < len && !do_exit) { size_t bytes_consumed_this_time = 0U; - if(parser.add_data(data + bytes_consumed, (uint32_t)(len - bytes_consumed), bytes_consumed_this_time)) { + if(parser.add_data(log_time, data + bytes_consumed, (uint32_t)(len - bytes_consumed), bytes_consumed_this_time)) { try { auto ublox_msg = parser.gen_msg(); diff --git a/selfdrive/locationd/ubx.ksy b/system/ubloxd/ubx.ksy similarity index 86% rename from selfdrive/locationd/ubx.ksy rename to system/ubloxd/ubx.ksy index 6945680d32..02c757fe71 100644 --- a/selfdrive/locationd/ubx.ksy +++ b/system/ubloxd/ubx.ksy @@ -17,6 +17,7 @@ seq: 0x0215: rxm_rawx 0x0a09: mon_hw 0x0a0b: mon_hw2 + 0x0135: nav_sat instances: checksum: pos: length + 6 @@ -142,13 +143,13 @@ types: type: u1 - id: reserved1 size: 3 - - id: measurements - type: meas + - id: meas + type: measurement size: 32 repeat: expr repeat-expr: num_meas types: - meas: + measurement: seq: - id: pr_mes type: f8 @@ -179,6 +180,39 @@ types: type: u1 - id: reserved3 size: 1 + nav_sat: + seq: + - id: itow + type: u4 + - id: version + type: u1 + - id: num_svs + type: u1 + - id: reserved + size: 2 + - id: svs + type: nav + size: 12 + repeat: expr + repeat-expr: num_svs + types: + nav: + seq: + - id: gnss_id + type: u1 + enum: gnss_type + - id: sv_id + type: u1 + - id: cno + type: u1 + - id: elev + type: s1 + - id: azim + type: s2 + - id: pr_res + type: s2 + - id: flags + type: u4 nav_pvt: seq: diff --git a/system/version.py b/system/version.py index f0817b3a9f..55f80afa35 100644 --- a/system/version.py +++ b/system/version.py @@ -7,7 +7,8 @@ from functools import lru_cache from common.basedir import BASEDIR from system.swaglog import cloudlog -TESTED_BRANCHES = ['devel', 'release3-staging', 'dashcam3-staging', 'release3', 'dashcam3'] +RELEASE_BRANCHES = ['release3-staging', 'dashcam3-staging', 'release3', 'dashcam3', 'nightly'] +TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] training_version: bytes = b"0.2.0" terms_version: bytes = b"2" @@ -96,6 +97,9 @@ def is_comma_remote() -> bool: def is_tested_branch() -> bool: return get_short_branch() in TESTED_BRANCHES +@cache +def is_release_branch() -> bool: + return get_short_branch() in RELEASE_BRANCHES @cache def is_dirty() -> bool: diff --git a/pyextra/.gitignore b/third_party/.gitignore similarity index 100% rename from pyextra/.gitignore rename to third_party/.gitignore diff --git a/third_party/SConscript b/third_party/SConscript index e5bbfaa07a..e8d1789ee0 100644 --- a/third_party/SConscript +++ b/third_party/SConscript @@ -4,3 +4,5 @@ env.Library('json11', ['json11/json11.cpp'], CCFLAGS=env['CCFLAGS'] + ['-Wno-unq env.Append(CPPPATH=[Dir('json11')]) env.Library('kaitai', ['kaitai/kaitaistream.cpp'], CPPDEFINES=['KS_STR_ENCODING_NONE']) + +SConscript(['cluster/SConscript']) diff --git a/pyextra/acados_template/.gitignore b/third_party/acados/acados_template/.gitignore similarity index 100% rename from pyextra/acados_template/.gitignore rename to third_party/acados/acados_template/.gitignore diff --git a/pyextra/acados_template/__init__.py b/third_party/acados/acados_template/__init__.py similarity index 100% rename from pyextra/acados_template/__init__.py rename to third_party/acados/acados_template/__init__.py diff --git a/pyextra/acados_template/acados_layout.json b/third_party/acados/acados_template/acados_layout.json similarity index 100% rename from pyextra/acados_template/acados_layout.json rename to third_party/acados/acados_template/acados_layout.json diff --git a/pyextra/acados_template/acados_model.py b/third_party/acados/acados_template/acados_model.py similarity index 100% rename from pyextra/acados_template/acados_model.py rename to third_party/acados/acados_template/acados_model.py diff --git a/pyextra/acados_template/acados_ocp.py b/third_party/acados/acados_template/acados_ocp.py similarity index 100% rename from pyextra/acados_template/acados_ocp.py rename to third_party/acados/acados_template/acados_ocp.py diff --git a/pyextra/acados_template/acados_ocp_solver.py b/third_party/acados/acados_template/acados_ocp_solver.py similarity index 100% rename from pyextra/acados_template/acados_ocp_solver.py rename to third_party/acados/acados_template/acados_ocp_solver.py diff --git a/pyextra/acados_template/acados_ocp_solver_pyx.pyx b/third_party/acados/acados_template/acados_ocp_solver_pyx.pyx similarity index 100% rename from pyextra/acados_template/acados_ocp_solver_pyx.pyx rename to third_party/acados/acados_template/acados_ocp_solver_pyx.pyx diff --git a/pyextra/acados_template/acados_sim.py b/third_party/acados/acados_template/acados_sim.py similarity index 100% rename from pyextra/acados_template/acados_sim.py rename to third_party/acados/acados_template/acados_sim.py diff --git a/pyextra/acados_template/acados_sim_layout.json b/third_party/acados/acados_template/acados_sim_layout.json similarity index 100% rename from pyextra/acados_template/acados_sim_layout.json rename to third_party/acados/acados_template/acados_sim_layout.json diff --git a/pyextra/acados_template/acados_sim_solver.py b/third_party/acados/acados_template/acados_sim_solver.py similarity index 100% rename from pyextra/acados_template/acados_sim_solver.py rename to third_party/acados/acados_template/acados_sim_solver.py diff --git a/pyextra/acados_template/acados_solver_common.pxd b/third_party/acados/acados_template/acados_solver_common.pxd similarity index 100% rename from pyextra/acados_template/acados_solver_common.pxd rename to third_party/acados/acados_template/acados_solver_common.pxd diff --git a/pyextra/acados_template/builders.py b/third_party/acados/acados_template/builders.py similarity index 100% rename from pyextra/acados_template/builders.py rename to third_party/acados/acados_template/builders.py diff --git a/pyextra/acados_template/c_templates_tera/CMakeLists.in.txt b/third_party/acados/acados_template/c_templates_tera/CMakeLists.in.txt similarity index 100% rename from pyextra/acados_template/c_templates_tera/CMakeLists.in.txt rename to third_party/acados/acados_template/c_templates_tera/CMakeLists.in.txt diff --git a/pyextra/acados_template/c_templates_tera/CPPLINT.cfg b/third_party/acados/acados_template/c_templates_tera/CPPLINT.cfg similarity index 100% rename from pyextra/acados_template/c_templates_tera/CPPLINT.cfg rename to third_party/acados/acados_template/c_templates_tera/CPPLINT.cfg diff --git a/pyextra/acados_template/c_templates_tera/Makefile.in b/third_party/acados/acados_template/c_templates_tera/Makefile.in similarity index 100% rename from pyextra/acados_template/c_templates_tera/Makefile.in rename to third_party/acados/acados_template/c_templates_tera/Makefile.in diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_create.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_create.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_create.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_create.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_free.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_free.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_free.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_free.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_set.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_set.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_set.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_set.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_mex_solve.in.c b/third_party/acados/acados_template/c_templates_tera/acados_mex_solve.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_mex_solve.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_mex_solve.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver.in.h rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver.in.h diff --git a/pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c b/third_party/acados/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_sim_solver_sfun.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.c b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.c diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.h b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.h rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.h diff --git a/pyextra/acados_template/c_templates_tera/acados_solver.in.pxd b/third_party/acados/acados_template/c_templates_tera/acados_solver.in.pxd similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver.in.pxd rename to third_party/acados/acados_template/c_templates_tera/acados_solver.in.pxd diff --git a/pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c b/third_party/acados/acados_template/c_templates_tera/acados_solver_sfun.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/acados_solver_sfun.in.c rename to third_party/acados/acados_template/c_templates_tera/acados_solver_sfun.in.c diff --git a/pyextra/acados_template/c_templates_tera/cost_y_0_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_0_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_0_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_0_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/cost_y_e_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_e_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_e_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_e_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/cost_y_fun.in.h b/third_party/acados/acados_template/c_templates_tera/cost_y_fun.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/cost_y_fun.in.h rename to third_party/acados/acados_template/c_templates_tera/cost_y_fun.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost_0.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost_0.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost_0.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost_0.in.h diff --git a/pyextra/acados_template/c_templates_tera/external_cost_e.in.h b/third_party/acados/acados_template/c_templates_tera/external_cost_e.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/external_cost_e.in.h rename to third_party/acados/acados_template/c_templates_tera/external_cost_e.in.h diff --git a/pyextra/acados_template/c_templates_tera/h_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/h_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/h_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/h_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/h_e_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/h_e_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/h_e_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/h_e_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/main.in.c b/third_party/acados/acados_template/c_templates_tera/main.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main.in.c rename to third_party/acados/acados_template/c_templates_tera/main.in.c diff --git a/pyextra/acados_template/c_templates_tera/main_mex.in.c b/third_party/acados/acados_template/c_templates_tera/main_mex.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main_mex.in.c rename to third_party/acados/acados_template/c_templates_tera/main_mex.in.c diff --git a/pyextra/acados_template/c_templates_tera/main_sim.in.c b/third_party/acados/acados_template/c_templates_tera/main_sim.in.c similarity index 100% rename from pyextra/acados_template/c_templates_tera/main_sim.in.c rename to third_party/acados/acados_template/c_templates_tera/main_sim.in.c diff --git a/pyextra/acados_template/c_templates_tera/make_main_mex.in.m b/third_party/acados/acados_template/c_templates_tera/make_main_mex.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_main_mex.in.m rename to third_party/acados/acados_template/c_templates_tera/make_main_mex.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_mex.in.m b/third_party/acados/acados_template/c_templates_tera/make_mex.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_mex.in.m rename to third_party/acados/acados_template/c_templates_tera/make_mex.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_sfun.in.m b/third_party/acados/acados_template/c_templates_tera/make_sfun.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_sfun.in.m rename to third_party/acados/acados_template/c_templates_tera/make_sfun.in.m diff --git a/pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m b/third_party/acados/acados_template/c_templates_tera/make_sfun_sim.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/make_sfun_sim.in.m rename to third_party/acados/acados_template/c_templates_tera/make_sfun_sim.in.m diff --git a/pyextra/acados_template/c_templates_tera/mex_solver.in.m b/third_party/acados/acados_template/c_templates_tera/mex_solver.in.m similarity index 100% rename from pyextra/acados_template/c_templates_tera/mex_solver.in.m rename to third_party/acados/acados_template/c_templates_tera/mex_solver.in.m diff --git a/pyextra/acados_template/c_templates_tera/model.in.h b/third_party/acados/acados_template/c_templates_tera/model.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/model.in.h rename to third_party/acados/acados_template/c_templates_tera/model.in.h diff --git a/pyextra/acados_template/c_templates_tera/phi_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/phi_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/phi_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/phi_constraint.in.h diff --git a/pyextra/acados_template/c_templates_tera/phi_e_constraint.in.h b/third_party/acados/acados_template/c_templates_tera/phi_e_constraint.in.h similarity index 100% rename from pyextra/acados_template/c_templates_tera/phi_e_constraint.in.h rename to third_party/acados/acados_template/c_templates_tera/phi_e_constraint.in.h diff --git a/pyextra/acados_template/generate_c_code_constraint.py b/third_party/acados/acados_template/generate_c_code_constraint.py similarity index 100% rename from pyextra/acados_template/generate_c_code_constraint.py rename to third_party/acados/acados_template/generate_c_code_constraint.py diff --git a/pyextra/acados_template/generate_c_code_discrete_dynamics.py b/third_party/acados/acados_template/generate_c_code_discrete_dynamics.py similarity index 100% rename from pyextra/acados_template/generate_c_code_discrete_dynamics.py rename to third_party/acados/acados_template/generate_c_code_discrete_dynamics.py diff --git a/pyextra/acados_template/generate_c_code_explicit_ode.py b/third_party/acados/acados_template/generate_c_code_explicit_ode.py similarity index 100% rename from pyextra/acados_template/generate_c_code_explicit_ode.py rename to third_party/acados/acados_template/generate_c_code_explicit_ode.py diff --git a/pyextra/acados_template/generate_c_code_external_cost.py b/third_party/acados/acados_template/generate_c_code_external_cost.py similarity index 100% rename from pyextra/acados_template/generate_c_code_external_cost.py rename to third_party/acados/acados_template/generate_c_code_external_cost.py diff --git a/pyextra/acados_template/generate_c_code_gnsf.py b/third_party/acados/acados_template/generate_c_code_gnsf.py similarity index 100% rename from pyextra/acados_template/generate_c_code_gnsf.py rename to third_party/acados/acados_template/generate_c_code_gnsf.py diff --git a/pyextra/acados_template/generate_c_code_implicit_ode.py b/third_party/acados/acados_template/generate_c_code_implicit_ode.py similarity index 100% rename from pyextra/acados_template/generate_c_code_implicit_ode.py rename to third_party/acados/acados_template/generate_c_code_implicit_ode.py diff --git a/pyextra/acados_template/generate_c_code_nls_cost.py b/third_party/acados/acados_template/generate_c_code_nls_cost.py similarity index 100% rename from pyextra/acados_template/generate_c_code_nls_cost.py rename to third_party/acados/acados_template/generate_c_code_nls_cost.py diff --git a/pyextra/acados_template/simulink_default_opts.json b/third_party/acados/acados_template/simulink_default_opts.json similarity index 100% rename from pyextra/acados_template/simulink_default_opts.json rename to third_party/acados/acados_template/simulink_default_opts.json diff --git a/pyextra/acados_template/utils.py b/third_party/acados/acados_template/utils.py similarity index 100% rename from pyextra/acados_template/utils.py rename to third_party/acados/acados_template/utils.py diff --git a/third_party/acados/build.sh b/third_party/acados/build.sh index 0481e8159b..9b18b2b67d 100755 --- a/third_party/acados/build.sh +++ b/third_party/acados/build.sh @@ -43,11 +43,10 @@ mkdir -p $INSTALL_DIR rm $DIR/acados_repo/lib/*.json -rm -rf $DIR/include +rm -rf $DIR/include $DIR/acados_template cp -r $DIR/acados_repo/include $DIR cp -r $DIR/acados_repo/lib $INSTALL_DIR -rm -rf $DIR/../../pyextra/acados_template -cp -r $DIR/acados_repo/interfaces/acados_template/acados_template $DIR/../../pyextra +cp -r $DIR/acados_repo/interfaces/acados_template/acados_template $DIR/ #pip3 install -e $DIR/acados/interfaces/acados_template # build tera diff --git a/third_party/bootstrap/.gitignore b/third_party/bootstrap/.gitignore new file mode 100644 index 0000000000..ac06c0cf85 --- /dev/null +++ b/third_party/bootstrap/.gitignore @@ -0,0 +1 @@ +/icons/ diff --git a/third_party/bootstrap/bootstrap-icons.svg b/third_party/bootstrap/bootstrap-icons.svg new file mode 100644 index 0000000000..61f2720db4 --- /dev/null +++ b/third_party/bootstrap/bootstrap-icons.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/third_party/bootstrap/pull.sh b/third_party/bootstrap/pull.sh new file mode 100755 index 0000000000..0b03b4db9e --- /dev/null +++ b/third_party/bootstrap/pull.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d icons/ ]; then + git clone https://github.com/twbs/icons/ +fi + +cd icons +git fetch --all +git checkout d5aa187483a1b0b186f87adcfa8576350d970d98 +cp bootstrap-icons.svg ../ diff --git a/selfdrive/controls/lib/cluster/.gitignore b/third_party/cluster/.gitignore similarity index 100% rename from selfdrive/controls/lib/cluster/.gitignore rename to third_party/cluster/.gitignore diff --git a/selfdrive/controls/lib/cluster/LICENSE b/third_party/cluster/LICENSE similarity index 100% rename from selfdrive/controls/lib/cluster/LICENSE rename to third_party/cluster/LICENSE diff --git a/selfdrive/controls/lib/cluster/README b/third_party/cluster/README similarity index 100% rename from selfdrive/controls/lib/cluster/README rename to third_party/cluster/README diff --git a/selfdrive/controls/lib/cluster/SConscript b/third_party/cluster/SConscript similarity index 100% rename from selfdrive/controls/lib/cluster/SConscript rename to third_party/cluster/SConscript diff --git a/third_party/cluster/__init__.py b/third_party/cluster/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/selfdrive/controls/lib/cluster/fastcluster.cpp b/third_party/cluster/fastcluster.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster.cpp rename to third_party/cluster/fastcluster.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster.h b/third_party/cluster/fastcluster.h similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster.h rename to third_party/cluster/fastcluster.h diff --git a/selfdrive/controls/lib/cluster/fastcluster_R_dm.cpp b/third_party/cluster/fastcluster_R_dm.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_R_dm.cpp rename to third_party/cluster/fastcluster_R_dm.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster_dm.cpp b/third_party/cluster/fastcluster_dm.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_dm.cpp rename to third_party/cluster/fastcluster_dm.cpp diff --git a/selfdrive/controls/lib/cluster/fastcluster_py.py b/third_party/cluster/fastcluster_py.py similarity index 100% rename from selfdrive/controls/lib/cluster/fastcluster_py.py rename to third_party/cluster/fastcluster_py.py diff --git a/selfdrive/controls/lib/cluster/test.cpp b/third_party/cluster/test.cpp similarity index 100% rename from selfdrive/controls/lib/cluster/test.cpp rename to third_party/cluster/test.cpp diff --git a/system/camerad/include/media/cam_cpas.h b/third_party/linux/include/media/cam_cpas.h similarity index 100% rename from system/camerad/include/media/cam_cpas.h rename to third_party/linux/include/media/cam_cpas.h diff --git a/system/camerad/include/media/cam_defs.h b/third_party/linux/include/media/cam_defs.h similarity index 100% rename from system/camerad/include/media/cam_defs.h rename to third_party/linux/include/media/cam_defs.h diff --git a/system/camerad/include/media/cam_fd.h b/third_party/linux/include/media/cam_fd.h similarity index 100% rename from system/camerad/include/media/cam_fd.h rename to third_party/linux/include/media/cam_fd.h diff --git a/system/camerad/include/media/cam_icp.h b/third_party/linux/include/media/cam_icp.h similarity index 100% rename from system/camerad/include/media/cam_icp.h rename to third_party/linux/include/media/cam_icp.h diff --git a/system/camerad/include/media/cam_isp.h b/third_party/linux/include/media/cam_isp.h similarity index 100% rename from system/camerad/include/media/cam_isp.h rename to third_party/linux/include/media/cam_isp.h diff --git a/system/camerad/include/media/cam_isp_ife.h b/third_party/linux/include/media/cam_isp_ife.h similarity index 100% rename from system/camerad/include/media/cam_isp_ife.h rename to third_party/linux/include/media/cam_isp_ife.h diff --git a/system/camerad/include/media/cam_isp_vfe.h b/third_party/linux/include/media/cam_isp_vfe.h similarity index 100% rename from system/camerad/include/media/cam_isp_vfe.h rename to third_party/linux/include/media/cam_isp_vfe.h diff --git a/system/camerad/include/media/cam_jpeg.h b/third_party/linux/include/media/cam_jpeg.h similarity index 100% rename from system/camerad/include/media/cam_jpeg.h rename to third_party/linux/include/media/cam_jpeg.h diff --git a/system/camerad/include/media/cam_lrme.h b/third_party/linux/include/media/cam_lrme.h similarity index 100% rename from system/camerad/include/media/cam_lrme.h rename to third_party/linux/include/media/cam_lrme.h diff --git a/system/camerad/include/media/cam_req_mgr.h b/third_party/linux/include/media/cam_req_mgr.h similarity index 100% rename from system/camerad/include/media/cam_req_mgr.h rename to third_party/linux/include/media/cam_req_mgr.h diff --git a/system/camerad/include/media/cam_sensor.h b/third_party/linux/include/media/cam_sensor.h similarity index 100% rename from system/camerad/include/media/cam_sensor.h rename to third_party/linux/include/media/cam_sensor.h diff --git a/system/camerad/include/media/cam_sensor_cmn_header.h b/third_party/linux/include/media/cam_sensor_cmn_header.h similarity index 100% rename from system/camerad/include/media/cam_sensor_cmn_header.h rename to third_party/linux/include/media/cam_sensor_cmn_header.h diff --git a/system/camerad/include/media/cam_sync.h b/third_party/linux/include/media/cam_sync.h similarity index 100% rename from system/camerad/include/media/cam_sync.h rename to third_party/linux/include/media/cam_sync.h diff --git a/system/camerad/include/msm_cam_sensor.h b/third_party/linux/include/msm_cam_sensor.h similarity index 100% rename from system/camerad/include/msm_cam_sensor.h rename to third_party/linux/include/msm_cam_sensor.h diff --git a/system/camerad/include/msm_camsensor_sdk.h b/third_party/linux/include/msm_camsensor_sdk.h similarity index 100% rename from system/camerad/include/msm_camsensor_sdk.h rename to third_party/linux/include/msm_camsensor_sdk.h diff --git a/selfdrive/modeld/thneed/include/msm_kgsl.h b/third_party/linux/include/msm_kgsl.h similarity index 100% rename from selfdrive/modeld/thneed/include/msm_kgsl.h rename to third_party/linux/include/msm_kgsl.h diff --git a/system/camerad/include/msmb_camera.h b/third_party/linux/include/msmb_camera.h similarity index 100% rename from system/camerad/include/msmb_camera.h rename to third_party/linux/include/msmb_camera.h diff --git a/system/camerad/include/msmb_isp.h b/third_party/linux/include/msmb_isp.h similarity index 100% rename from system/camerad/include/msmb_isp.h rename to third_party/linux/include/msmb_isp.h diff --git a/system/camerad/include/msmb_ispif.h b/third_party/linux/include/msmb_ispif.h similarity index 100% rename from system/camerad/include/msmb_ispif.h rename to third_party/linux/include/msmb_ispif.h diff --git a/tinygrad_repo b/tinygrad_repo index 870ea766ee..d8dda2af3a 160000 --- a/tinygrad_repo +++ b/tinygrad_repo @@ -1 +1 @@ -Subproject commit 870ea766eec7a38d7d590c81436f15271ba2667e +Subproject commit d8dda2af3afcef0bb772fff580cfa8b3eabf7f69 diff --git a/tools/README.md b/tools/README.md index 59eea3230d..3969deb24d 100644 --- a/tools/README.md +++ b/tools/README.md @@ -6,13 +6,12 @@ openpilot is developed and tested on **Ubuntu 20.04**, which is the primary deve ## Setup your PC - First, clone openpilot: ``` bash cd ~ git clone https://github.com/commaai/openpilot.git -cd openpilot +cd openpilot git submodule update --init ``` @@ -26,10 +25,10 @@ tools/ubuntu_setup.sh tools/mac_setup.sh ``` -Activate a shell with the install Python dependencies: +Activate a shell with the Python dependencies installed: ``` bash -cd openpilot && pipenv shell +cd openpilot && poetry shell ``` Build openpilot with this command: @@ -43,7 +42,7 @@ Neither openpilot nor any of the tools are developed or tested on Windows, but t Follow [these instructions](https://docs.microsoft.com/en-us/windows/wsl/install) to setup the WSL and install the `Ubuntu-20.04` distribution. Once your Ubuntu WSL environment is setup, follow the Linux setup instructions to finish setting up your environment. -GUI applications do not work with WSL out of the box. You will have to either [upgrade your system to Windows 11](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) or [set up an Xorg server](https://techcommunity.microsoft.com/t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242). +GUI applications do not work with WSL out of the box. You will have to either [upgrade your system to Windows 11](https://docs.microsoft.com/en-us/windows/wsl/tutorials/gui-apps) or [set up an Xorg server](https://techcommunity.microsoft.com/t5/windows-dev-appconsult/running-wsl-gui-apps-on-windows-10/ba-p/1493242). ## CTF diff --git a/tools/cabana/.gitignore b/tools/cabana/.gitignore index 88ffab2717..73879ab05d 100644 --- a/tools/cabana/.gitignore +++ b/tools/cabana/.gitignore @@ -3,3 +3,5 @@ moc_* _cabana settings +car_fingerprint_to_dbc.json +tests/_test_cabana diff --git a/tools/cabana/README.md b/tools/cabana/README.md index f64e6b2d2d..db247c39c5 100644 --- a/tools/cabana/README.md +++ b/tools/cabana/README.md @@ -6,4 +6,24 @@ Cabana is a tool developed to view raw CAN data. One use for this is creating an ## Usage Instructions +```bash +$ ./cabana -h +Usage: ./_cabana [options] route + +Options: + -h, --help Displays this help. + --demo use a demo route instead of providing your own + --qcam load qcamera + --ecam load wide road camera + --stream read can messages from live streaming + --zmq the ip address on which to receive zmq messages + --data_dir local directory with routes + --no-vipc do not output video + --dbc dbc file to open + +Arguments: + route the drive to replay. find your drives at + connect.comma.ai +``` + See [openpilot wiki](https://github.com/commaai/openpilot/wiki/Cabana) diff --git a/tools/cabana/SConscript b/tools/cabana/SConscript index fd44ecd138..3cb5913c01 100644 --- a/tools/cabana/SConscript +++ b/tools/cabana/SConscript @@ -1,3 +1,4 @@ +import os Import('env', 'qt_env', 'arch', 'common', 'messaging', 'visionipc', 'replay_lib', 'cereal', 'transformations', 'widgets', 'opendbc') @@ -7,10 +8,36 @@ base_libs = [common, messaging, cereal, visionipc, transformations, 'zmq', if arch == "Darwin": base_frameworks.append('OpenCL') + base_frameworks.append('QtCharts') else: base_libs.append('OpenCL') + base_libs.append('Qt5Charts') + +qt_libs = ['qt_util'] + base_libs -qt_libs = ['qt_util', 'Qt5Charts'] + base_libs cabana_libs = [widgets, cereal, messaging, visionipc, replay_lib, opendbc,'avutil', 'avcodec', 'avformat', 'bz2', 'curl', 'yuv'] + qt_libs -qt_env.Program('_cabana', ['cabana.cc', 'mainwin.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbcmanager.cc', - 'canmessages.cc', 'messageswidget.cc', 'detailwidget.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env = qt_env.Clone() +opendbc_path = '-DOPENDBC_FILE_PATH=\'"%s"\'' % (cabana_env.Dir("../../opendbc").abspath) +cabana_env['CXXFLAGS'] += [opendbc_path] + +# build assets +assets = "assets/assets.cc" +assets_src = "assets/assets.qrc" +cabana_env.Command(assets, assets_src, f"rcc $SOURCES -o $TARGET") +cabana_env.Depends(assets, Glob('/assets/*', exclude=[assets, assets_src, "assets/assets.o"])) + +prev_moc_path = cabana_env['QT_MOCHPREFIX'] +cabana_env['QT_MOCHPREFIX'] = os.path.dirname(prev_moc_path) + '/cabana/moc_' +cabana_lib = cabana_env.Library("cabana_lib", ['mainwin.cc', 'streams/livestream.cc', 'streams/abstractstream.cc', 'streams/replaystream.cc', 'binaryview.cc', 'chartswidget.cc', 'historylog.cc', 'videowidget.cc', 'signaledit.cc', 'dbc.cc', 'dbcmanager.cc', + 'commands.cc', 'messageswidget.cc', 'route.cc', 'settings.cc', 'util.cc', 'detailwidget.cc', 'tools/findsimilarbits.cc'], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) +cabana_env.Program('_cabana', ['cabana.cc', cabana_lib, assets], LIBS=cabana_libs, FRAMEWORKS=base_frameworks) + +if arch == "Darwin": + cabana_env.Execute('install_name_tool -change opendbc/can/libdbc.dylib @loader_path/../../opendbc/can/libdbc.dylib ./_cabana') + +if GetOption('test'): + cabana_env.Program('tests/_test_cabana', ['tests/test_runner.cc', 'tests/test_cabana.cc', cabana_lib], LIBS=[cabana_libs]) + +def generate_dbc_json(target, source, env): + env.Execute('tools/cabana/generate_dbc_json.py --out tools/cabana/car_fingerprint_to_dbc.json') +cabana_env.Command('generate_dbc_json', [], generate_dbc_json) diff --git a/tools/cabana/assets/.gitignore b/tools/cabana/assets/.gitignore new file mode 100644 index 0000000000..283034ca8b --- /dev/null +++ b/tools/cabana/assets/.gitignore @@ -0,0 +1 @@ +*.cc diff --git a/tools/cabana/assets/assets.qrc b/tools/cabana/assets/assets.qrc new file mode 100644 index 0000000000..6a8e5a3414 --- /dev/null +++ b/tools/cabana/assets/assets.qrc @@ -0,0 +1,6 @@ + + + ../../../third_party/bootstrap/bootstrap-icons.svg + cabana-icon.png + + diff --git a/tools/cabana/assets/cabana-icon.png b/tools/cabana/assets/cabana-icon.png new file mode 100644 index 0000000000..86add806fd Binary files /dev/null and b/tools/cabana/assets/cabana-icon.png differ diff --git a/tools/cabana/binaryview.cc b/tools/cabana/binaryview.cc index 71175e783e..0a4f6fc999 100644 --- a/tools/cabana/binaryview.cc +++ b/tools/cabana/binaryview.cc @@ -1,125 +1,318 @@ #include "tools/cabana/binaryview.h" -#include -#include -#include +#include -#include "tools/cabana/canmessages.h" +#include +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/commands.h" +#include "tools/cabana/signaledit.h" // BinaryView -const int CELL_HEIGHT = 30; +const int CELL_HEIGHT = 36; +const int VERTICAL_HEADER_WIDTH = 30; + +inline int get_bit_index(const QModelIndex &index, bool little_endian) { + return index.row() * 8 + (little_endian ? 7 - index.column() : index.column()); +} BinaryView::BinaryView(QWidget *parent) : QTableView(parent) { model = new BinaryViewModel(this); setModel(model); - setItemDelegate(new BinaryItemDelegate(this)); + delegate = new BinaryItemDelegate(this); + setItemDelegate(delegate); horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + verticalHeader()->setSectionsClickable(false); + verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + verticalHeader()->setDefaultSectionSize(CELL_HEIGHT); horizontalHeader()->hide(); - verticalHeader()->setSectionResizeMode(QHeaderView::Stretch); + setShowGrid(false); + setMouseTracking(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - // replace selection model - auto old_model = selectionModel(); - setSelectionModel(new BinarySelectionModel(model)); - delete old_model; + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &BinaryView::refresh); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &BinaryView::refresh); - QObject::connect(model, &QAbstractItemModel::modelReset, [this]() { - setFixedHeight((CELL_HEIGHT + 1) * std::min(model->rowCount(), 8) + 2); + addShortcuts(); + setWhatsThis(R"( + Binary View
+ + Shortcuts
+ Delete Signal: +  x , +  Backspace , +  Delete 
+ Change endianness:  e 
+ Change singedness:  s 
+ Open chart: +  c , +  p , +  g  + )"); +} + +void BinaryView::addShortcuts() { + // Delete (x, backspace, delete) + QShortcut *shortcut_delete_x = new QShortcut(QKeySequence(Qt::Key_X), this); + QShortcut *shortcut_delete_backspace = new QShortcut(QKeySequence(Qt::Key_Backspace), this); + QShortcut *shortcut_delete_delete = new QShortcut(QKeySequence(Qt::Key_Delete), this); + QObject::connect(shortcut_delete_delete, &QShortcut::activated, shortcut_delete_x, &QShortcut::activated); + QObject::connect(shortcut_delete_backspace, &QShortcut::activated, shortcut_delete_x, &QShortcut::activated); + QObject::connect(shortcut_delete_x, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + emit removeSignal(hovered_sig); + hovered_sig = nullptr; + } }); + + // Change endianness (e) + QShortcut *shortcut_endian = new QShortcut(QKeySequence(Qt::Key_E), this); + QObject::connect(shortcut_endian, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + const cabana::Signal *hovered_sig_prev = hovered_sig; + cabana::Signal s = *hovered_sig; + s.is_little_endian = !s.is_little_endian; + emit editSignal(hovered_sig, s); + + hovered_sig = nullptr; + highlight(hovered_sig_prev); + } + }); + + // Change signedness (s) + QShortcut *shortcut_sign = new QShortcut(QKeySequence(Qt::Key_S), this); + QObject::connect(shortcut_sign, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + const cabana::Signal *hovered_sig_prev = hovered_sig; + cabana::Signal s = *hovered_sig; + s.is_signed = !s.is_signed; + emit editSignal(hovered_sig, s); + + hovered_sig = nullptr; + highlight(hovered_sig_prev); + } + }); + + // Open chart (c, p, g) + QShortcut *shortcut_plot = new QShortcut(QKeySequence(Qt::Key_P), this); + QShortcut *shortcut_plot_g = new QShortcut(QKeySequence(Qt::Key_G), this); + QShortcut *shortcut_plot_c = new QShortcut(QKeySequence(Qt::Key_C), this); + QObject::connect(shortcut_plot_g, &QShortcut::activated, shortcut_plot, &QShortcut::activated); + QObject::connect(shortcut_plot_c, &QShortcut::activated, shortcut_plot, &QShortcut::activated); + QObject::connect(shortcut_plot, &QShortcut::activated, [=]{ + if (hovered_sig != nullptr) { + emit showChart(model->msg_id, hovered_sig, true, false); + } + }); +} + +QSize BinaryView::minimumSizeHint() const { + return {(horizontalHeader()->minimumSectionSize() + 1) * 9 + VERTICAL_HEADER_WIDTH + 2, + CELL_HEIGHT * std::min(model->rowCount(), 10) + 2}; +} + +void BinaryView::highlight(const cabana::Signal *sig) { + if (sig != hovered_sig) { + for (int i = 0; i < model->items.size(); ++i) { + auto &item_sigs = model->items[i].sigs; + if ((sig && item_sigs.contains(sig)) || (hovered_sig && item_sigs.contains(hovered_sig))) { + auto index = model->index(i / model->columnCount(), i % model->columnCount()); + emit model->dataChanged(index, index, {Qt::DisplayRole}); + } + } + + if (sig && underMouse()) { + QString tooltip = tr(R"(%1
+ Size:%2 LE:%3 SGD:%4 + )").arg(sig->name).arg(sig->size).arg(sig->is_little_endian ? "Y" : "N").arg(sig->is_signed ? "Y" : "N"); + QToolTip::showText(QCursor::pos(), tooltip, this, rect()); + } else { + QToolTip::showText(QCursor::pos(), "", this, rect()); + } + + hovered_sig = sig; + emit signalHovered(hovered_sig); + } +} + +void BinaryView::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) { + auto index = indexAt(viewport()->mapFromGlobal(QCursor::pos())); + if (!anchor_index.isValid() || !index.isValid()) + return; + + QItemSelection selection; + auto [start, size, is_lb] = getSelection(index); + for (int i = start; i < start + size; ++i) { + auto idx = model->bitIndex(i, is_lb); + selection.merge({idx, idx}, flags); + } + selectionModel()->select(selection, flags); +} + +void BinaryView::mousePressEvent(QMouseEvent *event) { + delegate->selection_color = (palette().color(QPalette::Active, QPalette::Highlight)); + if (auto index = indexAt(event->pos()); index.isValid() && index.column() != 8) { + anchor_index = index; + auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); + int bit_idx = get_bit_index(anchor_index, true); + for (auto s : item->sigs) { + if (bit_idx == s->lsb || bit_idx == s->msb) { + anchor_index = model->bitIndex(bit_idx == s->lsb ? s->msb : s->lsb, true); + resize_sig = s; + delegate->selection_color = getColor(s); + break; + } + } + } + event->accept(); +} + +void BinaryView::highlightPosition(const QPoint &pos) { + if (auto index = indexAt(viewport()->mapFromGlobal(pos)); index.isValid()) { + auto item = (BinaryViewModel::Item *)index.internalPointer(); + const cabana::Signal *sig = item->sigs.isEmpty() ? nullptr : item->sigs.back(); + highlight(sig); + } +} + +void BinaryView::mouseMoveEvent(QMouseEvent *event) { + highlightPosition(event->globalPos()); + QTableView::mouseMoveEvent(event); } void BinaryView::mouseReleaseEvent(QMouseEvent *event) { QTableView::mouseReleaseEvent(event); - if (auto indexes = selectedIndexes(); !indexes.isEmpty()) { - int start_bit = indexes.first().row() * 8 + indexes.first().column(); - int size = indexes.back().row() * 8 + indexes.back().column() - start_bit + 1; - emit cellsSelected(start_bit, size); + auto release_index = indexAt(event->pos()); + if (release_index.isValid() && anchor_index.isValid()) { + if (selectionModel()->hasSelection()) { + auto [start_bit, size, is_lb] = getSelection(release_index); + resize_sig ? emit resizeSignal(resize_sig, start_bit, size) + : emit addSignal(start_bit, size, is_lb); + } else { + auto item = (const BinaryViewModel::Item *)anchor_index.internalPointer(); + if (item && item->sigs.size() > 0) + emit signalClicked(item->sigs.back()); + } } -} - -void BinaryView::setMessage(const QString &message_id) { - msg_id = message_id; - model->setMessage(message_id); - resizeRowsToContents(); clearSelection(); - updateState(); + anchor_index = QModelIndex(); + resize_sig = nullptr; } -void BinaryView::updateState() { - model->updateState(); +void BinaryView::leaveEvent(QEvent *event) { + highlight(nullptr); + QTableView::leaveEvent(event); +} + +void BinaryView::setMessage(const MessageId &message_id) { + model->msg_id = message_id; + verticalScrollBar()->setValue(0); + refresh(); +} + +void BinaryView::refresh() { + clearSelection(); + anchor_index = QModelIndex(); + resize_sig = nullptr; + hovered_sig = nullptr; + model->refresh(); + highlightPosition(QCursor::pos()); +} + +QSet BinaryView::getOverlappingSignals() const { + QSet overlapping; + for (auto &item : model->items) { + if (item.sigs.size() > 1) + for (auto s : item.sigs) overlapping += s; + } + return overlapping; +} + +std::tuple BinaryView::getSelection(QModelIndex index) { + if (index.column() == 8) { + index = model->index(index.row(), 7); + } + bool is_lb = (resize_sig && resize_sig->is_little_endian) || (!resize_sig && index < anchor_index); + int cur_bit_idx = get_bit_index(index, is_lb); + int anchor_bit_idx = get_bit_index(anchor_index, is_lb); + auto [start_bit, end_bit] = std::minmax(cur_bit_idx, anchor_bit_idx); + return {start_bit, end_bit - start_bit + 1, is_lb}; } // BinaryViewModel -void BinaryViewModel::setMessage(const QString &message_id) { - msg_id = message_id; - +void BinaryViewModel::refresh() { beginResetModel(); items.clear(); - row_count = 0; - - dbc_msg = dbc()->msg(msg_id); - if (dbc_msg) { + if (auto dbc_msg = dbc()->msg(msg_id)) { row_count = dbc_msg->size; items.resize(row_count * column_count); - for (int i = 0; i < dbc_msg->sigs.size(); ++i) { - const auto &sig = dbc_msg->sigs[i]; - const int start = sig.is_little_endian ? sig.start_bit : bigEndianBitIndex(sig.start_bit); - const int end = start + sig.size - 1; + for (auto sig : dbc_msg->getSignals()) { + auto [start, end] = getSignalRange(sig); for (int j = start; j <= end; ++j) { - int idx = column_count * (j / 8) + j % 8; + int bit_index = sig->is_little_endian ? bigEndianBitIndex(j) : j; + int idx = column_count * (bit_index / 8) + bit_index % 8; if (idx >= items.size()) { - qWarning() << "signal " << sig.name.c_str() << "out of bounds.start_bit:" << sig.start_bit << "size:" << sig.size; + qWarning() << "signal " << sig->name << "out of bounds.start_bit:" << sig->start_bit << "size:" << sig->size; break; } - if (j == start) { - sig.is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; - } else if (j == end) { - sig.is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; - } - items[idx].bg_color = QColor(getColor(i)); + if (j == start) sig->is_little_endian ? items[idx].is_lsb = true : items[idx].is_msb = true; + if (j == end) sig->is_little_endian ? items[idx].is_msb = true : items[idx].is_lsb = true; + items[idx].bg_color = getColor(sig); + items[idx].sigs.push_back(sig); } } + } else { + row_count = can->lastMessage(msg_id).dat.size(); + items.resize(row_count * column_count); } - endResetModel(); -} - -QModelIndex BinaryViewModel::index(int row, int column, const QModelIndex &parent) const { - return createIndex(row, column, (void *)&items[row * column_count + column]); -} - -Qt::ItemFlags BinaryViewModel::flags(const QModelIndex &index) const { - return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable; + updateState(); } void BinaryViewModel::updateState() { auto prev_items = items; + const auto &last_msg = can->lastMessage(msg_id); + const auto &binary = last_msg.dat; - const auto &binary = can->lastMessage(msg_id).dat; // data size may changed. - if (!dbc_msg && binary.size() != row_count) { - beginResetModel(); + if (binary.size() > row_count) { + beginInsertRows({}, row_count, binary.size() - 1); row_count = binary.size(); - items.clear(); items.resize(row_count * column_count); - endResetModel(); + endInsertRows(); } - char hex[3] = {'\0'}; - for (int i = 0; i < std::min(binary.size(), row_count); ++i) { - for (int j = 0; j < column_count - 1; ++j) { - items[i * column_count + j].val = QChar((binary[i] >> (7 - j)) & 1 ? '1' : '0'); + double max_f = 255.0; + double factor = 0.25; + double scaler = max_f / log2(1.0 + factor); + for (int i = 0; i < binary.size(); ++i) { + for (int j = 0; j < 8; ++j) { + auto &item = items[i * column_count + j]; + item.val = ((binary[i] >> (7 - j)) & 1) != 0 ? '1' : '0'; + // Bit update frequency based highlighting + double offset = !item.sigs.empty() ? 50 : 0; + auto n = last_msg.bit_change_counts[i][7 - j]; + double min_f = n == 0 ? offset : offset + 25; + double alpha = std::clamp(offset + log2(1.0 + factor * (double)n / (double)last_msg.count) * scaler, min_f, max_f); + item.bg_color.setAlpha(alpha); } - hex[0] = toHex(binary[i] >> 4); - hex[1] = toHex(binary[i] & 0xf); - items[i * column_count + 8].val = hex; + items[i * column_count + 8].val = toHex(binary[i]); + items[i * column_count + 8].bg_color = last_msg.colors[i]; + } + for (int i = binary.size() * column_count; i < items.size(); ++i) { + items[i].val = "-"; } for (int i = 0; i < items.size(); ++i) { - if (i >= prev_items.size() || prev_items[i].val != items[i].val) { + if (i >= prev_items.size() || prev_items[i].val != items[i].val || prev_items[i].bg_color != items[i].bg_color) { auto idx = index(i / column_count, i % column_count); emit dataChanged(idx, idx); } @@ -129,56 +322,109 @@ void BinaryViewModel::updateState() { QVariant BinaryViewModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Vertical) { switch (role) { - case Qt::DisplayRole: return section + 1; - case Qt::SizeHintRole: return QSize(30, CELL_HEIGHT); + case Qt::DisplayRole: return section; + case Qt::SizeHintRole: return QSize(VERTICAL_HEADER_WIDTH, 0); case Qt::TextAlignmentRole: return Qt::AlignCenter; } } return {}; } -// BinarySelectionModel - -void BinarySelectionModel::select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) { - QItemSelection new_selection = selection; - if (auto indexes = selection.indexes(); !indexes.isEmpty()) { - auto [begin_idx, end_idx] = (QModelIndex[]){indexes.first(), indexes.back()}; - for (int row = begin_idx.row(); row <= end_idx.row(); ++row) { - int left_col = (row == begin_idx.row()) ? begin_idx.column() : 0; - int right_col = (row == end_idx.row()) ? end_idx.column() : 7; - new_selection.merge({model()->index(row, left_col), model()->index(row, right_col)}, command); - } - } - QItemSelectionModel::select(new_selection, command); -} - // BinaryItemDelegate BinaryItemDelegate::BinaryItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { - // cache fonts and color - small_font.setPointSize(6); - bold_font.setBold(true); - highlight_color = QApplication::style()->standardPalette().color(QPalette::Active, QPalette::Highlight); + small_font.setPixelSize(8); + hex_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + hex_font.setBold(true); } -QSize BinaryItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { - QSize sz = QStyledItemDelegate::sizeHint(option, index); - return {sz.width(), CELL_HEIGHT}; +bool BinaryItemDelegate::isSameColor(const QModelIndex &index, int dx, int dy) const { + QModelIndex index2 = index.sibling(index.row() + dy, index.column() + dx); + if (!index2.isValid()) { + return false; + } + auto color1 = ((const BinaryViewModel::Item *)index.internalPointer())->bg_color; + auto color2 = ((const BinaryViewModel::Item *)index2.internalPointer())->bg_color; + // Ignore alpha + return (color1.red() == color2.red()) && (color2.green() == color2.green()) && (color1.blue() == color2.blue()); } void BinaryItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { auto item = (const BinaryViewModel::Item *)index.internalPointer(); + BinaryView *bin_view = (BinaryView *)parent(); painter->save(); - // TODO: highlight signal cells on mouse over - painter->fillRect(option.rect, option.state & QStyle::State_Selected ? highlight_color : item->bg_color); + if (index.column() == 8) { - painter->setFont(bold_font); + painter->setFont(hex_font); + painter->fillRect(option.rect, item->bg_color); + } else if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, selection_color); + painter->setPen(option.palette.color(QPalette::BrightText)); + } else if (!bin_view->selectionModel()->hasSelection() || !item->sigs.contains(bin_view->resize_sig)) { // not resizing + QColor bg = item->bg_color; + if (bin_view->hovered_sig && item->sigs.contains(bin_view->hovered_sig)) { + bg.setAlpha(255); + painter->fillRect(option.rect, bg.darker(125)); // 4/5x brightness + painter->setPen(option.palette.color(QPalette::BrightText)); + } else { + if (item->sigs.size() > 0) { + drawBorder(painter, option, index); + bg.setAlpha(std::max(50, bg.alpha())); + } + painter->fillRect(option.rect, bg); + painter->setPen(option.palette.color(QPalette::Text)); + } } + painter->drawText(option.rect, Qt::AlignCenter, item->val); if (item->is_msb || item->is_lsb) { painter->setFont(small_font); - painter->drawText(option.rect, Qt::AlignHCenter | Qt::AlignBottom, item->is_msb ? "MSB" : "LSB"); + painter->drawText(option.rect.adjusted(8, 0, -8, -3), Qt::AlignRight | Qt::AlignBottom, item->is_msb ? "M" : "L"); } - painter->restore(); } + +// Draw border on edge of signal +void BinaryItemDelegate::drawBorder(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto item = (const BinaryViewModel::Item *)index.internalPointer(); + QColor border_color = item->bg_color; + border_color.setAlphaF(1.0); + + bool draw_left = !isSameColor(index, -1, 0); + bool draw_top = !isSameColor(index, 0, -1); + bool draw_right = !isSameColor(index, 1, 0); + bool draw_bottom = !isSameColor(index, 0, 1); + + const int spacing = 2; + QRect rc = option.rect.adjusted(draw_left * 3, draw_top * spacing, draw_right * -3, draw_bottom * -spacing); + QRegion subtract; + if (!draw_top) { + if (!draw_left && !isSameColor(index, -1, -1)) { + subtract += QRect{rc.left(), rc.top(), 3, spacing}; + } else if (!draw_right && !isSameColor(index, 1, -1)) { + subtract += QRect{rc.right() - 2, rc.top(), 3, spacing}; + } + } + if (!draw_bottom) { + if (!draw_left && !isSameColor(index, -1, 1)) { + subtract += QRect{rc.left(), rc.bottom() - (spacing - 1), 3, spacing}; + } else if (!draw_right && !isSameColor(index, 1, 1)) { + subtract += QRect{rc.right() - 2, rc.bottom() - (spacing - 1), 3, spacing}; + } + } + + painter->setPen(QPen(border_color, 1)); + if (draw_left) painter->drawLine(rc.topLeft(), rc.bottomLeft()); + if (draw_right) painter->drawLine(rc.topRight(), rc.bottomRight()); + if (draw_bottom) painter->drawLine(rc.bottomLeft(), rc.bottomRight()); + if (draw_top) painter->drawLine(rc.topLeft(), rc.topRight()); + + painter->setClipRegion(QRegion(rc).subtracted(subtract)); + if (!subtract.isEmpty()) { + // fill gaps inside corners. + painter->setPen(QPen(border_color, 2, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin)); + for (auto &r : subtract) { + painter->drawRect(r); + } + } +} diff --git a/tools/cabana/binaryview.h b/tools/cabana/binaryview.h index 631797ca48..f677d11941 100644 --- a/tools/cabana/binaryview.h +++ b/tools/cabana/binaryview.h @@ -1,57 +1,55 @@ #pragma once +#include +#include +#include #include #include #include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" class BinaryItemDelegate : public QStyledItemDelegate { - Q_OBJECT - public: BinaryItemDelegate(QObject *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; - QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setSelectionColor(const QColor &color) { selection_color = color; } + bool isSameColor(const QModelIndex &index, int dx, int dy) const; + void drawBorder(QPainter* painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; -private: - QFont small_font, bold_font; - QColor highlight_color; + QFont small_font, hex_font; + QColor selection_color; }; class BinaryViewModel : public QAbstractTableModel { - Q_OBJECT - public: BinaryViewModel(QObject *parent) : QAbstractTableModel(parent) {} - void setMessage(const QString &message_id); + void refresh(); void updateState(); - Qt::ItemFlags flags(const QModelIndex &index) const; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { return {}; } int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + inline QModelIndex bitIndex(int bit, bool is_lb) const { return index(bit / 8, is_lb ? (7 - bit % 8) : bit % 8); } + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override { + return createIndex(row, column, (void *)&items[row * column_count + column]); + } + Qt::ItemFlags flags(const QModelIndex &index) const override { + return (index.column() == column_count - 1) ? Qt::ItemIsEnabled : Qt::ItemIsEnabled | Qt::ItemIsSelectable; + } struct Item { - QColor bg_color = QColor(Qt::white); + QColor bg_color = QColor(102, 86, 169, 0); bool is_msb = false; bool is_lsb = false; - QString val = "0"; + QString val = "-"; + QList sigs; }; + std::vector items; -private: - QString msg_id; - const Msg *dbc_msg; + MessageId msg_id; int row_count = 0; const int column_count = 9; - std::vector items; -}; - -// the default QItemSelectionModel does not support our selection mode. -class BinarySelectionModel : public QItemSelectionModel { - public: - BinarySelectionModel(QAbstractItemModel *model = nullptr) : QItemSelectionModel(model) {} - void select(const QItemSelection &selection, QItemSelectionModel::SelectionFlags command) override; }; class BinaryView : public QTableView { @@ -59,14 +57,36 @@ class BinaryView : public QTableView { public: BinaryView(QWidget *parent = nullptr); - void mouseReleaseEvent(QMouseEvent *event) override; - void setMessage(const QString &message_id); - void updateState(); + void setMessage(const MessageId &message_id); + void highlight(const cabana::Signal *sig); + QSet getOverlappingSignals() const; + inline void updateState() { model->updateState(); } + QSize minimumSizeHint() const override; signals: - void cellsSelected(int start_bit, int size); + void signalClicked(const cabana::Signal *sig); + void signalHovered(const cabana::Signal *sig); + void addSignal(int start_bit, int size, bool little_endian); + void resizeSignal(const cabana::Signal *sig, int from, int size); + void removeSignal(const cabana::Signal *sig); + void editSignal(const cabana::Signal *origin_s, cabana::Signal &s); + void showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge); private: - QString msg_id; + void addShortcuts(); + void refresh(); + std::tuple getSelection(QModelIndex index); + void setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags flags) override; + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void leaveEvent(QEvent *event) override; + void highlightPosition(const QPoint &pt); + + QModelIndex anchor_index; BinaryViewModel *model; + BinaryItemDelegate *delegate; + const cabana::Signal *resize_sig = nullptr; + const cabana::Signal *hovered_sig = nullptr; + friend class BinaryItemDelegate; }; diff --git a/tools/cabana/cabana b/tools/cabana/cabana index b29dd66e3d..14647b6a10 100755 --- a/tools/cabana/cabana +++ b/tools/cabana/cabana @@ -1,4 +1,4 @@ #!/bin/sh cd "$(dirname "$0")" export LD_LIBRARY_PATH="../../opendbc/can:$LD_LIBRARY_PATH" -exec ./_cabana "$1" +exec ./_cabana "$@" diff --git a/tools/cabana/cabana.cc b/tools/cabana/cabana.cc index 1096487973..5c182f435f 100644 --- a/tools/cabana/cabana.cc +++ b/tools/cabana/cabana.cc @@ -1,33 +1,79 @@ #include #include -#include +#include "common/prefix.h" #include "selfdrive/ui/qt/util.h" #include "tools/cabana/mainwin.h" +#include "tools/cabana/route.h" +#include "tools/cabana/streams/livestream.h" +#include "tools/cabana/streams/replaystream.h" int main(int argc, char *argv[]) { + QCoreApplication::setApplicationName("Cabana"); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); initApp(argc, argv); QApplication app(argc, argv); - app.setStyle(QStyleFactory::create("Fusion")); + app.setApplicationDisplayName("Cabana"); + app.setWindowIcon(QIcon(":cabana-icon.png")); QCommandLineParser cmd_parser; cmd_parser.addHelpOption(); cmd_parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); cmd_parser.addOption({"demo", "use a demo route instead of providing your own"}); cmd_parser.addOption({"qcam", "load qcamera"}); + cmd_parser.addOption({"ecam", "load wide road camera"}); + cmd_parser.addOption({"stream", "read can messages from live streaming"}); + cmd_parser.addOption({"zmq", "the ip address on which to receive zmq messages", "zmq"}); cmd_parser.addOption({"data_dir", "local directory with routes", "data_dir"}); + cmd_parser.addOption({"no-vipc", "do not output video"}); + cmd_parser.addOption({"dbc", "dbc file to open", "dbc"}); cmd_parser.process(app); - const QStringList args = cmd_parser.positionalArguments(); - if (args.empty() && !cmd_parser.isSet("demo")) { - cmd_parser.showHelp(); + + std::unique_ptr op_prefix; + std::unique_ptr stream; + + if (cmd_parser.isSet("stream")) { + stream.reset(new LiveStream(&app, cmd_parser.value("zmq"))); + } else { + // TODO: Remove when OpenpilotPrefix supports ZMQ +#ifndef __APPLE__ + op_prefix.reset(new OpenpilotPrefix()); +#endif + uint32_t replay_flags = REPLAY_FLAG_NONE; + if (cmd_parser.isSet("ecam")) { + replay_flags |= REPLAY_FLAG_ECAM; + } else if (cmd_parser.isSet("qcam")) { + replay_flags |= REPLAY_FLAG_QCAMERA; + } else if (cmd_parser.isSet("no-vipc")) { + replay_flags |= REPLAY_FLAG_NO_VIPC; + } + + const QStringList args = cmd_parser.positionalArguments(); + QString route; + if (args.size() > 0) { + route = args.first(); + } else if (cmd_parser.isSet("demo")) { + route = DEMO_ROUTE; + } + + auto replay_stream = new ReplayStream(replay_flags, &app); + stream.reset(replay_stream); + if (route.isEmpty()) { + if (OpenRouteDialog dlg(nullptr); !dlg.exec()) { + return 0; + } + } else if (!replay_stream->loadRoute(route, cmd_parser.value("data_dir"))) { + return 0; + } } - const QString route = args.empty() ? DEMO_ROUTE : args.first(); - CANMessages p(&app); - if (!p.loadRoute(route, cmd_parser.value("data_dir"), cmd_parser.isSet("qcam"))) { - return 0; - } MainWindow w; - w.showMaximized(); + + // Load DBC + if (cmd_parser.isSet("dbc")) { + w.loadFile(cmd_parser.value("dbc")); + } + + w.show(); return app.exec(); } diff --git a/tools/cabana/canmessages.cc b/tools/cabana/canmessages.cc deleted file mode 100644 index 252a8c680c..0000000000 --- a/tools/cabana/canmessages.cc +++ /dev/null @@ -1,155 +0,0 @@ -#include "tools/cabana/canmessages.h" - -#include -#include - -Q_DECLARE_METATYPE(std::vector); - -Settings settings; -CANMessages *can = nullptr; - -CANMessages::CANMessages(QObject *parent) : QObject(parent) { - can = this; - - qRegisterMetaType>(); - QObject::connect(this, &CANMessages::received, this, &CANMessages::process, Qt::QueuedConnection); - QObject::connect(&settings, &Settings::changed, this, &CANMessages::settingChanged); -} - -CANMessages::~CANMessages() { - replay->stop(); -} - -static bool event_filter(const Event *e, void *opaque) { - CANMessages *c = (CANMessages *)opaque; - return c->eventFilter(e); -} - -bool CANMessages::loadRoute(const QString &route, const QString &data_dir, bool use_qcam) { - routeName = route; - replay = new Replay(route, {"can", "roadEncodeIdx", "carParams"}, {}, nullptr, use_qcam ? REPLAY_FLAG_QCAMERA : 0, data_dir, this); - replay->setSegmentCacheLimit(settings.cached_segment_limit); - replay->installEventFilter(event_filter, this); - QObject::connect(replay, &Replay::segmentsMerged, this, &CANMessages::segmentsMerged); - if (replay->load()) { - replay->start(); - return true; - } - return false; -} - -void CANMessages::process(QHash> *messages) { - for (auto it = messages->begin(); it != messages->end(); ++it) { - ++counters[it.key()]; - auto &msgs = can_msgs[it.key()]; - const auto &new_msgs = it.value(); - if (new_msgs.size() == settings.can_msg_log_size || msgs.empty()) { - msgs = std::move(new_msgs); - } else { - msgs.insert(msgs.begin(), std::make_move_iterator(new_msgs.begin()), std::make_move_iterator(new_msgs.end())); - while (msgs.size() >= settings.can_msg_log_size) { - msgs.pop_back(); - } - } - } - delete messages; - - if (current_sec < begin_sec || current_sec > end_sec) { - // loop replay in selected range. - seekTo(begin_sec); - } else { - emit updated(); - } -} - -bool CANMessages::eventFilter(const Event *event) { - static double prev_update_sec = 0; - // drop packets when the GUI thread is calling seekTo. to make sure the current_sec is accurate. - if (!seeking && event->which == cereal::Event::Which::CAN) { - if (!received_msgs) { - received_msgs.reset(new QHash>); - received_msgs->reserve(1000); - } - - current_sec = (event->mono_time - replay->routeStartTime()) / (double)1e9; - auto can_events = event->event.getCan(); - for (const auto &c : can_events) { - QString id = QString("%1:%2").arg(c.getSrc()).arg(c.getAddress(), 1, 16); - auto &list = (*received_msgs)[id]; - while (list.size() >= settings.can_msg_log_size) { - list.pop_back(); - } - CanData &data = list.emplace_front(); - data.ts = current_sec; - data.bus_time = c.getBusTime(); - data.dat.append((char *)c.getDat().begin(), c.getDat().size()); - } - - if (current_sec < prev_update_sec || (current_sec - prev_update_sec) > 1.0 / settings.fps) { - prev_update_sec = current_sec; - // use pointer to avoid data copy in queued connection. - emit received(received_msgs.release()); - } - } - return true; -} - -void CANMessages::seekTo(double ts) { - seeking = true; - replay->seekTo(ts, false); - seeking = false; -} - -void CANMessages::setRange(double min, double max) { - if (begin_sec != min || end_sec != max) { - begin_sec = min; - end_sec = max; - is_zoomed = begin_sec != event_begin_sec || end_sec != event_end_sec; - emit rangeChanged(min, max); - } -} - -void CANMessages::segmentsMerged() { - auto events = replay->events(); - if (!events || events->empty()) return; - - auto it = std::find_if(events->begin(), events->end(), [=](const Event *e) { return e->which == cereal::Event::Which::CAN; }); - event_begin_sec = it == events->end() ? 0 : ((*it)->mono_time - replay->routeStartTime()) / (double)1e9; - event_end_sec = double(events->back()->mono_time - replay->routeStartTime()) / 1e9; - if (!is_zoomed) { - begin_sec = event_begin_sec; - end_sec = event_end_sec; - } - emit eventsMerged(); -} - -void CANMessages::resetRange() { - setRange(event_begin_sec, event_end_sec); -} - -void CANMessages::settingChanged() { - replay->setSegmentCacheLimit(settings.cached_segment_limit); -} - -// Settings - -Settings::Settings() { - load(); -} - -void Settings::save() { - QSettings s("settings", QSettings::IniFormat); - s.setValue("fps", fps); - s.setValue("log_size", can_msg_log_size); - s.setValue("cached_segment", cached_segment_limit); - s.setValue("chart_height", chart_height); - emit changed(); -} - -void Settings::load() { - QSettings s("settings", QSettings::IniFormat); - fps = s.value("fps", 10).toInt(); - can_msg_log_size = s.value("log_size", 100).toInt(); - cached_segment_limit = s.value("cached_segment", 3).toInt(); - chart_height = s.value("chart_height", 200).toInt(); -} diff --git a/tools/cabana/canmessages.h b/tools/cabana/canmessages.h deleted file mode 100644 index 58f5ad70b7..0000000000 --- a/tools/cabana/canmessages.h +++ /dev/null @@ -1,104 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include "tools/replay/replay.h" - -class Settings : public QObject { - Q_OBJECT - -public: - Settings(); - void save(); - void load(); - - int fps = 10; - int can_msg_log_size = 100; - int cached_segment_limit = 3; - int chart_height = 200; - -signals: - void changed(); -}; - -struct CanData { - double ts; - uint16_t bus_time; - QByteArray dat; -}; - -class CANMessages : public QObject { - Q_OBJECT - -public: - CANMessages(QObject *parent); - ~CANMessages(); - bool loadRoute(const QString &route, const QString &data_dir, bool use_qcam); - void seekTo(double ts); - void resetRange(); - void setRange(double min, double max); - bool eventFilter(const Event *event); - - inline std::pair range() const { return {begin_sec, end_sec}; } - inline QString route() const { return routeName; } - inline QString carFingerprint() const { return replay->carFingerprint().c_str(); } - inline double totalSeconds() const { return replay->totalSeconds(); } - inline double routeStartTime() const { return replay->routeStartTime() / (double)1e9; } - inline double currentSec() const { return current_sec; } - inline bool isZoomed() const { return is_zoomed; } - inline const std::deque &messages(const QString &id) { return can_msgs[id]; } - inline const CanData &lastMessage(const QString &id) { return can_msgs[id].front(); } - - inline const std::vector *events() const { return replay->events(); } - inline void setSpeed(float speed) { replay->setSpeed(speed); } - inline bool isPaused() const { return replay->isPaused(); } - inline void pause(bool pause) { replay->pause(pause); } - inline const std::vector> getTimeline() { return replay->getTimeline(); } - -signals: - void eventsMerged(); - void rangeChanged(double min, double max); - void updated(); - void received(QHash> *); - -public: - QMap> can_msgs; - std::unique_ptr>> received_msgs = nullptr; - QHash counters; - -protected: - void process(QHash> *); - void segmentsMerged(); - void settingChanged(); - - std::atomic current_sec = 0.; - std::atomic seeking = false; - double begin_sec = 0; - double end_sec = 0; - double event_begin_sec = 0; - double event_end_sec = 0; - bool is_zoomed = false; - QString routeName; - Replay *replay = nullptr; -}; - -inline QString toHex(const QByteArray &dat) { - return dat.toHex(' ').toUpper(); -} -inline char toHex(uint value) { - return "0123456789ABCDEF"[value & 0xF]; -} - -inline const QString &getColor(int i) { - // TODO: add more colors - static const QString SIGNAL_COLORS[] = {"#9FE2BF", "#40E0D0", "#6495ED", "#CCCCFF", "#FF7F50", "#FFBF00"}; - return SIGNAL_COLORS[i % std::size(SIGNAL_COLORS)]; -} - -// A global pointer referring to the unique CANMessages object -extern CANMessages *can; -extern Settings settings; diff --git a/tools/cabana/chartswidget.cc b/tools/cabana/chartswidget.cc index 811eae68cb..a6eabfd1ae 100644 --- a/tools/cabana/chartswidget.cc +++ b/tools/cabana/chartswidget.cc @@ -1,365 +1,994 @@ #include "tools/cabana/chartswidget.h" +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include -#include -#include -#include +#include +#include +#include + +const int MAX_COLUMN_COUNT = 4; +static inline bool xLessThan(const QPointF &p, float x) { return p.x() < x; } // ChartsWidget -ChartsWidget::ChartsWidget(QWidget *parent) : QWidget(parent) { +ChartsWidget::ChartsWidget(QWidget *parent) : align_timer(this), QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - // title bar - title_bar = new QWidget(this); - title_bar->setVisible(false); - QHBoxLayout *title_layout = new QHBoxLayout(title_bar); - title_layout->setContentsMargins(0, 0, 0, 0); - title_label = new QLabel(tr("Charts")); + // toolbar + QToolBar *toolbar = new QToolBar(tr("Charts"), this); + int icon_size = style()->pixelMetric(QStyle::PM_SmallIconSize); + toolbar->setIconSize({icon_size, icon_size}); - title_layout->addWidget(title_label); - title_layout->addStretch(); + QAction *new_plot_btn = toolbar->addAction(utils::icon("file-plus"), tr("New Plot")); + toolbar->addWidget(title_label = new QLabel()); + title_label->setContentsMargins(0, 0, style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing), 0); - range_label = new QLabel(); - title_layout->addWidget(range_label); + QMenu *menu = new QMenu(this); + for (int i = 0; i < MAX_COLUMN_COUNT; ++i) { + menu->addAction(tr("%1").arg(i + 1), [=]() { setColumnCount(i + 1); }); + } + columns_action = toolbar->addAction(""); + columns_action->setMenu(menu); + qobject_cast(toolbar->widgetForAction(columns_action))->setPopupMode(QToolButton::InstantPopup); - reset_zoom_btn = new QPushButton("⟲", this); - reset_zoom_btn->setFixedSize(30, 30); - reset_zoom_btn->setToolTip(tr("Reset zoom (drag on chart to zoom X-Axis)")); - title_layout->addWidget(reset_zoom_btn); + QLabel *stretch_label = new QLabel(this); + stretch_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); + toolbar->addWidget(stretch_label); - remove_all_btn = new QPushButton("✖", this); - remove_all_btn->setToolTip(tr("Remove all charts")); - remove_all_btn->setFixedSize(30, 30); - title_layout->addWidget(remove_all_btn); + range_lb_action = toolbar->addWidget(range_lb = new QLabel(this)); + range_slider = new LogSlider(1000, Qt::Horizontal, this); + range_slider->setMaximumWidth(200); + range_slider->setToolTip(tr("Set the chart range")); + range_slider->setRange(1, settings.max_cached_minutes * 60); + range_slider->setSingleStep(1); + range_slider->setPageStep(60); // 1 min + range_slider_action = toolbar->addWidget(range_slider); - dock_btn = new QPushButton(); - dock_btn->setFixedSize(30, 30); - title_layout->addWidget(dock_btn); + reset_zoom_action = toolbar->addAction(utils::icon("zoom-out"), tr("Reset Zoom")); + qobject_cast(toolbar->widgetForAction(reset_zoom_action))->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); - main_layout->addWidget(title_bar, 0, Qt::AlignTop); + remove_all_btn = toolbar->addAction(utils::icon("x"), tr("Remove all charts")); + dock_btn = toolbar->addAction(""); + main_layout->addWidget(toolbar); // charts + charts_layout = new QGridLayout(); + charts_layout->setSpacing(10); + QWidget *charts_container = new QWidget(this); - QVBoxLayout *charts_main = new QVBoxLayout(charts_container); - charts_layout = new QVBoxLayout(); - charts_main->addLayout(charts_layout); - charts_main->addStretch(); + QVBoxLayout *charts_main_layout = new QVBoxLayout(charts_container); + charts_main_layout->setContentsMargins(0, 0, 0, 0); + charts_main_layout->addLayout(charts_layout); + charts_main_layout->addStretch(0); QScrollArea *charts_scroll = new QScrollArea(this); + charts_scroll->setFrameStyle(QFrame::NoFrame); charts_scroll->setWidgetResizable(true); charts_scroll->setWidget(charts_container); charts_scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(charts_scroll); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); - QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartsWidget::removeChart); - QObject::connect(dbc(), &DBCManager::signalUpdated, [this](const Signal *sig) { - if (auto it = charts.find(sig); it != charts.end()) { - it.value()->chart_view->updateSeries(); - } - }); - QObject::connect(dbc(), &DBCManager::msgUpdated, [this](const QString &id) { - for (auto chart : charts) { - if (chart->id == id) - chart->updateTitle(); - } - }); + // init settings + use_dark_theme = QApplication::palette().color(QPalette::WindowText).value() > + QApplication::palette().color(QPalette::Background).value(); + column_count = std::clamp(settings.chart_column_count, 1, MAX_COLUMN_COUNT); + max_chart_range = std::clamp(settings.chart_range, 1, settings.max_cached_minutes * 60); + display_range = {0, max_chart_range}; + range_slider->setValue(max_chart_range); + updateToolBar(); - QObject::connect(can, &CANMessages::rangeChanged, [this]() { updateTitleBar(); }); - QObject::connect(reset_zoom_btn, &QPushButton::clicked, can, &CANMessages::resetRange); - QObject::connect(remove_all_btn, &QPushButton::clicked, this, &ChartsWidget::removeAll); - QObject::connect(dock_btn, &QPushButton::clicked, [this]() { + align_timer.setSingleShot(true); + QObject::connect(&align_timer, &QTimer::timeout, this, &ChartsWidget::alignCharts); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &ChartsWidget::removeAll); + QObject::connect(can, &AbstractStream::eventsMerged, this, &ChartsWidget::eventsMerged); + QObject::connect(can, &AbstractStream::updated, this, &ChartsWidget::updateState); + QObject::connect(range_slider, &QSlider::valueChanged, this, &ChartsWidget::setMaxChartRange); + QObject::connect(new_plot_btn, &QAction::triggered, this, &ChartsWidget::newChart); + QObject::connect(remove_all_btn, &QAction::triggered, this, &ChartsWidget::removeAll); + QObject::connect(reset_zoom_action, &QAction::triggered, this, &ChartsWidget::zoomReset); + QObject::connect(&settings, &Settings::changed, this, &ChartsWidget::settingChanged); + QObject::connect(dock_btn, &QAction::triggered, [this]() { emit dock(!docking); docking = !docking; - updateTitleBar(); - }); - QObject::connect(&settings, &Settings::changed, [this]() { - for (auto chart : charts) { - chart->setHeight(settings.chart_height); - } + updateToolBar(); }); + + setWhatsThis(tr(R"( + Chart view
+ + )")); } -void ChartsWidget::updateTitleBar() { - title_bar->setVisible(!charts.isEmpty()); +void ChartsWidget::eventsMerged() { + { + QFutureSynchronizer future_synchronizer; + for (auto c : charts) { + future_synchronizer.addFuture(QtConcurrent::run(c, &ChartView::updateSeries, nullptr)); + } + } + if (can->isPaused()) { + updateState(); + } +} + +void ChartsWidget::zoomIn(double min, double max) { + zoomed_range = {min, max}; + is_zoomed = zoomed_range != display_range; + updateToolBar(); + updateState(); + emit rangeChanged(min, max, is_zoomed); +} + +void ChartsWidget::zoomReset() { + zoomIn(display_range.first, display_range.second); +} + +void ChartsWidget::updateState() { if (charts.isEmpty()) return; - // show select range - range_label->setVisible(can->isZoomed()); - reset_zoom_btn->setEnabled(can->isZoomed()); - if (can->isZoomed()) { - auto [min, max] = can->range(); - range_label->setText(tr("%1 - %2").arg(min, 0, 'f', 2).arg(max, 0, 'f', 2)); + const double cur_sec = can->currentSec(); + if (!is_zoomed) { + double pos = (cur_sec - display_range.first) / std::max(1.0, max_chart_range); + if (pos < 0 || pos > 0.8) { + display_range.first = std::max(0.0, cur_sec - max_chart_range * 0.1); + } + double max_sec = std::min(std::floor(display_range.first + max_chart_range), can->lastEventSecond()); + display_range.first = std::max(0.0, max_sec - max_chart_range); + display_range.second = display_range.first + max_chart_range; + } else if (cur_sec < zoomed_range.first || cur_sec >= zoomed_range.second) { + // loop in zoommed range + can->seekTo(zoomed_range.first); } - title_label->setText(tr("Charts (%1)").arg(charts.size())); - dock_btn->setText(docking ? "⬈" : "⬋"); + const auto &range = is_zoomed ? zoomed_range : display_range; + for (auto c : charts) { + c->updatePlot(cur_sec, range.first, range.second); + } +} + +void ChartsWidget::setMaxChartRange(int value) { + max_chart_range = settings.chart_range = range_slider->value(); + updateToolBar(); + updateState(); +} + +void ChartsWidget::updateToolBar() { + title_label->setText(tr("Charts: %1").arg(charts.size())); + columns_action->setText(tr("Column: %1").arg(column_count)); + range_lb->setText(QString("Range: %1:%2 ").arg(max_chart_range / 60, 2, 10, QLatin1Char('0')).arg(max_chart_range % 60, 2, 10, QLatin1Char('0'))); + range_lb_action->setVisible(!is_zoomed); + range_slider_action->setVisible(!is_zoomed); + reset_zoom_action->setVisible(is_zoomed); + reset_zoom_action->setText(is_zoomed ? tr("Zoomin: %1-%2").arg(zoomed_range.first, 0, 'f', 1).arg(zoomed_range.second, 0, 'f', 1) : ""); + remove_all_btn->setEnabled(!charts.isEmpty()); + dock_btn->setIcon(utils::icon(docking ? "arrow-up-right-square" : "arrow-down-left-square")); dock_btn->setToolTip(docking ? tr("Undock charts") : tr("Dock charts")); } -void ChartsWidget::addChart(const QString &id, const Signal *sig) { - if (!charts.contains(sig)) { - auto chart = new ChartWidget(id, sig, this); - QObject::connect(chart, &ChartWidget::remove, [=]() { removeChart(sig); }); - charts_layout->insertWidget(0, chart); - charts.insert(sig, chart); +void ChartsWidget::settingChanged() { + range_slider->setRange(1, settings.max_cached_minutes * 60); + for (auto c : charts) { + c->setFixedHeight(settings.chart_height); + c->setSeriesType((SeriesType)settings.chart_series_type); } - updateTitleBar(); } -void ChartsWidget::removeChart(const Signal *sig) { - auto it = charts.find(sig); - if (it != charts.end()) { - it.value()->deleteLater(); - charts.remove(sig); +ChartView *ChartsWidget::findChart(const MessageId &id, const cabana::Signal *sig) { + for (auto c : charts) + if (c->hasSeries(id, sig)) return c; + return nullptr; +} + +ChartView *ChartsWidget::createChart() { + auto chart = new ChartView(this); + chart->setFixedHeight(settings.chart_height); + chart->setMinimumWidth(CHART_MIN_WIDTH); + chart->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + chart->chart()->setTheme(use_dark_theme ? QChart::QChart::ChartThemeDark : QChart::ChartThemeLight); + QObject::connect(chart, &ChartView::remove, [=]() { removeChart(chart); }); + QObject::connect(chart, &ChartView::zoomIn, this, &ChartsWidget::zoomIn); + QObject::connect(chart, &ChartView::zoomReset, this, &ChartsWidget::zoomReset); + QObject::connect(chart, &ChartView::seriesRemoved, this, &ChartsWidget::seriesChanged); + QObject::connect(chart, &ChartView::seriesAdded, this, &ChartsWidget::seriesChanged); + QObject::connect(chart, &ChartView::axisYLabelWidthChanged, &align_timer, qOverload<>(&QTimer::start)); + charts.push_back(chart); + updateLayout(); + return chart; +} + +void ChartsWidget::showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge) { + ChartView *chart = findChart(id, sig); + if (show && !chart) { + chart = merge && charts.size() > 0 ? charts.back() : createChart(); + chart->addSeries(id, sig); + updateState(); + } else if (!show && chart) { + chart->removeIf([&](auto &s) { return s.msg_id == id && s.sig == sig; }); } - updateTitleBar(); + updateToolBar(); +} + +void ChartsWidget::setColumnCount(int n) { + n = std::clamp(n, 1, MAX_COLUMN_COUNT); + if (column_count != n) { + column_count = settings.chart_column_count = n; + updateToolBar(); + updateLayout(); + } +} + +void ChartsWidget::updateLayout() { + int n = MAX_COLUMN_COUNT; + for (; n > 1; --n) { + if ((n * CHART_MIN_WIDTH + (n - 1) * charts_layout->spacing()) < charts_layout->geometry().width()) break; + } + + bool show_column_cb = n > 1; + columns_action->setVisible(show_column_cb); + + n = std::min(column_count, n); + if (charts.size() != charts_layout->count() || n != current_column_count) { + current_column_count = n; + charts_layout->parentWidget()->setUpdatesEnabled(false); + for (int i = 0; i < charts.size(); ++i) { + charts_layout->addWidget(charts[charts.size() - i - 1], i / n, i % n); + } + QTimer::singleShot(0, [this]() { charts_layout->parentWidget()->setUpdatesEnabled(true); }); + } +} + +void ChartsWidget::resizeEvent(QResizeEvent *event) { + QWidget::resizeEvent(event); + updateLayout(); +} + +void ChartsWidget::newChart() { + SeriesSelector dlg(tr("New Chart"), this); + if (dlg.exec() == QDialog::Accepted) { + auto items = dlg.seletedItems(); + if (!items.isEmpty()) { + auto c = createChart(); + for (auto it : items) { + c->addSeries(it->msg_id, it->sig); + } + } + } +} + +void ChartsWidget::removeChart(ChartView *chart) { + charts.removeOne(chart); + chart->deleteLater(); + updateToolBar(); + alignCharts(); + updateLayout(); + emit seriesChanged(); } void ChartsWidget::removeAll() { - for (auto chart : charts) - chart->deleteLater(); + for (auto c : charts) { + c->deleteLater(); + } charts.clear(); - updateTitleBar(); + updateToolBar(); + emit seriesChanged(); +} + +void ChartsWidget::alignCharts() { + int plot_left = 0; + for (auto c : charts) { + plot_left = std::max(plot_left, c->y_label_width); + } + plot_left = std::max((plot_left / 10) * 10 + 10, 50); + for (auto c : charts) { + c->updatePlotArea(plot_left); + } } bool ChartsWidget::eventFilter(QObject *obj, QEvent *event) { if (obj != this && event->type() == QEvent::Close) { - emit dock_btn->clicked(); + emit dock_btn->triggered(); return true; } return false; } -// ChartWidget - -ChartWidget::ChartWidget(const QString &id, const Signal *sig, QWidget *parent) : id(id), signal(sig), QWidget(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - main_layout->setSpacing(0); - main_layout->setContentsMargins(0, 0, 0, 0); - - QWidget *header = new QWidget(this); - header->setStyleSheet("background-color:white"); - QHBoxLayout *header_layout = new QHBoxLayout(header); - header_layout->setContentsMargins(11, 11, 11, 0); - title = new QLabel(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); - header_layout->addWidget(title); - header_layout->addStretch(); - - QPushButton *remove_btn = new QPushButton("✖", this); - remove_btn->setFixedSize(30, 30); - remove_btn->setToolTip(tr("Remove chart")); - QObject::connect(remove_btn, &QPushButton::clicked, this, &ChartWidget::remove); - header_layout->addWidget(remove_btn); - main_layout->addWidget(header); - - chart_view = new ChartView(id, sig, this); - chart_view->setFixedHeight(settings.chart_height); - main_layout->addWidget(chart_view); - main_layout->addStretch(); - - setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); -} - -void ChartWidget::updateTitle() { - title->setText(tr("%1 %2").arg(dbc()->msg(id)->name.c_str()).arg(id)); -} - // ChartView -ChartView::ChartView(const QString &id, const Signal *sig, QWidget *parent) - : id(id), signal(sig), QChartView(nullptr, parent) { - QLineSeries *series = new QLineSeries(); - series->setUseOpenGL(true); +ChartView::ChartView(QWidget *parent) : QChartView(nullptr, parent) { + series_type = (SeriesType)settings.chart_series_type; QChart *chart = new QChart(); - chart->setTitle(sig->name.c_str()); - chart->addSeries(series); - chart->createDefaultAxes(); - chart->legend()->hide(); - QFont font; - font.setBold(true); - chart->setTitleFont(font); + chart->setBackgroundVisible(false); + axis_x = new QValueAxis(this); + axis_y = new QValueAxis(this); + chart->addAxis(axis_x, Qt::AlignBottom); + chart->addAxis(axis_y, Qt::AlignLeft); + chart->legend()->layout()->setContentsMargins(0, 0, 0, 0); + chart->legend()->setShowToolTips(true); chart->setMargins({0, 0, 0, 0}); - chart->layout()->setContentsMargins(0, 0, 0, 0); - track_line = new QGraphicsLineItem(chart); - track_line->setPen(QPen(Qt::gray, 1, Qt::DashLine)); - value_text = new QGraphicsSimpleTextItem(chart); - value_text->setBrush(Qt::gray); - line_marker = new QGraphicsLineItem(chart); - line_marker->setPen(QPen(Qt::black, 2)); + background = new QGraphicsRectItem(chart); + background->setBrush(QApplication::palette().color(QPalette::Base)); + background->setPen(Qt::NoPen); + background->setZValue(chart->zValue() - 1); setChart(chart); + createToolButtons(); setRenderHint(QPainter::Antialiasing); - setRubberBand(QChartView::HorizontalRubberBand); - if (auto rubber = findChild()) { - QPalette pal; - pal.setBrush(QPalette::Base, QColor(0, 0, 0, 80)); - rubber->setPalette(pal); + // TODO: enable zoomIn/seekTo in live streaming mode. + setRubberBand(can->liveStreaming() ? QChartView::NoRubberBand : QChartView::HorizontalRubberBand); + setMouseTracking(true); + + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &ChartView::signalRemoved); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &ChartView::signalUpdated); + QObject::connect(dbc(), &DBCManager::msgRemoved, this, &ChartView::msgRemoved); + QObject::connect(dbc(), &DBCManager::msgUpdated, this, &ChartView::msgUpdated); +} + +void ChartView::createToolButtons() { + move_icon = new QGraphicsPixmapItem(utils::icon("grip-horizontal"), chart()); + move_icon->setToolTip(tr("Drag and drop to combine charts")); + + QToolButton *remove_btn = toolButton("x", tr("Remove Chart")); + close_btn_proxy = new QGraphicsProxyWidget(chart()); + close_btn_proxy->setWidget(remove_btn); + close_btn_proxy->setZValue(chart()->zValue() + 11); + + // series types + QMenu *menu = new QMenu(this); + auto change_series_group = new QActionGroup(menu); + change_series_group->setExclusive(true); + QStringList types{tr("line"), tr("Step Line"), tr("Scatter")}; + for (int i = 0; i < types.size(); ++i) { + QAction *act = new QAction(types[i], change_series_group); + act->setData(i); + act->setCheckable(true); + act->setChecked(i == (int)series_type); + menu->addAction(act); } + menu->addSeparator(); + menu->addAction(tr("Manage series"), this, &ChartView::manageSeries); - QTimer *timer = new QTimer(this); - timer->setInterval(100); - timer->setSingleShot(true); - timer->callOnTimeout(this, &ChartView::adjustChartMargins); + QToolButton *manage_btn = toolButton("list", ""); + manage_btn->setMenu(menu); + manage_btn->setPopupMode(QToolButton::InstantPopup); + manage_btn_proxy = new QGraphicsProxyWidget(chart()); + manage_btn_proxy->setWidget(manage_btn); + manage_btn_proxy->setZValue(chart()->zValue() + 11); - QObject::connect(can, &CANMessages::updated, this, &ChartView::updateState); - QObject::connect(can, &CANMessages::rangeChanged, this, &ChartView::rangeChanged); - QObject::connect(can, &CANMessages::eventsMerged, this, &ChartView::updateSeries); - QObject::connect(dynamic_cast(chart->axisX()), &QValueAxis::rangeChanged, can, &CANMessages::setRange); - QObject::connect(chart, &QChart::plotAreaChanged, [=](const QRectF &plotArea) { - // use a singleshot timer to avoid recursion call. - timer->start(); + QObject::connect(remove_btn, &QToolButton::clicked, this, &ChartView::remove); + QObject::connect(change_series_group, &QActionGroup::triggered, [this](QAction *action) { + setSeriesType((SeriesType)action->data().toInt()); }); - - updateSeries(); } -void ChartView::adjustChartMargins() { - // TODO: Remove hardcoded aligned_pos - const int aligned_pos = 60; - if (chart()->plotArea().left() != aligned_pos) { - const float left_margin = chart()->margins().left() + aligned_pos - chart()->plotArea().left(); - chart()->setMargins(QMargins(left_margin, 0, 0, 0)); - } +void ChartView::addSeries(const MessageId &msg_id, const cabana::Signal *sig) { + if (hasSeries(msg_id, sig)) return; + + QXYSeries *series = createSeries(series_type, getColor(sig)); + sigs.push_back({.msg_id = msg_id, .sig = sig, .series = series}); + updateTitle(); + updateSeries(sig); + updateSeriesPoints(); + emit seriesAdded(msg_id, sig); } -void ChartWidget::setHeight(int height) { - chart_view->setFixedHeight(height); +bool ChartView::hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const { + return std::any_of(sigs.begin(), sigs.end(), [&](auto &s) { return s.msg_id == msg_id && s.sig == sig; }); } -void ChartView::updateState() { - auto axis_x = dynamic_cast(chart()->axisX()); - int x = chart()->plotArea().left() + chart()->plotArea().width() * (can->currentSec() - axis_x->min()) / (axis_x->max() - axis_x->min()); - line_marker->setLine(x, 0, x, height()); -} - -void ChartView::updateSeries() { - chart()->setTitle(signal->name.c_str()); - auto events = can->events(); - if (!events) return; - - auto l = id.split(':'); - int bus = l[0].toInt(); - uint32_t address = l[1].toUInt(nullptr, 16); - - vals.clear(); - vals.reserve(3 * 60 * 100); - uint64_t route_start_time = can->routeStartTime(); - for (auto &evt : *events) { - if (evt->which == cereal::Event::Which::CAN) { - for (auto c : evt->event.getCan()) { - if (bus == c.getSrc() && address == c.getAddress()) { - auto dat = c.getDat(); - double value = get_raw_value((uint8_t *)dat.begin(), dat.size(), *signal); - double ts = (evt->mono_time / (double)1e9) - route_start_time; // seconds - vals.push_back({ts, value}); - } - } +void ChartView::removeIf(std::function predicate) { + int prev_size = sigs.size(); + for (auto it = sigs.begin(); it != sigs.end(); /**/) { + if (predicate(*it)) { + chart()->removeSeries(it->series); + it->series->deleteLater(); + auto msg_id = it->msg_id; + auto sig = it->sig; + it = sigs.erase(it); + emit seriesRemoved(msg_id, sig); + } else { + ++it; } } - QLineSeries *series = (QLineSeries *)chart()->series()[0]; - series->replace(vals); - series->setPointLabelsColor(Qt::black); - auto [begin, end] = can->range(); - chart()->axisX()->setRange(begin, end); - updateAxisY(); + if (sigs.empty()) { + emit remove(); + } else if (sigs.size() != prev_size) { + updateAxisY(); + } } -void ChartView::rangeChanged(qreal min, qreal max) { - auto axis_x = dynamic_cast(chart()->axisX()); - if (axis_x->min() != min || axis_x->max() != max) { +void ChartView::signalUpdated(const cabana::Signal *sig) { + if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.sig == sig; })) { + updateTitle(); + // TODO: don't update series if only name changed. + updateSeries(sig); + } +} + +void ChartView::msgUpdated(MessageId id) { + if (std::any_of(sigs.begin(), sigs.end(), [=](auto &s) { return s.msg_id == id; })) + updateTitle(); +} + +void ChartView::manageSeries() { + SeriesSelector dlg(tr("Mange Chart"), this); + for (auto &s : sigs) { + dlg.addSelected(s.msg_id, s.sig); + } + if (dlg.exec() == QDialog::Accepted) { + auto items = dlg.seletedItems(); + for (auto s : items) { + addSeries(s->msg_id, s->sig); + } + removeIf([&](auto &s) { + return std::none_of(items.cbegin(), items.cend(), [&](auto &it) { return s.msg_id == it->msg_id && s.sig == it->sig; }); + }); + } +} + +void ChartView::resizeEvent(QResizeEvent *event) { + qreal left, top, right, bottom; + chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); + move_icon->setPos(left, top); + close_btn_proxy->setPos(rect().right() - right - close_btn_proxy->size().width(), top); + int x = close_btn_proxy->pos().x() - manage_btn_proxy->size().width() - style()->pixelMetric(QStyle::PM_LayoutHorizontalSpacing); + manage_btn_proxy->setPos(x, top); + chart()->legend()->setGeometry({move_icon->sceneBoundingRect().topRight(), manage_btn_proxy->sceneBoundingRect().bottomLeft()}); + if (align_to > 0) { + updatePlotArea(align_to); + } + QChartView::resizeEvent(event); +} + +void ChartView::updatePlotArea(int left_pos) { + if (align_to != left_pos || rect() != background->rect()) { + align_to = left_pos; + background->setRect(rect()); + + qreal left, top, right, bottom; + chart()->layout()->getContentsMargins(&left, &top, &right, &bottom); + QSizeF x_label_size = QFontMetrics(axis_x->labelsFont()).size(Qt::TextSingleLine, QString::number(axis_x->max(), 'f', 2)); + x_label_size += QSizeF{5 * devicePixelRatioF(), 5 * devicePixelRatioF()}; + int adjust_top = chart()->legend()->geometry().height() + style()->pixelMetric(QStyle::PM_LayoutTopMargin); + chart()->setPlotArea(rect().adjusted(align_to + left, adjust_top + top, -x_label_size.width() / 2 - right, -x_label_size.height() - bottom)); + chart()->layout()->invalidate(); + if (can->isPaused()) { + update(); + } + } +} + +void ChartView::updateTitle() { + for (QLegendMarker *marker : chart()->legend()->markers()) { + QObject::connect(marker, &QLegendMarker::clicked, this, &ChartView::handleMarkerClicked, Qt::UniqueConnection); + } + for (auto &s : sigs) { + auto decoration = s.series->isVisible() ? "none" : "line-through"; + s.series->setName(QString("%2 %3 %4").arg(decoration, s.sig->name, msgName(s.msg_id), s.msg_id.toString())); + } +} + +void ChartView::updatePlot(double cur, double min, double max) { + cur_sec = cur; + if (min != axis_x->min() || max != axis_x->max()) { axis_x->setRange(min, max); + updateAxisY(); + updateSeriesPoints(); + } + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); +} + +void ChartView::updateSeriesPoints() { + // Show points when zoomed in enough + for (auto &s : sigs) { + auto begin = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto end = std::lower_bound(begin, s.vals.end(), axis_x->max(), xLessThan); + + int num_points = std::max(end - begin, 1); + int pixels_per_point = width() / num_points; + + if (series_type == SeriesType::Scatter) { + ((QScatterSeries *)s.series)->setMarkerSize(std::clamp(pixels_per_point / 3, 2, 8) * devicePixelRatioF()); + } else { + s.series->setPointsVisible(pixels_per_point > 20); + } + } +} + +void ChartView::updateSeries(const cabana::Signal *sig) { + for (auto &s : sigs) { + if (!sig || s.sig == sig) { + if (!can->liveStreaming()) { + s.vals.clear(); + s.step_vals.clear(); + s.last_value_mono_time = 0; + } + s.series->setColor(getColor(s.sig)); + + auto msgs = can->events().at(s.msg_id); + auto first = std::upper_bound(msgs.cbegin(), msgs.cend(), CanEvent{.mono_time = s.last_value_mono_time}); + int new_size = std::max(s.vals.size() + std::distance(first, msgs.cend()), settings.max_cached_minutes * 60 * 100); + if (s.vals.capacity() <= new_size) { + s.vals.reserve(new_size * 2); + s.step_vals.reserve(new_size * 4); + } + + const double route_start_time = can->routeStartTime(); + for (auto end = msgs.cend(); first != end; ++first) { + double value = get_raw_value(first->dat, first->size, *s.sig); + double ts = first->mono_time / 1e9 - route_start_time; // seconds + s.vals.append({ts, value}); + if (!s.step_vals.empty()) { + s.step_vals.append({ts, s.step_vals.back().y()}); + } + s.step_vals.append({ts, value}); + s.last_value_mono_time = first->mono_time; + } + if (!can->liveStreaming()) { + s.segment_tree.build(s.vals); + } + s.series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); + } } updateAxisY(); } // auto zoom on yaxis void ChartView::updateAxisY() { - const auto axis_x = dynamic_cast(chart()->axisX()); - const auto axis_y = dynamic_cast(chart()->axisY()); - // vals is a sorted list - auto begin = std::lower_bound(vals.begin(), vals.end(), axis_x->min(), [](auto &p, double x) { return p.x() < x; }); - if (begin == vals.end()) - return; + if (sigs.isEmpty()) return; - auto end = std::upper_bound(vals.begin(), vals.end(), axis_x->max(), [](double x, auto &p) { return x < p.x(); }); - const auto [min, max] = std::minmax_element(begin, end, [](auto &p1, auto &p2) { return p1.y() < p2.y(); }); - if (min->y() == max->y()) { - if (max->y() < 0) { - axis_y->setRange(max->y(), 0); - } else { - axis_y->setRange(0, max->y() == 0 ? 1 : max->y()); + double min = std::numeric_limits::max(); + double max = std::numeric_limits::lowest(); + QString unit = sigs[0].sig->unit; + + for (auto &s : sigs) { + if (!s.series->isVisible()) continue; + + // Only show unit when all signals have the same unit + if (unit != s.sig->unit) { + unit.clear(); } - } else { - axis_y->setRange(min->y(), max->y()); + + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); + if (can->liveStreaming()) { + for (auto it = first; it != last; ++it) { + if (it->y() < min) min = it->y(); + if (it->y() > max) max = it->y(); + } + } else { + auto [min_y, max_y] = s.segment_tree.minmax(std::distance(s.vals.begin(), first), std::distance(s.vals.begin(), last)); + min = std::min(min, min_y); + max = std::max(max, max_y); + } + } + if (min == std::numeric_limits::max()) min = 0; + if (max == std::numeric_limits::lowest()) max = 0; + + if (axis_y->titleText() != unit) { + axis_y->setTitleText(unit); + y_label_width = 0; // recalc width + } + + double delta = std::abs(max - min) < 1e-3 ? 1 : (max - min) * 0.05; + auto [min_y, max_y, tick_count] = getNiceAxisNumbers(min - delta, max + delta, axis_y->tickCount()); + if (min_y != axis_y->min() || max_y != axis_y->max() || y_label_width == 0) { + axis_y->setRange(min_y, max_y); + axis_y->setTickCount(tick_count); + + int title_spacing = unit.isEmpty() ? 0 : QFontMetrics(axis_y->titleFont()).size(Qt::TextSingleLine, unit).height(); + QFontMetrics fm(axis_y->labelsFont()); + int n = qMax(int(-qFloor(std::log10((max_y - min_y) / (tick_count - 1)))), 0) + 1; + y_label_width = title_spacing + qMax(fm.width(QString::number(min_y, 'f', n)), fm.width(QString::number(max_y, 'f', n))) + 15; + axis_y->setLabelFormat(QString("%.%1f").arg(n)); + emit axisYLabelWidthChanged(y_label_width); } } -void ChartView::enterEvent(QEvent *event) { - track_line->setVisible(true); - value_text->setVisible(true); - QChartView::enterEvent(event); +std::tuple ChartView::getNiceAxisNumbers(qreal min, qreal max, int tick_count) { + qreal range = niceNumber((max - min), true); // range with ceiling + qreal step = niceNumber(range / (tick_count - 1), false); + min = qFloor(min / step); + max = qCeil(max / step); + tick_count = int(max - min) + 1; + return {min * step, max * step, tick_count}; +} + +// nice numbers can be expressed as form of 1*10^n, 2* 10^n or 5*10^n +qreal ChartView::niceNumber(qreal x, bool ceiling) { + qreal z = qPow(10, qFloor(std::log10(x))); //find corresponding number of the form of 10^n than is smaller than x + qreal q = x / z; //q<10 && q>=1; + if (ceiling) { + if (q <= 1.0) q = 1; + else if (q <= 2.0) q = 2; + else if (q <= 5.0) q = 5; + else q = 10; + } else { + if (q < 1.5) q = 1; + else if (q < 3.0) q = 2; + else if (q < 7.0) q = 5; + else q = 10; + } + return q * z; } void ChartView::leaveEvent(QEvent *event) { - track_line->setVisible(false); - value_text->setVisible(false); + clearTrackPoints(); + scene()->update(); QChartView::leaveEvent(event); } +void ChartView::mousePressEvent(QMouseEvent *event) { + if (event->button() == Qt::LeftButton && move_icon->sceneBoundingRect().contains(event->pos())) { + QMimeData *mimeData = new QMimeData; + mimeData->setData(mime_type, QByteArray::number((qulonglong)this)); + QDrag *drag = new QDrag(this); + drag->setMimeData(mimeData); + drag->setPixmap(grab()); + drag->setHotSpot(event->pos()); + drag->exec(Qt::CopyAction | Qt::MoveAction, Qt::MoveAction); + } else if (event->button() == Qt::LeftButton && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { + if (!can->liveStreaming()) { + // Save current playback state when scrubbing + resume_after_scrub = !can->isPaused(); + if (resume_after_scrub) { + can->pause(true); + } + is_scrubbing = true; + } + } else { + QChartView::mousePressEvent(event); + } +} + void ChartView::mouseReleaseEvent(QMouseEvent *event) { auto rubber = findChild(); if (event->button() == Qt::LeftButton && rubber && rubber->isVisible()) { - auto [begin, end] = can->range(); + rubber->hide(); + QRectF rect = rubber->geometry().normalized(); + double min = chart()->mapToValue(rect.topLeft()).x(); + double max = chart()->mapToValue(rect.bottomRight()).x(); + + // Prevent zooming/seeking past the end of the route + min = std::clamp(min, 0., can->totalSeconds()); + max = std::clamp(max, 0., can->totalSeconds()); + + double min_rounded = std::floor(min * 10.0) / 10.0; + double max_rounded = std::floor(max * 10.0) / 10.0; if (rubber->width() <= 0) { - double seek_to = begin + ((event->pos().x() - chart()->plotArea().x()) / chart()->plotArea().width()) * (end - begin); - can->seekTo(seek_to); - } else if (((double)rubber->width() / chart()->plotArea().width()) * (end - begin) < 0.5) { - // don't zoom if selected range is less than 0.5s - rubber->hide(); - event->accept(); - return; - } - } else if (event->button() == Qt::RightButton) { - // reset zoom - if (can->isZoomed()) { - can->resetRange(); - event->accept(); - return; + // no rubber dragged, seek to mouse position + can->seekTo(min); + } else if ((max_rounded - min_rounded) >= 0.5) { + // zoom in if selected range is greater than 0.5s + emit zoomIn(min_rounded, max_rounded); + } else { + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); } + event->accept(); + } else if (!can->liveStreaming() && event->button() == Qt::RightButton) { + emit zoomReset(); + event->accept(); + } else { + QGraphicsView::mouseReleaseEvent(event); + } + + // Resume playback if we were scrubbing + is_scrubbing = false; + if (resume_after_scrub) { + can->pause(false); + resume_after_scrub = false; } - QChartView::mouseReleaseEvent(event); - line_marker->setVisible(true); } void ChartView::mouseMoveEvent(QMouseEvent *ev) { + const auto plot_area = chart()->plotArea(); + // Scrubbing + if (is_scrubbing && QApplication::keyboardModifiers().testFlag(Qt::ShiftModifier)) { + if (plot_area.contains(ev->pos())) { + can->seekTo(std::clamp(chart()->mapToValue(ev->pos()).x(), 0., can->totalSeconds())); + } + return; + } + auto rubber = findChild(); - bool show = !(rubber && rubber->isVisible()); + bool is_zooming = rubber && rubber->isVisible(); + is_scrubbing = false; + clearTrackPoints(); - if (show) { - const auto plot_area = chart()->plotArea(); - float x = std::clamp((float)ev->pos().x(), (float)plot_area.left(), (float)plot_area.right()); - track_line->setLine(x, plot_area.top(), x, plot_area.bottom()); + if (!is_zooming && plot_area.contains(ev->pos())) { + QStringList text_list; + const double sec = chart()->mapToValue(ev->pos()).x(); + qreal x = -1; + for (auto &s : sigs) { + if (!s.series->isVisible()) continue; - auto [begin, end] = can->range(); - double sec = begin + ((x - plot_area.x()) / plot_area.width()) * (end - begin); - auto value = std::lower_bound(vals.begin(), vals.end(), sec, [](auto &p, double x) { return p.x() < x; }); - value_text->setPos(x + 6, plot_area.bottom() - 25); - if (value != vals.end()) { - value_text->setText(QString("(%1, %2)").arg(value->x(), 0, 'f', 3).arg(value->y())); + // use reverse iterator to find last item <= sec. + double value = 0; + auto it = std::lower_bound(s.vals.rbegin(), s.vals.rend(), sec, [](auto &p, double x) { return p.x() > x; }); + if (it != s.vals.rend() && it->x() >= axis_x->min()) { + value = it->y(); + s.track_pt = chart()->mapToPosition(*it); + x = std::max(x, s.track_pt.x()); + } + text_list.push_back(QString("%2: %3") + .arg(s.series->color().name(), s.sig->name, + s.track_pt.isNull() ? "--" : QString::number(value))); + } + if (x < 0) { + x = ev->pos().x(); + } + text_list.push_front(QString::number(chart()->mapToValue({x, 0}).x(), 'f', 3)); + QPointF tooltip_pt(x + 12, plot_area.top() - 20); + QToolTip::showText(mapToGlobal(tooltip_pt.toPoint()), text_list.join("
"), this, plot_area.toRect()); + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); + } else { + QToolTip::hideText(); + } + + QChartView::mouseMoveEvent(ev); + if (is_zooming) { + QRect rubber_rect = rubber->geometry(); + rubber_rect.setLeft(std::max(rubber_rect.left(), (int)plot_area.left())); + rubber_rect.setRight(std::min(rubber_rect.right(), (int)plot_area.right())); + if (rubber_rect != rubber->geometry()) { + rubber->setGeometry(rubber_rect); + } + scene()->invalidate({}, QGraphicsScene::ForegroundLayer); + } +} + +void ChartView::dragMoveEvent(QDragMoveEvent *event) { + if (event->mimeData()->hasFormat(mime_type)) { + event->setDropAction(event->source() == this ? Qt::MoveAction : Qt::CopyAction); + event->accept(); + } else { + event->ignore(); + } +} + +void ChartView::dropEvent(QDropEvent *event) { + if (event->mimeData()->hasFormat(mime_type)) { + if (event->source() == this) { + event->setDropAction(Qt::MoveAction); + event->accept(); } else { - value_text->setText("(--, --)"); + ChartView *source_chart = (ChartView *)event->source(); + for (auto &s : source_chart->sigs) { + addSeries(s.msg_id, s.sig); + } + emit source_chart->remove(); + event->acceptProposedAction(); + } + } else { + event->ignore(); + } +} + +void ChartView::drawForeground(QPainter *painter, const QRectF &rect) { + // draw time line + qreal x = chart()->mapToPosition(QPointF{cur_sec, 0}).x(); + x = std::clamp(x, chart()->plotArea().left(), chart()->plotArea().right()); + qreal y1 = chart()->plotArea().top() - 2; + qreal y2 = chart()->plotArea().bottom() + 2; + painter->setPen(QPen(chart()->titleBrush().color(), 2)); + painter->drawLine(QPointF{x, y1}, QPointF{x, y2}); + + // draw track points + painter->setPen(Qt::NoPen); + qreal track_line_x = -1; + for (auto &s : sigs) { + if (!s.track_pt.isNull() && s.series->isVisible()) { + painter->setBrush(s.series->color().darker(125)); + painter->drawEllipse(s.track_pt, 5.5, 5.5); + track_line_x = std::max(track_line_x, s.track_pt.x()); + } + } + if (track_line_x > 0) { + painter->setPen(QPen(Qt::darkGray, 1, Qt::DashLine)); + painter->drawLine(QPointF{track_line_x, y1}, QPointF{track_line_x, y2}); + } + + // paint points. OpenGL mode lacks certain features (such as showing points) + painter->setPen(Qt::NoPen); + for (auto &s : sigs) { + if (s.series->useOpenGL() && s.series->isVisible() && s.series->pointsVisible()) { + auto first = std::lower_bound(s.vals.begin(), s.vals.end(), axis_x->min(), xLessThan); + auto last = std::lower_bound(first, s.vals.end(), axis_x->max(), xLessThan); + for (auto it = first; it != last; ++it) { + painter->setBrush(s.series->color()); + painter->drawEllipse(chart()->mapToPosition(*it), 4, 4); + } } } - value_text->setVisible(show); - track_line->setVisible(show); - line_marker->setVisible(show); - QChartView::mouseMoveEvent(ev); + // paint zoom range + auto rubber = findChild(); + if (rubber && rubber->isVisible() && rubber->width() > 1) { + painter->setPen(Qt::white); + auto rubber_rect = rubber->geometry().normalized(); + for (const auto &pt : {rubber_rect.bottomLeft(), rubber_rect.bottomRight()}) { + QString sec = QString::number(chart()->mapToValue(pt).x(), 'f', 1); + // ChartAxisElement's padding is 4 (https://codebrowser.dev/qt5/qtcharts/src/charts/axis/chartaxiselement_p.h.html) + auto r = painter->fontMetrics().boundingRect(sec).adjusted(-6, -4, 6, 4); + pt == rubber_rect.bottomLeft() ? r.moveTopRight(pt + QPoint{0, 2}) : r.moveTopLeft(pt + QPoint{0, 2}); + painter->fillRect(r, Qt::gray); + painter->drawText(r, Qt::AlignCenter, sec); + } + } +} + +QXYSeries *ChartView::createSeries(SeriesType type, QColor color) { + QXYSeries *series = nullptr; + if (type == SeriesType::Line) { + series = new QLineSeries(this); + chart()->legend()->setMarkerShape(QLegend::MarkerShapeRectangle); + } else if (type == SeriesType::StepLine) { + series = new QLineSeries(this); + chart()->legend()->setMarkerShape(QLegend::MarkerShapeFromSeries); + } else { + series = new QScatterSeries(this); + chart()->legend()->setMarkerShape(QLegend::MarkerShapeCircle); + } + series->setColor(color); + // TODO: Due to a bug in CameraWidget the camera frames + // are drawn instead of the graphs on MacOS. Re-enable OpenGL when fixed +#ifndef __APPLE__ + series->setUseOpenGL(true); + // Qt doesn't properly apply device pixel ratio in OpenGL mode + QPen pen = series->pen(); + pen.setWidthF(2.0 * devicePixelRatioF()); + series->setPen(pen); +#endif + chart()->addSeries(series); + series->attachAxis(axis_x); + series->attachAxis(axis_y); + + // disables the delivery of mouse events to the opengl widget. + // this enables the user to select the zoom area when the mouse press on the data point. + auto glwidget = findChild(); + if (glwidget && !glwidget->testAttribute(Qt::WA_TransparentForMouseEvents)) { + glwidget->setAttribute(Qt::WA_TransparentForMouseEvents); + } + return series; +} + +void ChartView::setSeriesType(SeriesType type) { + if (type != series_type) { + series_type = type; + for (auto &s : sigs) { + chart()->removeSeries(s.series); + s.series->deleteLater(); + } + for (auto &s : sigs) { + auto series = createSeries(series_type, getColor(s.sig)); + series->replace(series_type == SeriesType::StepLine ? s.step_vals : s.vals); + s.series = series; + } + updateSeriesPoints(); + updateTitle(); + } +} + +void ChartView::handleMarkerClicked() { + auto marker = qobject_cast(sender()); + Q_ASSERT(marker); + if (sigs.size() > 1) { + auto series = marker->series(); + series->setVisible(!series->isVisible()); + marker->setVisible(true); + updateAxisY(); + updateTitle(); + } +} + +// SeriesSelector + +SeriesSelector::SeriesSelector(QString title, QWidget *parent) : QDialog(parent) { + setWindowTitle(title); + QGridLayout *main_layout = new QGridLayout(this); + + // left column + main_layout->addWidget(new QLabel(tr("Available Signals")), 0, 0); + main_layout->addWidget(msgs_combo = new QComboBox(this), 1, 0); + msgs_combo->setEditable(true); + msgs_combo->lineEdit()->setPlaceholderText(tr("Select a msg...")); + msgs_combo->setInsertPolicy(QComboBox::NoInsert); + msgs_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); + msgs_combo->completer()->setFilterMode(Qt::MatchContains); + + main_layout->addWidget(available_list = new QListWidget(this), 2, 0); + + // buttons + QVBoxLayout *btn_layout = new QVBoxLayout(); + QPushButton *add_btn = new QPushButton(utils::icon("chevron-right"), "", this); + add_btn->setEnabled(false); + QPushButton *remove_btn = new QPushButton(utils::icon("chevron-left"), "", this); + remove_btn->setEnabled(false); + btn_layout->addStretch(0); + btn_layout->addWidget(add_btn); + btn_layout->addWidget(remove_btn); + btn_layout->addStretch(0); + main_layout->addLayout(btn_layout, 0, 1, 3, 1); + + // right column + main_layout->addWidget(new QLabel(tr("Selected Signals")), 0, 2); + main_layout->addWidget(selected_list = new QListWidget(this), 1, 2, 2, 1); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + main_layout->addWidget(buttonBox, 3, 2); + + for (auto it = can->last_msgs.cbegin(); it != can->last_msgs.cend(); ++it) { + if (auto m = dbc()->msg(it.key())) { + msgs_combo->addItem(QString("%1 (%2)").arg(m->name).arg(it.key().toString()), QVariant::fromValue(it.key())); + } + } + msgs_combo->model()->sort(0); + msgs_combo->setCurrentIndex(-1); + + QObject::connect(msgs_combo, qOverload(&QComboBox::currentIndexChanged), this, &SeriesSelector::updateAvailableList); + QObject::connect(available_list, &QListWidget::currentRowChanged, [=](int row) { add_btn->setEnabled(row != -1); }); + QObject::connect(selected_list, &QListWidget::currentRowChanged, [=](int row) { remove_btn->setEnabled(row != -1); }); + QObject::connect(available_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::add); + QObject::connect(selected_list, &QListWidget::itemDoubleClicked, this, &SeriesSelector::remove); + QObject::connect(add_btn, &QPushButton::clicked, [this]() { if (auto item = available_list->currentItem()) add(item); }); + QObject::connect(remove_btn, &QPushButton::clicked, [this]() { if (auto item = selected_list->currentItem()) remove(item); }); + QObject::connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); + QObject::connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void SeriesSelector::add(QListWidgetItem *item) { + auto it = (ListItem *)item; + addItemToList(selected_list, it->msg_id, it->sig, true); + delete item; +} + +void SeriesSelector::remove(QListWidgetItem *item) { + auto it = (ListItem *)item; + if (it->msg_id == msgs_combo->currentData().value()) { + addItemToList(available_list, it->msg_id, it->sig); + } + delete item; +} + +void SeriesSelector::updateAvailableList(int index) { + if (index == -1) return; + available_list->clear(); + MessageId msg_id = msgs_combo->itemData(index).value(); + auto selected_items = seletedItems(); + for (auto s : dbc()->msg(msg_id)->getSignals()) { + bool is_selected = std::any_of(selected_items.begin(), selected_items.end(), [=, sig=s](auto it) { return it->msg_id == msg_id && it->sig == sig; }); + if (!is_selected) { + addItemToList(available_list, msg_id, s); + } + } +} + +void SeriesSelector::addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name) { + QString text = QString(" %1").arg(getColor(sig).name(), sig->name); + if (show_msg_name) text += QString(" %0 %1").arg(msgName(id), id.toString()); + + QLabel *label = new QLabel(text); + label->setContentsMargins(5, 0, 5, 0); + auto new_item = new ListItem(id, sig, parent); + new_item->setSizeHint(label->sizeHint()); + parent->setItemWidget(new_item, label); +} + +QList SeriesSelector::seletedItems() { + QList ret; + for (int i = 0; i < selected_list->count(); ++i) ret.push_back((ListItem *)selected_list->item(i)); + return ret; } diff --git a/tools/cabana/chartswidget.h b/tools/cabana/chartswidget.h index fe2e14db9f..731be47d06 100644 --- a/tools/cabana/chartswidget.h +++ b/tools/cabana/chartswidget.h @@ -1,88 +1,180 @@ #pragma once -#include - +#include #include -#include -#include -#include -#include -#include +#include +#include +#include +#include #include +#include +#include +#include +#include -#include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" - +#include "tools/cabana/streams/abstractstream.h" using namespace QtCharts; +const int CHART_MIN_WIDTH = 300; + +enum class SeriesType { + Line = 0, + StepLine, + Scatter +}; + class ChartView : public QChartView { Q_OBJECT public: - ChartView(const QString &id, const Signal *sig, QWidget *parent = nullptr); - void updateSeries(); + ChartView(QWidget *parent = nullptr); + void addSeries(const MessageId &msg_id, const cabana::Signal *sig); + bool hasSeries(const MessageId &msg_id, const cabana::Signal *sig) const; + void updateSeries(const cabana::Signal *sig = nullptr); + void updatePlot(double cur, double min, double max); + void setSeriesType(SeriesType type); + void updatePlotArea(int left); -private: - void mouseReleaseEvent(QMouseEvent *event) override; - void mouseMoveEvent(QMouseEvent *ev) override; - void enterEvent(QEvent *event) override; - void leaveEvent(QEvent *event) override; - void adjustChartMargins(); - - void rangeChanged(qreal min, qreal max); - void updateAxisY(); - void updateState(); - - QGraphicsLineItem *track_line; - QGraphicsSimpleTextItem *value_text; - QGraphicsLineItem *line_marker; - QList vals; - QString id; - const Signal *signal; -}; - -class ChartWidget : public QWidget { -Q_OBJECT - -public: - ChartWidget(const QString &id, const Signal *sig, QWidget *parent); - void updateTitle(); - void setHeight(int height); + struct SigItem { + MessageId msg_id; + const cabana::Signal *sig = nullptr; + QXYSeries *series = nullptr; + QVector vals; + QVector step_vals; + uint64_t last_value_mono_time = 0; + QPointF track_pt{}; + SegmentTree segment_tree; + }; signals: + void seriesRemoved(const MessageId &id, const cabana::Signal *sig); + void seriesAdded(const MessageId &id, const cabana::Signal *sig); + void zoomIn(double min, double max); + void zoomReset(); void remove(); + void axisYLabelWidthChanged(int w); -public: - QString id; - const Signal *signal; - QLabel *title; - ChartView *chart_view = nullptr; -}; +private slots: + void signalUpdated(const cabana::Signal *sig); + void manageSeries(); + void handleMarkerClicked(); + void msgUpdated(MessageId id); + void msgRemoved(MessageId id) { removeIf([=](auto &s) { return s.msg_id == id; }); } + void signalRemoved(const cabana::Signal *sig) { removeIf([=](auto &s) { return s.sig == sig; }); } -class ChartsWidget : public QWidget { +private: + void createToolButtons(); + void mousePressEvent(QMouseEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *ev) override; + void dragMoveEvent(QDragMoveEvent *event) override; + void dropEvent(QDropEvent *event) override; + void leaveEvent(QEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + QSize sizeHint() const override { return {CHART_MIN_WIDTH, settings.chart_height}; } + void updateAxisY(); + void updateTitle(); + void drawForeground(QPainter *painter, const QRectF &rect) override; + std::tuple getNiceAxisNumbers(qreal min, qreal max, int tick_count); + qreal niceNumber(qreal x, bool ceiling); + QXYSeries *createSeries(SeriesType type, QColor color); + void updateSeriesPoints(); + void removeIf(std::function predicate); + inline void clearTrackPoints() { for (auto &s : sigs) s.track_pt = {}; } + + int y_label_width = 0; + int align_to = 0; + QValueAxis *axis_x; + QValueAxis *axis_y; + QGraphicsPixmapItem *move_icon; + QGraphicsProxyWidget *close_btn_proxy; + QGraphicsProxyWidget *manage_btn_proxy; + QGraphicsRectItem *background; + QList sigs; + double cur_sec = 0; + const QString mime_type = "application/x-cabanachartview"; + SeriesType series_type = SeriesType::Line; + bool is_scrubbing = false; + bool resume_after_scrub = false; + friend class ChartsWidget; + }; + +class ChartsWidget : public QFrame { Q_OBJECT public: ChartsWidget(QWidget *parent = nullptr); - void addChart(const QString &id, const Signal *sig); - void removeChart(const Signal *sig); + void showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge); + inline bool hasSignal(const MessageId &id, const cabana::Signal *sig) { return findChart(id, sig) != nullptr; } + +public slots: + void setColumnCount(int n); + void removeAll(); signals: void dock(bool floating); + void rangeChanged(double min, double max, bool is_zommed); + void seriesChanged(); private: + void resizeEvent(QResizeEvent *event) override; + void alignCharts(); + void newChart(); + ChartView *createChart(); + void removeChart(ChartView *chart); + void eventsMerged(); void updateState(); - void updateTitleBar(); - void removeAll(); - bool eventFilter(QObject *obj, QEvent *event); + void zoomIn(double min, double max); + void zoomReset(); + void updateToolBar(); + void setMaxChartRange(int value); + void updateLayout(); + void settingChanged(); + bool eventFilter(QObject *obj, QEvent *event) override; + ChartView *findChart(const MessageId &id, const cabana::Signal *sig); - QWidget *title_bar; QLabel *title_label; - QLabel *range_label; + QLabel *range_lb; + LogSlider *range_slider; + QAction *range_lb_action; + QAction *range_slider_action; bool docking = true; - QPushButton *dock_btn; - QPushButton *reset_zoom_btn; - QPushButton *remove_all_btn; - QVBoxLayout *charts_layout; - QHash charts; + QAction *dock_btn; + QAction *reset_zoom_action; + QAction *remove_all_btn; + QGridLayout *charts_layout; + QList charts; + uint32_t max_chart_range = 0; + bool is_zoomed = false; + std::pair display_range; + std::pair zoomed_range; + bool use_dark_theme = false; + QAction *columns_action; + int column_count = 1; + int current_column_count = 0; + QTimer align_timer; +}; + +class SeriesSelector : public QDialog { +public: + struct ListItem : public QListWidgetItem { + ListItem(const MessageId &msg_id, const cabana::Signal *sig, QListWidget *parent) : msg_id(msg_id), sig(sig), QListWidgetItem(parent) {} + MessageId msg_id; + const cabana::Signal *sig; + }; + + SeriesSelector(QString title, QWidget *parent); + QList seletedItems(); + inline void addSelected(const MessageId &id, const cabana::Signal *sig) { addItemToList(selected_list, id, sig, true); } + +private: + void updateAvailableList(int index); + void addItemToList(QListWidget *parent, const MessageId id, const cabana::Signal *sig, bool show_msg_name = false); + void add(QListWidgetItem *item); + void remove(QListWidgetItem *item); + + QComboBox *msgs_combo; + QListWidget *available_list; + QListWidget *selected_list; }; diff --git a/tools/cabana/commands.cc b/tools/cabana/commands.cc new file mode 100644 index 0000000000..337de1c702 --- /dev/null +++ b/tools/cabana/commands.cc @@ -0,0 +1,88 @@ +#include + +#include "tools/cabana/commands.h" + +// EditMsgCommand + +EditMsgCommand::EditMsgCommand(const MessageId &id, const QString &name, int size, QUndoCommand *parent) + : id(id), new_name(name), new_size(size), QUndoCommand(parent) { + if (auto msg = dbc()->msg(id)) { + old_name = msg->name; + old_size = msg->size; + setText(QObject::tr("edit message %1:%2").arg(name).arg(id.address)); + } else { + setText(QObject::tr("new message %1:%2").arg(name).arg(id.address)); + } +} + +void EditMsgCommand::undo() { + if (old_name.isEmpty()) + dbc()->removeMsg(id); + else + dbc()->updateMsg(id, old_name, old_size); +} + +void EditMsgCommand::redo() { + dbc()->updateMsg(id, new_name, new_size); +} + +// RemoveMsgCommand + +RemoveMsgCommand::RemoveMsgCommand(const MessageId &id, QUndoCommand *parent) : id(id), QUndoCommand(parent) { + if (auto msg = dbc()->msg(id)) { + message = *msg; + setText(QObject::tr("remove message %1:%2").arg(message.name).arg(id.address)); + } +} + +void RemoveMsgCommand::undo() { + if (!message.name.isEmpty()) { + dbc()->updateMsg(id, message.name, message.size); + for (auto s : message.getSignals()) + dbc()->addSignal(id, *s); + } +} + +void RemoveMsgCommand::redo() { + if (!message.name.isEmpty()) + dbc()->removeMsg(id); +} + +// AddSigCommand + +AddSigCommand::AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUndoCommand *parent) + : id(id), signal(sig), QUndoCommand(parent) { + setText(QObject::tr("add signal %1 to %2:%3").arg(sig.name).arg(msgName(id)).arg(id.address)); +} + +void AddSigCommand::undo() { dbc()->removeSignal(id, signal.name); } +void AddSigCommand::redo() { dbc()->addSignal(id, signal); } + +// RemoveSigCommand + +RemoveSigCommand::RemoveSigCommand(const MessageId &id, const cabana::Signal *sig, QUndoCommand *parent) + : id(id), signal(*sig), QUndoCommand(parent) { + setText(QObject::tr("remove signal %1 from %2:%3").arg(signal.name).arg(msgName(id)).arg(id.address)); +} + +void RemoveSigCommand::undo() { dbc()->addSignal(id, signal); } +void RemoveSigCommand::redo() { dbc()->removeSignal(id, signal.name); } + +// EditSignalCommand + +EditSignalCommand::EditSignalCommand(const MessageId &id, const cabana::Signal *sig, const cabana::Signal &new_sig, QUndoCommand *parent) + : id(id), old_signal(*sig), new_signal(new_sig), QUndoCommand(parent) { + setText(QObject::tr("edit signal %1 in %2:%3").arg(old_signal.name).arg(msgName(id)).arg(id.address)); +} + +void EditSignalCommand::undo() { dbc()->updateSignal(id, new_signal.name, old_signal); } +void EditSignalCommand::redo() { dbc()->updateSignal(id, old_signal.name, new_signal); } + +namespace UndoStack { + +QUndoStack *instance() { + static QUndoStack *undo_stack = new QUndoStack(qApp); + return undo_stack; +} + +} // namespace UndoStack diff --git a/tools/cabana/commands.h b/tools/cabana/commands.h new file mode 100644 index 0000000000..2a5f40d584 --- /dev/null +++ b/tools/cabana/commands.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" + +class EditMsgCommand : public QUndoCommand { +public: + EditMsgCommand(const MessageId &id, const QString &name, int size, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const MessageId id; + QString old_name, new_name; + int old_size = 0, new_size = 0; +}; + +class RemoveMsgCommand : public QUndoCommand { +public: + RemoveMsgCommand(const MessageId &id, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const MessageId id; + cabana::Msg message; +}; + +class AddSigCommand : public QUndoCommand { +public: + AddSigCommand(const MessageId &id, const cabana::Signal &sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const MessageId id; + cabana::Signal signal = {}; +}; + +class RemoveSigCommand : public QUndoCommand { +public: + RemoveSigCommand(const MessageId &id, const cabana::Signal *sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const MessageId id; + cabana::Signal signal = {}; +}; + +class EditSignalCommand : public QUndoCommand { +public: + EditSignalCommand(const MessageId &id, const cabana::Signal *sig, const cabana::Signal &new_sig, QUndoCommand *parent = nullptr); + void undo() override; + void redo() override; + +private: + const MessageId id; + cabana::Signal old_signal = {}; + cabana::Signal new_signal = {}; +}; + +namespace UndoStack { + QUndoStack *instance(); + inline void push(QUndoCommand *cmd) { instance()->push(cmd); } +}; diff --git a/tools/cabana/dbc.cc b/tools/cabana/dbc.cc new file mode 100644 index 0000000000..38002af06c --- /dev/null +++ b/tools/cabana/dbc.cc @@ -0,0 +1,82 @@ +#include "tools/cabana/dbc.h" +#include "tools/cabana/util.h" + +uint qHash(const MessageId &item) { + return qHash(item.source) ^ qHash(item.address); +} + +std::vector cabana::Msg::getSignals() const { + std::vector ret; + ret.reserve(sigs.size()); + for (auto &sig : sigs) ret.push_back(&sig); + std::sort(ret.begin(), ret.end(), [](auto l, auto r) { return l->start_bit < r->start_bit; }); + return ret; +} + +void cabana::Signal::updatePrecision() { + precision = std::max(num_decimals(factor), num_decimals(offset)); +} + +// helper functions + +static QVector BIG_ENDIAN_START_BITS = []() { + QVector ret; + for (int i = 0; i < 64; i++) + for (int j = 7; j >= 0; j--) + ret.push_back(j + i * 8); + return ret; +}(); + +double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig) { + int64_t val = 0; + + int i = sig.msb / 8; + int bits = sig.size; + while (i >= 0 && i < data_size && bits > 0) { + int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; + int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; + int size = msb - lsb + 1; + + uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); + val |= d << (bits - size); + + bits -= size; + i = sig.is_little_endian ? i - 1 : i + 1; + } + if (sig.is_signed) { + val -= ((val >> (sig.size - 1)) & 0x1) ? (1ULL << sig.size) : 0; + } + return val * sig.factor + sig.offset; +} + +bool cabana::operator==(const cabana::Signal &l, const cabana::Signal &r) { + return l.name == r.name && l.size == r.size && + l.start_bit == r.start_bit && + l.msb == r.msb && l.lsb == r.lsb && + l.is_signed == r.is_signed && l.is_little_endian == r.is_little_endian && + l.factor == r.factor && l.offset == r.offset && + l.min == r.min && l.max == r.max && l.comment == r.comment && l.unit == r.unit && l.val_desc == r.val_desc; +} + +int bigEndianStartBitsIndex(int start_bit) { return BIG_ENDIAN_START_BITS[start_bit]; } +int bigEndianBitIndex(int index) { return BIG_ENDIAN_START_BITS.indexOf(index); } + +void updateSigSizeParamsFromRange(cabana::Signal &s, int start_bit, int size) { + s.start_bit = s.is_little_endian ? start_bit : bigEndianBitIndex(start_bit); + s.size = size; + if (s.is_little_endian) { + s.lsb = s.start_bit; + s.msb = s.start_bit + s.size - 1; + } else { + s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1); + s.msb = s.start_bit; + } +} + +std::pair getSignalRange(const cabana::Signal *s) { + int from = s->is_little_endian ? s->start_bit : bigEndianBitIndex(s->start_bit); + int to = from + s->size - 1; + return {from, to}; +} + +std::vector allDBCNames() { return get_dbc_names(); } diff --git a/tools/cabana/dbc.h b/tools/cabana/dbc.h new file mode 100644 index 0000000000..701908112f --- /dev/null +++ b/tools/cabana/dbc.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "opendbc/can/common_dbc.h" + +const QString UNTITLED = "untitled"; + +struct MessageId { + uint8_t source; + uint32_t address; + + QString toString() const { + return QString("%1:%2").arg(source).arg(address, 1, 16); + } + + bool operator==(const MessageId &other) const { + return source == other.source && address == other.address; + } + + bool operator!=(const MessageId &other) const { + return !(*this == other); + } + + bool operator<(const MessageId &other) const { + return std::pair{source, address} < std::pair{other.source, other.address}; + } + + bool operator>(const MessageId &other) const { + return std::pair{source, address} > std::pair{other.source, other.address}; + } +}; + +uint qHash(const MessageId &item); +Q_DECLARE_METATYPE(MessageId); + +template <> +struct std::hash { + std::size_t operator()(const MessageId &k) const noexcept { return qHash(k); } +}; + +typedef QList> ValueDescription; + +namespace cabana { + struct Signal { + QString name; + int start_bit, msb, lsb, size; + bool is_signed; + double factor, offset; + bool is_little_endian; + QString min, max, unit; + QString comment; + ValueDescription val_desc; + int precision = 0; + void updatePrecision(); + }; + + struct Msg { + QString name; + uint32_t size; + QList sigs; + + std::vector getSignals() const; + const cabana::Signal *sig(const QString &sig_name) const { + auto it = std::find_if(sigs.begin(), sigs.end(), [&](auto &s) { return s.name == sig_name; }); + return it != sigs.end() ? &(*it) : nullptr; + } + }; + + bool operator==(const cabana::Signal &l, const cabana::Signal &r); + inline bool operator!=(const cabana::Signal &l, const cabana::Signal &r) { return !(l == r); } +} + +// Helper functions +double get_raw_value(const uint8_t *data, size_t data_size, const cabana::Signal &sig); +int bigEndianStartBitsIndex(int start_bit); +int bigEndianBitIndex(int index); +void updateSigSizeParamsFromRange(cabana::Signal &s, int start_bit, int size); +std::pair getSignalRange(const cabana::Signal *s); +std::vector allDBCNames(); diff --git a/tools/cabana/dbcmanager.cc b/tools/cabana/dbcmanager.cc index fc40fc58e4..49aac5c18b 100644 --- a/tools/cabana/dbcmanager.cc +++ b/tools/cabana/dbcmanager.cc @@ -1,70 +1,189 @@ #include "tools/cabana/dbcmanager.h" +#include -#include +#include +#include +#include #include +#include +#include -DBCManager::DBCManager(QObject *parent) : QObject(parent) {} - -DBCManager::~DBCManager() {} - -void DBCManager::open(const QString &dbc_file_name) { - dbc_name = dbc_file_name; - dbc = const_cast(dbc_lookup(dbc_name.toStdString())); - msg_map.clear(); - for (auto &msg : dbc->msgs) { - msg_map[msg.address] = &msg; +bool DBCManager::open(const QString &dbc_file_name, QString *error) { + QString opendbc_file_path = QString("%1/%2.dbc").arg(OPENDBC_FILE_PATH, dbc_file_name); + QFile file(opendbc_file_path); + if (file.open(QIODevice::ReadOnly)) { + return open(dbc_file_name, file.readAll(), error); } - emit DBCFileChanged(); + return false; } -void DBCManager::open(const QString &name, const QString &content) { - this->dbc_name = name; - std::istringstream stream(content.toStdString()); - dbc = const_cast(dbc_parse_from_stream(name.toStdString(), stream)); - msg_map.clear(); - for (auto &msg : dbc->msgs) { - msg_map[msg.address] = &msg; +bool DBCManager::open(const QString &name, const QString &content, QString *error) { + try { + std::istringstream stream(content.toStdString()); + auto dbc = const_cast(dbc_parse_from_stream(name.toStdString(), stream)); + msgs.clear(); + for (auto &msg : dbc->msgs) { + auto &m = msgs[msg.address]; + m.name = msg.name.c_str(); + m.size = msg.size; + for (auto &s : msg.sigs) { + m.sigs.push_back({}); + auto &sig = m.sigs.last(); + sig.name = s.name.c_str(); + sig.start_bit = s.start_bit; + sig.msb = s.msb; + sig.lsb = s.lsb; + sig.size = s.size; + sig.is_signed = s.is_signed; + sig.factor = s.factor; + sig.offset = s.offset; + sig.is_little_endian = s.is_little_endian; + sig.updatePrecision(); + } + } + parseExtraInfo(content); + name_ = name; + emit DBCFileChanged(); + delete dbc; + } catch (std::exception &e) { + if (error) *error = e.what(); + return false; } - emit DBCFileChanged(); + return true; } -void save(const QString &dbc_file_name) { - // TODO: save DBC to file -} +void DBCManager::parseExtraInfo(const QString &content) { + static QRegularExpression bo_regexp(R"(^BO_ (\w+) (\w+) *: (\w+) (\w+))"); + static QRegularExpression sg_regexp(R"(^SG_ (\w+) : (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); + static QRegularExpression sgm_regexp(R"(^SG_ (\w+) (\w+) *: (\d+)\|(\d+)@(\d+)([\+|\-]) \(([0-9.+\-eE]+),([0-9.+\-eE]+)\) \[([0-9.+\-eE]+)\|([0-9.+\-eE]+)\] \"(.*)\" (.*))"); + static QRegularExpression sg_comment_regexp(R"(^CM_ SG_ *(\w+) *(\w+) *\"(.*)\";)"); + static QRegularExpression val_regexp(R"(VAL_ (\w+) (\w+) (.*);)"); + auto get_sig = [this](uint32_t address, const QString &name) -> cabana::Signal * { + auto m = (cabana::Msg *)msg(address); + return m ? (cabana::Signal *)m->sig(name) : nullptr; + }; -void DBCManager::updateMsg(const QString &id, const QString &name, uint32_t size) { - auto m = const_cast(msg(id)); - if (m) { - m->name = name.toStdString(); - m->size = size; - } else { - uint32_t address = addressFromId(id); - dbc->msgs.push_back({.address = address, .name = name.toStdString(), .size = size}); - msg_map[address] = &dbc->msgs.back(); - } - emit msgUpdated(id); -} - -void DBCManager::addSignal(const QString &id, const Signal &sig) { - if (Msg *m = const_cast(msg(id))) { - m->sigs.push_back(sig); - emit signalAdded(&m->sigs.back()); - } -} - -void DBCManager::updateSignal(const QString &id, const QString &sig_name, const Signal &sig) { - if (Msg *m = const_cast(msg(id))) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); - if (it != m->sigs.end()) { - *it = sig; - emit signalUpdated(&(*it)); + QTextStream stream((QString *)&content); + uint32_t address = 0; + while (!stream.atEnd()) { + QString line = stream.readLine().trimmed(); + if (line.startsWith("BO_ ")) { + if (auto match = bo_regexp.match(line); match.hasMatch()) { + address = match.captured(1).toUInt(); + } + } else if (line.startsWith("SG_ ")) { + int offset = 0; + auto match = sg_regexp.match(line); + if (!match.hasMatch()) { + match = sgm_regexp.match(line); + offset = 1; + } + if (match.hasMatch()) { + if (auto s = get_sig(address, match.captured(1))) { + s->min = match.captured(8 + offset); + s->max = match.captured(9 + offset); + s->unit = match.captured(10 + offset); + } + } + } else if (line.startsWith("VAL_ ")) { + if (auto match = val_regexp.match(line); match.hasMatch()) { + if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) { + QStringList desc_list = match.captured(3).trimmed().split('"'); + for (int i = 0; i < desc_list.size(); i += 2) { + auto val = desc_list[i].trimmed(); + if (!val.isEmpty() && (i + 1) < desc_list.size()) { + auto desc = desc_list[i+1].trimmed(); + s->val_desc.push_back({val, desc}); + } + } + } + } + } else if (line.startsWith("CM_ SG_ ")) { + if (auto match = sg_comment_regexp.match(line); match.hasMatch()) { + if (auto s = get_sig(match.captured(1).toUInt(), match.captured(2))) { + s->comment = match.captured(3).trimmed(); + } + } } } } -void DBCManager::removeSignal(const QString &id, const QString &sig_name) { - if (Msg *m = const_cast(msg(id))) { - auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [=](auto &sig) { return sig_name == sig.name.c_str(); }); +QString DBCManager::generateDBC() { + QString dbc_string, signal_comment, val_desc; + for (auto &[address, m] : msgs) { + dbc_string += QString("BO_ %1 %2: %3 XXX\n").arg(address).arg(m.name).arg(m.size); + for (auto sig : m.getSignals()) { + dbc_string += QString(" SG_ %1 : %2|%3@%4%5 (%6,%7) [%8|%9] \"%10\" XXX\n") + .arg(sig->name) + .arg(sig->start_bit) + .arg(sig->size) + .arg(sig->is_little_endian ? '1' : '0') + .arg(sig->is_signed ? '-' : '+') + .arg(sig->factor, 0, 'g', std::numeric_limits::digits10) + .arg(sig->offset, 0, 'g', std::numeric_limits::digits10) + .arg(sig->min) + .arg(sig->max) + .arg(sig->unit); + if (!sig->comment.isEmpty()) { + signal_comment += QString("CM_ SG_ %1 %2 \"%3\";\n").arg(address).arg(sig->name).arg(sig->comment); + } + if (!sig->val_desc.isEmpty()) { + QStringList text; + for (auto &[val, desc] : sig->val_desc) { + text << QString("%1 \"%2\"").arg(val, desc); + } + val_desc += QString("VAL_ %1 %2 %3;\n").arg(address).arg(sig->name).arg(text.join(" ")); + } + } + dbc_string += "\n"; + } + return dbc_string + signal_comment + val_desc; +} + +void DBCManager::updateMsg(const MessageId &id, const QString &name, uint32_t size) { + auto &m = msgs[id.address]; + m.name = name; + m.size = size; + + // This DBC applies to all active sources, emit for every source + for (uint8_t source : sources) { + emit msgUpdated({.source = source, .address = id.address}); + } +} + +void DBCManager::removeMsg(const MessageId &id) { + msgs.erase(id.address); + + // This DBC applies to all active sources, emit for every source + for (uint8_t source : sources) { + emit msgRemoved({.source = source, .address = id.address}); + } +} + +void DBCManager::addSignal(const MessageId &id, const cabana::Signal &sig) { + if (auto m = const_cast(msg(id.address))) { + m->sigs.push_back(sig); + auto s = &m->sigs.last(); + + // This DBC applies to all active sources, emit for every source + for (uint8_t source : sources) { + emit signalAdded({.source = source, .address = id.address}, s); + } + } +} + +void DBCManager::updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig) { + if (auto m = const_cast(msg(id))) { + if (auto s = (cabana::Signal *)m->sig(sig_name)) { + *s = sig; + emit signalUpdated(s); + } + } +} + +void DBCManager::removeSignal(const MessageId &id, const QString &sig_name) { + if (auto m = const_cast(msg(id))) { + auto it = std::find_if(m->sigs.begin(), m->sigs.end(), [&](auto &s) { return s.name == sig_name; }); if (it != m->sigs.end()) { emit signalRemoved(&(*it)); m->sigs.erase(it); @@ -72,52 +191,24 @@ void DBCManager::removeSignal(const QString &id, const QString &sig_name) { } } -uint32_t DBCManager::addressFromId(const QString &id) { - return id.mid(id.indexOf(':') + 1).toUInt(nullptr, 16); +QStringList DBCManager::signalNames() { + // Used for autocompletion + QStringList ret; + for (auto const& [_, msg] : msgs) { + for (auto sig: msg.getSignals()) { + ret << sig->name; + } + } + ret.sort(); + ret.removeDuplicates(); + return ret; +} + +void DBCManager::updateSources(const QSet &s) { + sources = s; } DBCManager *dbc() { static DBCManager dbc_manager(nullptr); return &dbc_manager; } - -// helper functions - -static QVector BIG_ENDIAN_START_BITS = []() { - QVector ret; - for (int i = 0; i < 64; i++) - for (int j = 7; j >= 0; j--) - ret.push_back(j + i * 8); - return ret; -}(); - -int bigEndianStartBitsIndex(int start_bit) { - return BIG_ENDIAN_START_BITS[start_bit]; -} - -int bigEndianBitIndex(int index) { - return BIG_ENDIAN_START_BITS.indexOf(index); -} - -double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig) { - int64_t val = 0; - - int i = sig.msb / 8; - int bits = sig.size; - while (i >= 0 && i < data_size && bits > 0) { - int lsb = (int)(sig.lsb / 8) == i ? sig.lsb : i * 8; - int msb = (int)(sig.msb / 8) == i ? sig.msb : (i + 1) * 8 - 1; - int size = msb - lsb + 1; - - uint64_t d = (data[i] >> (lsb - (i * 8))) & ((1ULL << size) - 1); - val |= d << (bits - size); - - bits -= size; - i = sig.is_little_endian ? i - 1 : i + 1; - } - if (sig.is_signed) { - val -= ((val >> (sig.size - 1)) & 0x1) ? (1ULL << sig.size) : 0; - } - double value = val * sig.factor + sig.offset; - return value; -} diff --git a/tools/cabana/dbcmanager.h b/tools/cabana/dbcmanager.h index 74d935119a..dd3d9811c7 100644 --- a/tools/cabana/dbcmanager.h +++ b/tools/cabana/dbcmanager.h @@ -1,51 +1,78 @@ #pragma once +#include +#include +#include #include +#include +#include +#include -#include "opendbc/can/common_dbc.h" +#include "tools/cabana/dbc.h" class DBCManager : public QObject { Q_OBJECT public: - DBCManager(QObject *parent); - ~DBCManager(); + DBCManager(QObject *parent) {} + ~DBCManager() {} + bool open(const QString &dbc_file_name, QString *error = nullptr); + bool open(const QString &name, const QString &content, QString *error = nullptr); + QString generateDBC(); + void addSignal(const MessageId &id, const cabana::Signal &sig); + void updateSignal(const MessageId &id, const QString &sig_name, const cabana::Signal &sig); + void removeSignal(const MessageId &id, const QString &sig_name); + inline int msgCount() const { return msgs.size(); } - void open(const QString &dbc_file_name); - void open(const QString &name, const QString &content); - void save(const QString &dbc_file_name); - - void addSignal(const QString &id, const Signal &sig); - void updateSignal(const QString &id, const QString &sig_name, const Signal &sig); - void removeSignal(const QString &id, const QString &sig_name); - - static uint32_t addressFromId(const QString &id); - inline static std::vector allDBCNames() { return get_dbc_names(); } - inline QString name() const { return dbc_name; } - - void updateMsg(const QString &id, const QString &name, uint32_t size); - inline const Msg *msg(const QString &id) const { return msg(addressFromId(id)); } - inline const Msg *msg(uint32_t address) const { - auto it = msg_map.find(address); - return it != msg_map.end() ? it->second : nullptr; + inline QString name() const { return name_; } + void updateMsg(const MessageId &id, const QString &name, uint32_t size); + void removeMsg(const MessageId &id); + inline std::map getMessages(uint8_t source) { + std::map ret; + for (auto &[address, msg] : msgs) { + MessageId id = {.source = source, .address = address}; + ret[id] = msg; + } + return ret; } + inline const cabana::Msg *msg(const MessageId &id) const { return msg(id.address); } + inline const cabana::Msg* msg(uint8_t source, const QString &name) { + for (auto &[_, msg] : msgs) { + if (msg.name == name) { + return &msg; + } + } + + return nullptr; + } + QStringList signalNames(); + +public slots: + void updateSources(const QSet &s); signals: - void signalAdded(const Signal *sig); - void signalRemoved(const Signal *sig); - void signalUpdated(const Signal *sig); - void msgUpdated(const QString &id); + void signalAdded(MessageId id, const cabana::Signal *sig); + void signalRemoved(const cabana::Signal *sig); + void signalUpdated(const cabana::Signal *sig); + void msgUpdated(MessageId id); + void msgRemoved(MessageId id); void DBCFileChanged(); private: - QString dbc_name; - DBC *dbc = nullptr; - std::unordered_map msg_map; + void parseExtraInfo(const QString &content); + std::map msgs; + QString name_; + QSet sources; + + inline const cabana::Msg *msg(uint32_t address) const { + auto it = msgs.find(address); + return it != msgs.end() ? &it->second : nullptr; + } }; -// TODO: Add helper function in dbc.h -double get_raw_value(uint8_t *data, size_t data_size, const Signal &sig); -int bigEndianStartBitsIndex(int start_bit); -int bigEndianBitIndex(int index); - DBCManager *dbc(); + +inline QString msgName(const MessageId &id) { + auto msg = dbc()->msg(id); + return msg ? msg->name : UNTITLED; +} diff --git a/tools/cabana/detailwidget.cc b/tools/cabana/detailwidget.cc index 60d0632d4c..ab723fa0e2 100644 --- a/tools/cabana/detailwidget.cc +++ b/tools/cabana/detailwidget.cc @@ -1,156 +1,190 @@ #include "tools/cabana/detailwidget.h" -#include #include +#include #include -#include -#include "tools/cabana/canmessages.h" -#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/commands.h" // DetailWidget -DetailWidget::DetailWidget(QWidget *parent) : QWidget(parent) { +DetailWidget::DetailWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); - // title + // tabbar + tabbar = new QTabBar(this); + tabbar->setTabsClosable(true); + tabbar->setUsesScrollButtons(true); + tabbar->setAutoHide(true); + tabbar->setContextMenuPolicy(Qt::CustomContextMenu); + main_layout->addWidget(tabbar); + + // message title QHBoxLayout *title_layout = new QHBoxLayout(); - title_layout->addWidget(new QLabel("time:")); + title_layout->setContentsMargins(0, 6, 0, 0); time_label = new QLabel(this); + time_label->setToolTip(tr("Current time")); + time_label->setStyleSheet("QLabel{font-weight:bold;}"); title_layout->addWidget(time_label); - time_label->setStyleSheet("font-weight:bold"); - title_layout->addStretch(); - name_label = new QLabel(this); - name_label->setStyleSheet("font-weight:bold;"); + name_label = new ElidedLabel(this); + name_label->setStyleSheet("QLabel{font-weight:bold;}"); + name_label->setAlignment(Qt::AlignCenter); + name_label->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); title_layout->addWidget(name_label); - title_layout->addStretch(); - edit_btn = new QPushButton(tr("Edit"), this); - edit_btn->setVisible(false); + auto edit_btn = toolButton("pencil", tr("Edit Message")); title_layout->addWidget(edit_btn); + remove_btn = toolButton("x-lg", tr("Remove Message")); + title_layout->addWidget(remove_btn); main_layout->addLayout(title_layout); - // binary view - binary_view = new BinaryView(this); - main_layout->addWidget(binary_view, 0, Qt::AlignTop); + // warning + warning_widget = new QWidget(this); + QHBoxLayout *warning_hlayout = new QHBoxLayout(warning_widget); + warning_hlayout->addWidget(warning_icon = new QLabel(this), 0, Qt::AlignTop); + warning_hlayout->addWidget(warning_label = new QLabel(this), 1, Qt::AlignLeft); + warning_widget->hide(); + main_layout->addWidget(warning_widget); - // signals - signals_container = new QWidget(this); - signals_container->setLayout(new QVBoxLayout); - signals_container->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + // msg widget + splitter = new QSplitter(Qt::Vertical, this); + splitter->setAutoFillBackground(true); + splitter->addWidget(binary_view = new BinaryView(this)); + splitter->addWidget(signal_view = new SignalView(charts, this)); + binary_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); + signal_view->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); + splitter->setStretchFactor(0, 0); + splitter->setStretchFactor(1, 1); - scroll = new ScrollArea(this); - scroll->setWidget(signals_container); - scroll->setWidgetResizable(true); - scroll->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); - main_layout->addWidget(scroll); + tab_widget = new QTabWidget(this); + tab_widget->setStyleSheet("QTabWidget::pane {border: none; margin-bottom: -2px;}"); + tab_widget->setTabPosition(QTabWidget::South); + tab_widget->addTab(splitter, utils::icon("file-earmark-ruled"), "&Msg"); + tab_widget->addTab(history_log = new LogsWidget(this), utils::icon("stopwatch"), "&Logs"); + main_layout->addWidget(tab_widget); - // history log - history_log = new HistoryLog(this); - main_layout->addWidget(history_log); - - QObject::connect(edit_btn, &QPushButton::clicked, this, &DetailWidget::editMsg); - QObject::connect(binary_view, &BinaryView::cellsSelected, this, &DetailWidget::addSignal); - QObject::connect(can, &CANMessages::updated, this, &DetailWidget::updateState); - QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::dbcMsgChanged); -} - -void DetailWidget::setMessage(const QString &message_id) { - if (msg_id != message_id) { - msg_id = message_id; - dbcMsgChanged(); - } -} - -void DetailWidget::dbcMsgChanged() { - if (msg_id.isEmpty()) return; - - qDeleteAll(signals_container->findChildren()); - QString msg_name = tr("untitled"); - if (auto msg = dbc()->msg(msg_id)) { - for (int i = 0; i < msg->sigs.size(); ++i) { - auto form = new SignalEdit(i, msg_id, msg->sigs[i]); - signals_container->layout()->addWidget(form); - QObject::connect(form, &SignalEdit::showChart, [this, sig = &msg->sigs[i]]() { emit showChart(msg_id, sig); }); - QObject::connect(form, &SignalEdit::showFormClicked, this, &DetailWidget::showForm); - QObject::connect(form, &SignalEdit::remove, this, &DetailWidget::removeSignal); - QObject::connect(form, &SignalEdit::save, this, &DetailWidget::saveSignal); + QObject::connect(edit_btn, &QToolButton::clicked, this, &DetailWidget::editMsg); + QObject::connect(remove_btn, &QToolButton::clicked, this, &DetailWidget::removeMsg); + QObject::connect(binary_view, &BinaryView::resizeSignal, signal_view->model, &SignalModel::resizeSignal); + QObject::connect(binary_view, &BinaryView::addSignal, signal_view->model, &SignalModel::addSignal); + QObject::connect(binary_view, &BinaryView::signalHovered, signal_view, &SignalView::signalHovered); + QObject::connect(binary_view, &BinaryView::signalClicked, [this](const cabana::Signal *s) { signal_view->selectSignal(s, true); }); + QObject::connect(binary_view, &BinaryView::editSignal, signal_view->model, &SignalModel::saveSignal); + QObject::connect(binary_view, &BinaryView::removeSignal, signal_view->model, &SignalModel::removeSignal); + QObject::connect(binary_view, &BinaryView::showChart, charts, &ChartsWidget::showChart); + QObject::connect(signal_view, &SignalView::showChart, charts, &ChartsWidget::showChart); + QObject::connect(signal_view, &SignalView::highlight, binary_view, &BinaryView::highlight); + QObject::connect(tab_widget, &QTabWidget::currentChanged, [this]() { updateState(); }); + QObject::connect(can, &AbstractStream::msgsReceived, this, &DetailWidget::updateState); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &DetailWidget::refresh); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &DetailWidget::refresh); + QObject::connect(tabbar, &QTabBar::customContextMenuRequested, this, &DetailWidget::showTabBarContextMenu); + QObject::connect(tabbar, &QTabBar::currentChanged, [this](int index) { + if (index != -1) { + setMessage(tabbar->tabData(index).value()); } - msg_name = msg->name.c_str(); - } - edit_btn->setVisible(true); - name_label->setText(msg_name); + }); + QObject::connect(tabbar, &QTabBar::tabCloseRequested, tabbar, &QTabBar::removeTab); + QObject::connect(charts, &ChartsWidget::seriesChanged, signal_view, &SignalView::updateChartState); +} +void DetailWidget::showTabBarContextMenu(const QPoint &pt) { + int index = tabbar->tabAt(pt); + if (index >= 0) { + QMenu menu(this); + menu.addAction(tr("Close Other Tabs")); + if (menu.exec(tabbar->mapToGlobal(pt))) { + tabbar->moveTab(index, 0); + tabbar->setCurrentIndex(0); + while (tabbar->count() > 1) { + tabbar->removeTab(1); + } + } + } +} + +void DetailWidget::setMessage(const MessageId &message_id) { + if (std::exchange(msg_id, message_id) == message_id) return; + + tabbar->blockSignals(true); + int index = tabbar->count() - 1; + for (/**/; index >= 0; --index) { + if (tabbar->tabData(index).value() == message_id) break; + } + if (index == -1) { + index = tabbar->addTab(message_id.toString()); + tabbar->setTabData(index, QVariant::fromValue(message_id)); + tabbar->setTabToolTip(index, msgName(message_id)); + } + tabbar->setCurrentIndex(index); + tabbar->blockSignals(false); + + setUpdatesEnabled(false); + signal_view->setMessage(msg_id); binary_view->setMessage(msg_id); history_log->setMessage(msg_id); + refresh(); + setUpdatesEnabled(true); } -void DetailWidget::updateState() { - time_label->setText(QString::number(can->currentSec(), 'f', 3)); - if (msg_id.isEmpty()) return; - - binary_view->updateState(); - history_log->updateState(); -} - -void DetailWidget::showForm() { - SignalEdit *sender = qobject_cast(QObject::sender()); - for (auto f : signals_container->findChildren()) { - f->setFormVisible(f == sender && !f->isFormVisible()); - if (f == sender) { - QTimer::singleShot(0, [=]() { scroll->ensureWidgetVisible(f); }); +void DetailWidget::refresh() { + QStringList warnings; + auto msg = dbc()->msg(msg_id); + if (msg) { + if (msg->size != can->lastMessage(msg_id).dat.size()) { + warnings.push_back(tr("Message size (%1) is incorrect.").arg(msg->size)); } + for (auto s : binary_view->getOverlappingSignals()) { + warnings.push_back(tr("%1 has overlapping bits.").arg(s->name)); + } + } else { + warnings.push_back(tr("Drag-Select in binary view to create new signal.")); } + remove_btn->setEnabled(msg != nullptr); + name_label->setText(msgName(msg_id)); + + if (!warnings.isEmpty()) { + warning_label->setText(warnings.join('\n')); + warning_icon->setPixmap(utils::icon(msg ? "exclamation-triangle" : "info-circle")); + } + warning_widget->setVisible(!warnings.isEmpty()); +} + +void DetailWidget::updateState(const QHash *msgs) { + time_label->setText(QString::number(can->currentSec(), 'f', 3)); + if ((msgs && !msgs->contains(msg_id))) + return; + + if (tab_widget->currentIndex() == 0) + binary_view->updateState(); + else + history_log->updateState(); } void DetailWidget::editMsg() { auto msg = dbc()->msg(msg_id); - QString name = msg ? msg->name.c_str() : "untitled"; int size = msg ? msg->size : can->lastMessage(msg_id).dat.size(); - EditMessageDialog dlg(msg_id, name, size, this); + EditMessageDialog dlg(msg_id, msgName(msg_id), size, this); if (dlg.exec()) { - dbc()->updateMsg(msg_id, dlg.name_edit->text(), dlg.size_spin->value()); - dbcMsgChanged(); + UndoStack::push(new EditMsgCommand(msg_id, dlg.name_edit->text(), dlg.size_spin->value())); } } -void DetailWidget::addSignal(int start_bit, int size) { - if (dbc()->msg(msg_id)) { - AddSignalDialog dlg(msg_id, start_bit, size, this); - if (dlg.exec()) { - dbc()->addSignal(msg_id, dlg.form->getSignal()); - dbcMsgChanged(); - } - } -} - -void DetailWidget::saveSignal() { - SignalEdit *sig_form = qobject_cast(QObject::sender()); - auto s = sig_form->form->getSignal(); - dbc()->updateSignal(msg_id, sig_form->sig_name, s); - // update binary view and history log - binary_view->setMessage(msg_id); - history_log->setMessage(msg_id); -} - -void DetailWidget::removeSignal() { - SignalEdit *sig_form = qobject_cast(QObject::sender()); - QString text = tr("Are you sure you want to remove signal '%1'").arg(sig_form->sig_name); - if (QMessageBox::Yes == QMessageBox::question(this, tr("Remove signal"), text)) { - dbc()->removeSignal(msg_id, sig_form->sig_name); - dbcMsgChanged(); - } +void DetailWidget::removeMsg() { + UndoStack::push(new RemoveMsgCommand(msg_id)); } // EditMessageDialog -EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Edit message")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - - QFormLayout *form_layout = new QFormLayout(); - form_layout->addRow("ID", new QLabel(msg_id)); +EditMessageDialog::EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent) + : original_name(title), msg_id(msg_id), QDialog(parent) { + setWindowTitle(tr("Edit message: %1").arg(msg_id.toString())); + QFormLayout *form_layout = new QFormLayout(this); + form_layout->addRow("", error_label = new QLabel); + error_label->setVisible(false); name_edit = new QLineEdit(title, this); + name_edit->setValidator(new NameValidator(name_edit)); form_layout->addRow(tr("Name"), name_edit); size_spin = new QSpinBox(this); @@ -159,28 +193,83 @@ EditMessageDialog::EditMessageDialog(const QString &msg_id, const QString &title size_spin->setValue(size); form_layout->addRow(tr("Size"), size_spin); - main_layout->addLayout(form_layout); + btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + validateName(name_edit->text()); + form_layout->addRow(btn_box); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); setFixedWidth(parent->width() * 0.9); - - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(name_edit, &QLineEdit::textEdited, this, &EditMessageDialog::validateName); + connect(btn_box, &QDialogButtonBox::accepted, this, &QDialog::accept); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); } -// ScrollArea - -bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { - if (obj == widget() && ev->type() == QEvent::Resize) { - int height = widget()->height() + 4; - setMinimumHeight(height > 480 ? 480 : height); - setMaximumHeight(height); +void EditMessageDialog::validateName(const QString &text) { + bool valid = text.compare(UNTITLED, Qt::CaseInsensitive) != 0; + error_label->setVisible(false); + if (!text.isEmpty() && valid && text != original_name) { + valid = dbc()->msg(msg_id.source, text) == nullptr; + if (!valid) { + error_label->setText(tr("Name already exists")); + error_label->setVisible(true); + } } - return QScrollArea::eventFilter(obj, ev); + btn_box->button(QDialogButtonBox::Ok)->setEnabled(valid); } -void ScrollArea::setWidget(QWidget *w) { - QScrollArea::setWidget(w); - w->installEventFilter(this); +// CenterWidget + +CenterWidget::CenterWidget(ChartsWidget *charts, QWidget *parent) : charts(charts), QWidget(parent) { + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->addWidget(welcome_widget = createWelcomeWidget()); +} + +void CenterWidget::setMessage(const MessageId &msg_id) { + if (!detail_widget) { + delete welcome_widget; + welcome_widget = nullptr; + layout()->addWidget(detail_widget = new DetailWidget(charts, this)); + } + detail_widget->setMessage(msg_id); +} + +void CenterWidget::clear() { + delete detail_widget; + detail_widget = nullptr; + if (!welcome_widget) { + layout()->addWidget(welcome_widget = createWelcomeWidget()); + } +} + +QWidget *CenterWidget::createWelcomeWidget() { + QWidget *w = new QWidget(this); + QVBoxLayout *main_layout = new QVBoxLayout(w); + main_layout->addStretch(0); + QLabel *logo = new QLabel("CABANA"); + logo->setAlignment(Qt::AlignCenter); + logo->setStyleSheet("font-size:50px;font-weight:bold;"); + main_layout->addWidget(logo); + + auto newShortcutRow = [](const QString &title, const QString &key) { + QHBoxLayout *hlayout = new QHBoxLayout(); + auto btn = new QToolButton(); + btn->setText(key); + btn->setEnabled(false); + hlayout->addWidget(new QLabel(title), 0, Qt::AlignRight); + hlayout->addWidget(btn, 0, Qt::AlignLeft); + return hlayout; + }; + + auto lb = new QLabel(tr("<-Select a message to view details")); + lb->setAlignment(Qt::AlignHCenter); + main_layout->addWidget(lb); + main_layout->addLayout(newShortcutRow("Pause", "Space")); + main_layout->addLayout(newShortcutRow("Help", "F1")); + main_layout->addLayout(newShortcutRow("WhatsThis", "Shift+F1")); + main_layout->addStretch(0); + + w->setStyleSheet("QLabel{color:darkGray;}"); + w->setBackgroundRole(QPalette::Base); + w->setAutoFillBackground(true); + return w; } diff --git a/tools/cabana/detailwidget.h b/tools/cabana/detailwidget.h index 9d3b81dcb0..cc5bffd534 100644 --- a/tools/cabana/detailwidget.h +++ b/tools/cabana/detailwidget.h @@ -1,55 +1,67 @@ #pragma once -#include +#include +#include +#include +#include "selfdrive/ui/qt/widgets/controls.h" #include "tools/cabana/binaryview.h" +#include "tools/cabana/chartswidget.h" #include "tools/cabana/historylog.h" #include "tools/cabana/signaledit.h" class EditMessageDialog : public QDialog { - Q_OBJECT - public: - EditMessageDialog(const QString &msg_id, const QString &title, int size, QWidget *parent); + EditMessageDialog(const MessageId &msg_id, const QString &title, int size, QWidget *parent); + void validateName(const QString &text); + MessageId msg_id; + QString original_name; + QDialogButtonBox *btn_box; QLineEdit *name_edit; + QLabel *error_label; QSpinBox *size_spin; }; -class ScrollArea : public QScrollArea { - Q_OBJECT - -public: - ScrollArea(QWidget *parent) : QScrollArea(parent) {} - bool eventFilter(QObject *obj, QEvent *ev) override; - void setWidget(QWidget *w); -}; - class DetailWidget : public QWidget { Q_OBJECT public: - DetailWidget(QWidget *parent); - void setMessage(const QString &message_id); - void dbcMsgChanged(); - -signals: - void showChart(const QString &msg_id, const Signal *sig); - void removeChart(const Signal *sig); + DetailWidget(ChartsWidget *charts, QWidget *parent); + void setMessage(const MessageId &message_id); + void refresh(); + QSize minimumSizeHint() const override { return binary_view->minimumSizeHint(); } private: - void addSignal(int start_bit, int size); - void saveSignal(); - void removeSignal(); + void showTabBarContextMenu(const QPoint &pt); void editMsg(); - void showForm(); - void updateState(); + void removeMsg(); + void updateState(const QHash * msgs = nullptr); - QString msg_id; - QLabel *name_label, *time_label; - QPushButton *edit_btn; - QWidget *signals_container; - HistoryLog *history_log; + MessageId msg_id; + QLabel *time_label, *warning_icon, *warning_label; + ElidedLabel *name_label; + QWidget *warning_widget; + QTabBar *tabbar; + QTabWidget *tab_widget; + QToolButton *remove_btn; + LogsWidget *history_log; BinaryView *binary_view; - ScrollArea *scroll; + SignalView *signal_view; + ChartsWidget *charts; + QSplitter *splitter; +}; + +class CenterWidget : public QWidget { + Q_OBJECT +public: + CenterWidget(ChartsWidget* charts, QWidget *parent); + void setMessage(const MessageId &msg_id); + void clear(); + +private: + QWidget *createWelcomeWidget(); + DetailWidget *detail_widget = nullptr; + QWidget *welcome_widget = nullptr; + ChartsWidget *charts; }; diff --git a/tools/cabana/generate_dbc_json.py b/tools/cabana/generate_dbc_json.py new file mode 100755 index 0000000000..cb122e2eb2 --- /dev/null +++ b/tools/cabana/generate_dbc_json.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import argparse +import json + +from selfdrive.car.car_helpers import get_interface_attr + + +def generate_dbc_json() -> str: + all_cars_by_brand = get_interface_attr("CAR_INFO") + all_dbcs_by_brand = get_interface_attr("DBC") + dbc_map = {car: all_dbcs_by_brand[brand][car]['pt'] for brand, cars in all_cars_by_brand.items() for car in cars if car != 'mock'} + return json.dumps(dict(sorted(dbc_map.items())), indent=2) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Generate mapping for all car fingerprints to DBC names and outputs json file", + formatter_class=argparse.ArgumentDefaultsHelpFormatter) + + parser.add_argument("--out", required=True, help="Generated json filepath") + args = parser.parse_args() + + with open(args.out, 'w') as f: + f.write(generate_dbc_json()) + print(f"Generated and written to {args.out}") diff --git a/tools/cabana/historylog.cc b/tools/cabana/historylog.cc index c0efbca280..4ebe8e0473 100644 --- a/tools/cabana/historylog.cc +++ b/tools/cabana/historylog.cc @@ -1,78 +1,296 @@ #include "tools/cabana/historylog.h" -#include -#include +#include +#include #include +#include "tools/cabana/commands.h" +// HistoryLogModel + QVariant HistoryLogModel::data(const QModelIndex &index, int role) const { - bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); + const bool show_signals = display_signals_mode && sigs.size() > 0; + const auto &m = messages[index.row()]; if (role == Qt::DisplayRole) { - const auto &m = can->messages(msg_id)[index.row()]; if (index.column() == 0) { - return QString::number(m.ts, 'f', 2); + return QString::number((m.mono_time / (double)1e9) - can->routeStartTime(), 'f', 2); } - return has_signal ? QString::number(get_raw_value((uint8_t *)m.dat.begin(), m.dat.size(), dbc_msg->sigs[index.column() - 1])) - : toHex(m.dat); - } else if (role == Qt::FontRole && index.column() == 1 && !has_signal) { - return QFontDatabase::systemFont(QFontDatabase::FixedFont); + int i = index.column() - 1; + return show_signals ? QString::number(m.sig_values[i], 'f', sigs[i]->precision) : toHex(m.data); + } else if (role == ColorsRole) { + return QVariant::fromValue(m.colors); + } else if (role == BytesRole) { + return m.data; + } else if (role == Qt::TextAlignmentRole) { + return (uint32_t)(Qt::AlignRight | Qt::AlignVCenter); } return {}; } -void HistoryLogModel::setMessage(const QString &message_id) { - beginResetModel(); +void HistoryLogModel::setMessage(const MessageId &message_id) { msg_id = message_id; - dbc_msg = dbc()->msg(message_id); - column_count = (dbc_msg && !dbc_msg->sigs.empty() ? dbc_msg->sigs.size() : 1) + 1; - row_count = 0; - endResetModel(); +} - updateState(); +void HistoryLogModel::refresh(bool fetch_message) { + beginResetModel(); + sigs.clear(); + if (auto dbc_msg = dbc()->msg(msg_id)) { + sigs = dbc_msg->getSignals(); + } + last_fetch_time = 0; + has_more_data = true; + messages.clear(); + hex_colors.clear(); + if (fetch_message) { + updateState(); + } + endResetModel(); } QVariant HistoryLogModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { - bool has_signal = dbc_msg && !dbc_msg->sigs.empty(); + const bool show_signals = display_signals_mode && !sigs.empty(); if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { if (section == 0) { return "Time"; } - return has_signal ? dbc_msg->sigs[section - 1].name.c_str() : "Data"; - } else if (role == Qt::BackgroundRole && section > 0 && has_signal) { - return QBrush(QColor(getColor(section - 1))); + if (show_signals) { + QString name = sigs[section - 1]->name; + if (!sigs[section - 1]->unit.isEmpty()) { + name += QString(" (%1)").arg(sigs[section - 1]->unit); + } + return name; + } else { + return "Data"; + } + } else if (role == Qt::BackgroundRole && section > 0 && show_signals) { + return QBrush(getColor(sigs[section - 1])); } } return {}; } -void HistoryLogModel::updateState() { - if (msg_id.isEmpty()) return; +void HistoryLogModel::setDynamicMode(int state) { + dynamic_mode = state != 0; + refresh(); +} - int prev_row_count = row_count; - row_count = can->messages(msg_id).size(); - int delta = row_count - prev_row_count; - if (delta > 0) { - beginInsertRows({}, prev_row_count, row_count - 1); - endInsertRows(); - } else if (delta < 0) { - beginRemoveRows({}, row_count, prev_row_count - 1); - endRemoveRows(); - } - if (row_count > 0) { - emit dataChanged(index(0, 0), index(row_count - 1, column_count - 1), {Qt::DisplayRole}); +void HistoryLogModel::setDisplayType(int type) { + display_signals_mode = type == 0; + refresh(); +} + +void HistoryLogModel::segmentsMerged() { + if (!dynamic_mode) { + has_more_data = true; } } -HistoryLog::HistoryLog(QWidget *parent) : QWidget(parent) { +void HistoryLogModel::setFilter(int sig_idx, const QString &value, std::function cmp) { + filter_sig_idx = sig_idx; + filter_value = value.toDouble(); + filter_cmp = value.isEmpty() ? nullptr : cmp; +} + +void HistoryLogModel::updateState() { + uint64_t current_time = (can->lastMessage(msg_id).ts + can->routeStartTime()) * 1e9 + 1; + auto new_msgs = dynamic_mode ? fetchData(current_time, last_fetch_time) : fetchData(0); + if (!new_msgs.empty()) { + beginInsertRows({}, 0, new_msgs.size() - 1); + messages.insert(messages.begin(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); + endInsertRows(); + } + has_more_data = new_msgs.size() >= batch_size; + last_fetch_time = current_time; +} + +void HistoryLogModel::fetchMore(const QModelIndex &parent) { + if (!messages.empty()) { + auto new_msgs = fetchData(messages.back().mono_time); + if (!new_msgs.empty()) { + beginInsertRows({}, messages.size(), messages.size() + new_msgs.size() - 1); + messages.insert(messages.end(), std::move_iterator(new_msgs.begin()), std::move_iterator(new_msgs.end())); + endInsertRows(); + } + has_more_data = new_msgs.size() >= batch_size; + } +} + +template +std::deque HistoryLogModel::fetchData(InputIt first, InputIt last, uint64_t min_time) { + std::deque msgs; + QVector values(sigs.size()); + for (; first != last && first->mono_time > min_time; ++first) { + for (int i = 0; i < sigs.size(); ++i) { + values[i] = get_raw_value(first->dat, first->size, *sigs[i]); + } + if (!filter_cmp || filter_cmp(values[filter_sig_idx], filter_value)) { + auto &m = msgs.emplace_back(); + m.mono_time = first->mono_time; + m.data = QByteArray((const char *)first->dat, first->size); + m.sig_values = values; + if (msgs.size() >= batch_size && min_time == 0) { + return msgs; + } + } + } + return msgs; +} + +std::deque HistoryLogModel::fetchData(uint64_t from_time, uint64_t min_time) { + const auto &events = can->events().at(msg_id); + const auto freq = can->lastMessage(msg_id).freq; + const bool update_colors = !display_signals_mode || sigs.empty(); + + if (dynamic_mode) { + auto first = std::upper_bound(events.rbegin(), events.rend(), CanEvent{.mono_time=from_time}, std::greater()); + auto msgs = fetchData(first, events.rend(), min_time); + if (update_colors && (min_time > 0 || messages.empty())) { + for (auto it = msgs.rbegin(); it != msgs.rend(); ++it) { + hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); + it->colors = hex_colors.colors; + } + } + return msgs; + } else { + assert(min_time == 0); + auto first = std::upper_bound(events.begin(), events.end(), CanEvent{.mono_time=from_time}); + auto msgs = fetchData(first, events.end(), 0); + if (update_colors) { + for (auto it = msgs.begin(); it != msgs.end(); ++it) { + hex_colors.compute(it->data, it->mono_time / (double)1e9, freq); + it->colors = hex_colors.colors; + } + } + return msgs; + } +} + +// HeaderView + +QSize HeaderView::sectionSizeFromContents(int logicalIndex) const { + static QSize time_col_size = fontMetrics().boundingRect({0, 0, 200, 200}, defaultAlignment(), "000000.000").size() + QSize(10, 6); + if (logicalIndex == 0) { + return time_col_size; + } else { + int default_size = qMax(100, (rect().width() - time_col_size.width()) / (model()->columnCount() - 1)); + QString text = model()->headerData(logicalIndex, this->orientation(), Qt::DisplayRole).toString(); + const QRect rect = fontMetrics().boundingRect({0, 0, default_size, 2000}, defaultAlignment(), text.replace(QChar('_'), ' ')); + QSize size = rect.size() + QSize{10, 6}; + return QSize{qMax(size.width(), default_size), size.height()}; + } +} + +void HeaderView::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const { + auto bg_role = model()->headerData(logicalIndex, Qt::Horizontal, Qt::BackgroundRole); + if (bg_role.isValid()) { + painter->fillRect(rect, bg_role.value()); + } + QString text = model()->headerData(logicalIndex, Qt::Horizontal, Qt::DisplayRole).toString(); + painter->drawText(rect.adjusted(5, 3, -5, -3), defaultAlignment(), text.replace(QChar('_'), ' ')); +} + +// LogsWidget + +LogsWidget::LogsWidget(QWidget *parent) : QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); - model = new HistoryLogModel(this); - table = new QTableView(this); - table->setModel(model); - table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - table->setColumnWidth(0, 60); - table->verticalHeader()->setVisible(false); - table->setStyleSheet("QTableView::item { border:0px; padding-left:5px; padding-right:5px; }"); - main_layout->addWidget(table); + main_layout->setSpacing(0); + + QWidget *toolbar = new QWidget(this); + toolbar->setAutoFillBackground(true); + QHBoxLayout *h = new QHBoxLayout(toolbar); + + filters_widget = new QWidget(this); + QHBoxLayout *filter_layout = new QHBoxLayout(filters_widget); + filter_layout->setContentsMargins(0, 0, 0, 0); + filter_layout->addWidget(display_type_cb = new QComboBox(this)); + filter_layout->addWidget(signals_cb = new QComboBox(this)); + filter_layout->addWidget(comp_box = new QComboBox(this)); + filter_layout->addWidget(value_edit = new QLineEdit(this)); + h->addWidget(filters_widget); + h->addStretch(0); + h->addWidget(dynamic_mode = new QCheckBox(tr("Dynamic")), 0, Qt::AlignRight); + + display_type_cb->addItems({"Signal", "Hex"}); + display_type_cb->setToolTip(tr("Display signal value or raw hex value")); + comp_box->addItems({">", "=", "!=", "<"}); + value_edit->setClearButtonEnabled(true); + value_edit->setValidator(new QDoubleValidator(-500000, 500000, 6, this)); + dynamic_mode->setChecked(true); + dynamic_mode->setEnabled(!can->liveStreaming()); + + main_layout->addWidget(toolbar); + QFrame *line = new QFrame(this); + line->setFrameStyle(QFrame::HLine | QFrame::Sunken); + main_layout->addWidget(line); + main_layout->addWidget(logs = new QTableView(this)); + logs->setModel(model = new HistoryLogModel(this)); + delegate = new MessageBytesDelegate(this); + logs->setItemDelegateForColumn(1, new MessageBytesDelegate(this)); + logs->setHorizontalHeader(new HeaderView(Qt::Horizontal, this)); + logs->horizontalHeader()->setDefaultAlignment(Qt::AlignRight | (Qt::Alignment)Qt::TextWordWrap); + logs->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents); + logs->verticalHeader()->setVisible(false); + logs->setFrameShape(QFrame::NoFrame); + + QObject::connect(display_type_cb, qOverload(&QComboBox::activated), [this](int index) { + logs->setItemDelegateForColumn(1, index == 1 ? delegate : nullptr); + model->setDisplayType(index); + }); + QObject::connect(dynamic_mode, &QCheckBox::stateChanged, model, &HistoryLogModel::setDynamicMode); + QObject::connect(signals_cb, SIGNAL(activated(int)), this, SLOT(setFilter())); + QObject::connect(comp_box, SIGNAL(activated(int)), this, SLOT(setFilter())); + QObject::connect(value_edit, &QLineEdit::textChanged, this, &LogsWidget::setFilter); + QObject::connect(can, &AbstractStream::seekedTo, model, &HistoryLogModel::refresh); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &LogsWidget::refresh); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &LogsWidget::refresh); + QObject::connect(can, &AbstractStream::eventsMerged, model, &HistoryLogModel::segmentsMerged); +} + +void LogsWidget::setMessage(const MessageId &message_id) { + model->setMessage(message_id); + refresh(); +} + +void LogsWidget::refresh() { + model->setFilter(0, "", nullptr); + model->refresh(isVisible()); + bool has_signal = model->sigs.size(); + if (has_signal) { + signals_cb->clear(); + for (auto s : model->sigs) { + signals_cb->addItem(s->name); + } + } + logs->setItemDelegateForColumn(1, !has_signal || display_type_cb->currentIndex() == 1 ? delegate : nullptr); + value_edit->clear(); + comp_box->setCurrentIndex(0); + filters_widget->setVisible(has_signal); +} + +void LogsWidget::setFilter() { + if (value_edit->text().isEmpty() && !value_edit->isModified()) return; + + std::function cmp = nullptr; + switch (comp_box->currentIndex()) { + case 0: cmp = std::greater{}; break; + case 1: cmp = std::equal_to{}; break; + case 2: cmp = [](double l, double r) { return l != r; }; break; // not equal + case 3: cmp = std::less{}; break; + } + model->setFilter(signals_cb->currentIndex(), value_edit->text(), cmp); + model->refresh(); +} + +void LogsWidget::updateState() { + if (isVisible() && dynamic_mode->isChecked()) { + model->updateState(); + } +} + +void LogsWidget::showEvent(QShowEvent *event) { + if (dynamic_mode->isChecked() || model->canFetchMore({}) && model->rowCount() == 0) { + model->refresh(); + } } diff --git a/tools/cabana/historylog.h b/tools/cabana/historylog.h index bc6d1f9376..119b61a0e8 100644 --- a/tools/cabana/historylog.h +++ b/tools/cabana/historylog.h @@ -1,38 +1,92 @@ #pragma once +#include +#include +#include +#include +#include #include -#include "tools/cabana/canmessages.h" #include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" +#include "tools/cabana/util.h" + +class HeaderView : public QHeaderView { +public: + HeaderView(Qt::Orientation orientation, QWidget *parent = nullptr) : QHeaderView(orientation, parent) {} + QSize sectionSizeFromContents(int logicalIndex) const override; + void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const; +}; class HistoryLogModel : public QAbstractTableModel { Q_OBJECT public: HistoryLogModel(QObject *parent) : QAbstractTableModel(parent) {} - void setMessage(const QString &message_id); + void setMessage(const MessageId &message_id); void updateState(); + void setFilter(int sig_idx, const QString &value, std::function cmp); QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } - int columnCount(const QModelIndex &parent = QModelIndex()) const override { return column_count; } + void fetchMore(const QModelIndex &parent) override; + inline bool canFetchMore(const QModelIndex &parent) const override { return has_more_data; } + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return messages.size(); } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { + return display_signals_mode && !sigs.empty() ? sigs.size() + 1 : 2; + } + void refresh(bool fetch_message = true); -private: - QString msg_id; - int row_count = 0; - int column_count = 2; - const Msg *dbc_msg = nullptr; +public slots: + void setDisplayType(int type); + void setDynamicMode(int state); + void segmentsMerged(); + +public: + struct Message { + uint64_t mono_time = 0; + QVector sig_values; + QByteArray data; + QVector colors; + }; + + template + std::deque fetchData(InputIt first, InputIt last, uint64_t min_time); + std::deque fetchData(uint64_t from_time, uint64_t min_time = 0); + + MessageId msg_id; + ChangeTracker hex_colors; + bool has_more_data = true; + const int batch_size = 50; + int filter_sig_idx = -1; + double filter_value = 0; + uint64_t last_fetch_time = 0; + std::function filter_cmp = nullptr; + std::deque messages; + std::vector sigs; + bool dynamic_mode = true; + bool display_signals_mode = true; }; -class HistoryLog : public QWidget { +class LogsWidget : public QFrame { Q_OBJECT public: - HistoryLog(QWidget *parent); - void setMessage(const QString &message_id) { model->setMessage(message_id); } - void updateState() { model->updateState(); } + LogsWidget(QWidget *parent); + void setMessage(const MessageId &message_id); + void updateState(); + void showEvent(QShowEvent *event) override; + +private slots: + void setFilter(); private: - QTableView *table; + void refresh(); + + QTableView *logs; HistoryLogModel *model; + QCheckBox *dynamic_mode; + QComboBox *signals_cb, *comp_box, *display_type_cb; + QLineEdit *value_edit; + QWidget *filters_widget; + MessageBytesDelegate *delegate; }; diff --git a/tools/cabana/mainwin.cc b/tools/cabana/mainwin.cc index 50fcbc455c..c739e26744 100644 --- a/tools/cabana/mainwin.cc +++ b/tools/cabana/mainwin.cc @@ -1,80 +1,489 @@ #include "tools/cabana/mainwin.h" -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include +#include -MainWindow::MainWindow() : QWidget() { - QVBoxLayout *main_layout = new QVBoxLayout(this); +#include "tools/cabana/commands.h" +#include "tools/cabana/route.h" - QHBoxLayout *h_layout = new QHBoxLayout(); - main_layout->addLayout(h_layout); +static MainWindow *main_win = nullptr; +void qLogMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) { + if (type == QtDebugMsg) std::cout << msg.toStdString() << std::endl; + if (main_win) emit main_win->showMessage(msg, 2000); +} - QSplitter *splitter = new QSplitter(Qt::Horizontal, this); +MainWindow::MainWindow() : QMainWindow() { + createDockWindows(); + center_widget = new CenterWidget(charts_widget, this); + setCentralWidget(center_widget); + createActions(); + createStatusBar(); + createShortcuts(); - messages_widget = new MessagesWidget(this); - splitter->addWidget(messages_widget); + // restore states + restoreGeometry(settings.geometry); + if (isMaximized()) { + setGeometry(QApplication::desktop()->availableGeometry(this)); + } + restoreState(settings.window_state); + messages_widget->restoreHeaderState(settings.message_header_state); - detail_widget = new DetailWidget(this); - splitter->addWidget(detail_widget); + qRegisterMetaType("uint64_t"); + qRegisterMetaType>("QSet"); + qRegisterMetaType("ReplyMsgType"); + installMessageHandler([this](ReplyMsgType type, const std::string msg) { + // use queued connection to recv the log messages from replay. + emit showMessage(QString::fromStdString(msg), 2000); + }); + installDownloadProgressHandler([this](uint64_t cur, uint64_t total, bool success) { + emit updateProgressBar(cur, total, success); + }); - splitter->setSizes({100, 500}); - h_layout->addWidget(splitter); + main_win = this; + qInstallMessageHandler(qLogMessageHandler); + QFile json_file("./car_fingerprint_to_dbc.json"); + if (json_file.open(QIODevice::ReadOnly)) { + fingerprint_to_dbc = QJsonDocument::fromJson(json_file.readAll()); + } - // right widgets - QWidget *right_container = new QWidget(this); - right_container->setFixedWidth(640); - r_layout = new QVBoxLayout(right_container); + setStyleSheet(QString(R"(QMainWindow::separator { + width: %1px; /* when vertical */ + height: %1px; /* when horizontal */ + })").arg(style()->pixelMetric(QStyle::PM_SplitterWidth))); - QHBoxLayout *right_hlayout = new QHBoxLayout(); - QLabel *fingerprint_label = new QLabel(this); - right_hlayout->addWidget(fingerprint_label); - - // TODO: click to select another route. - right_hlayout->addWidget(new QLabel(can->route())); - QPushButton *settings_btn = new QPushButton("Settings"); - right_hlayout->addWidget(settings_btn, 0, Qt::AlignRight); - - r_layout->addLayout(right_hlayout); - - video_widget = new VideoWidget(this); - r_layout->addWidget(video_widget, 0, Qt::AlignTop); - - charts_widget = new ChartsWidget(this); - r_layout->addWidget(charts_widget); - - h_layout->addWidget(right_container); - - QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, detail_widget, &DetailWidget::setMessage); - QObject::connect(detail_widget, &DetailWidget::showChart, charts_widget, &ChartsWidget::addChart); + QObject::connect(this, &MainWindow::showMessage, statusBar(), &QStatusBar::showMessage); + QObject::connect(this, &MainWindow::updateProgressBar, this, &MainWindow::updateDownloadProgress); + QObject::connect(messages_widget, &MessagesWidget::msgSelectionChanged, center_widget, &CenterWidget::setMessage); QObject::connect(charts_widget, &ChartsWidget::dock, this, &MainWindow::dockCharts); - QObject::connect(settings_btn, &QPushButton::clicked, this, &MainWindow::setOption); - QObject::connect(can, &CANMessages::eventsMerged, [=]() { fingerprint_label->setText(can->carFingerprint() ); }); + QObject::connect(can, &AbstractStream::streamStarted, this, &MainWindow::loadDBCFromFingerprint); + QObject::connect(can, &AbstractStream::eventsMerged, this, &MainWindow::updateStatus); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &MainWindow::DBCFileChanged); + QObject::connect(can, &AbstractStream::sourcesUpdated, dbc(), &DBCManager::updateSources); + QObject::connect(UndoStack::instance(), &QUndoStack::cleanChanged, this, &MainWindow::undoStackCleanChanged); + QObject::connect(UndoStack::instance(), &QUndoStack::indexChanged, this, &MainWindow::undoStackIndexChanged); + QObject::connect(&settings, &Settings::changed, this, &MainWindow::updateStatus); +} + +void MainWindow::createActions() { + QMenu *file_menu = menuBar()->addMenu(tr("&File")); + if (!can->liveStreaming()) { + file_menu->addAction(tr("Open Route..."), this, &MainWindow::openRoute); + file_menu->addSeparator(); + } + + file_menu->addAction(tr("New DBC File"), this, &MainWindow::newFile)->setShortcuts(QKeySequence::New); + file_menu->addAction(tr("Open DBC File..."), this, &MainWindow::openFile)->setShortcuts(QKeySequence::Open); + + open_recent_menu = file_menu->addMenu(tr("Open &Recent")); + for (int i = 0; i < MAX_RECENT_FILES; ++i) { + recent_files_acts[i] = new QAction(this); + recent_files_acts[i]->setVisible(false); + QObject::connect(recent_files_acts[i], &QAction::triggered, this, &MainWindow::openRecentFile); + open_recent_menu->addAction(recent_files_acts[i]); + } + updateRecentFileActions(); + + file_menu->addSeparator(); + QMenu *load_opendbc_menu = file_menu->addMenu(tr("Load DBC from commaai/opendbc")); + // load_opendbc_menu->setStyleSheet("QMenu { menu-scrollable: true; }"); + auto dbc_names = allDBCNames(); + std::sort(dbc_names.begin(), dbc_names.end()); + for (const auto &name : dbc_names) { + load_opendbc_menu->addAction(QString::fromStdString(name), this, &MainWindow::openOpendbcFile); + } + + file_menu->addAction(tr("Load DBC From Clipboard"), this, &MainWindow::loadDBCFromClipboard); + + file_menu->addSeparator(); + file_menu->addAction(tr("Save DBC..."), this, &MainWindow::save)->setShortcuts(QKeySequence::Save); + file_menu->addAction(tr("Save DBC As..."), this, &MainWindow::saveAs)->setShortcuts(QKeySequence::SaveAs); + file_menu->addAction(tr("Copy DBC To Clipboard"), this, &MainWindow::saveDBCToClipboard); + file_menu->addSeparator(); + file_menu->addAction(tr("Settings..."), this, &MainWindow::setOption)->setShortcuts(QKeySequence::Preferences); + + file_menu->addSeparator(); + file_menu->addAction(tr("E&xit"), qApp, &QApplication::closeAllWindows)->setShortcuts(QKeySequence::Quit); + + QMenu *edit_menu = menuBar()->addMenu(tr("&Edit")); + auto undo_act = UndoStack::instance()->createUndoAction(this, tr("&Undo")); + undo_act->setShortcuts(QKeySequence::Undo); + edit_menu->addAction(undo_act); + auto redo_act = UndoStack::instance()->createRedoAction(this, tr("&Rndo")); + redo_act->setShortcuts(QKeySequence::Redo); + edit_menu->addAction(redo_act); + edit_menu->addSeparator(); + + QMenu *commands_menu = edit_menu->addMenu(tr("Command &List")); + auto undo_view = new QUndoView(UndoStack::instance()); + undo_view->setWindowTitle(tr("Command List")); + QWidgetAction *commands_act = new QWidgetAction(this); + commands_act->setDefaultWidget(undo_view); + commands_menu->addAction(commands_act); + + if (!can->liveStreaming()) { + QMenu *tools_menu = menuBar()->addMenu(tr("&Tools")); + tools_menu->addAction(tr("Find &Similar Bits"), this, &MainWindow::findSimilarBits); + } + + QMenu *help_menu = menuBar()->addMenu(tr("&Help")); + help_menu->addAction(tr("Help"), this, &MainWindow::onlineHelp)->setShortcuts(QKeySequence::HelpContents); + help_menu->addAction(tr("About &Qt"), qApp, &QApplication::aboutQt); +} + +void MainWindow::createDockWindows() { + // left panel + messages_widget = new MessagesWidget(this); + QDockWidget *dock = new QDockWidget(tr("MESSAGES"), this); + dock->setObjectName("MessagesPanel"); + dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea | Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea); + dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + dock->setWidget(messages_widget); + addDockWidget(Qt::LeftDockWidgetArea, dock); + + // right panel + charts_widget = new ChartsWidget(this); + QWidget *charts_container = new QWidget(this); + charts_layout = new QVBoxLayout(charts_container); + charts_layout->setContentsMargins(0, 0, 0, 0); + charts_layout->addWidget(charts_widget); + + // splitter between video and charts + video_splitter = new QSplitter(Qt::Vertical, this); + video_widget = new VideoWidget(this); + video_splitter->addWidget(video_widget); + QObject::connect(charts_widget, &ChartsWidget::rangeChanged, video_widget, &VideoWidget::rangeChanged); + + video_splitter->addWidget(charts_container); + video_splitter->setStretchFactor(1, 1); + video_splitter->restoreState(settings.video_splitter_state); + if (can->liveStreaming() || video_splitter->sizes()[0] == 0) { + // display video at minimum size. + video_splitter->setSizes({1, 1}); + } + + video_dock = new QDockWidget(can->routeName(), this); + video_dock->setObjectName(tr("VideoPanel")); + video_dock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); + video_dock->setFeatures(QDockWidget::DockWidgetMovable | QDockWidget::DockWidgetFloatable); + video_dock->setWidget(video_splitter); + addDockWidget(Qt::RightDockWidgetArea, video_dock); +} + +void MainWindow::createStatusBar() { + progress_bar = new QProgressBar(); + progress_bar->setRange(0, 100); + progress_bar->setTextVisible(true); + progress_bar->setFixedSize({300, 16}); + progress_bar->setVisible(false); + statusBar()->addWidget(new QLabel(tr("For Help, Press F1"))); + statusBar()->addPermanentWidget(progress_bar); + + statusBar()->addPermanentWidget(status_label = new QLabel(this)); + updateStatus(); +} + +void MainWindow::createShortcuts() { + auto shortcut = new QShortcut(QKeySequence(Qt::Key_Space), this, nullptr, nullptr, Qt::ApplicationShortcut); + QObject::connect(shortcut, &QShortcut::activated, []() { can->pause(!can->isPaused()); }); + shortcut = new QShortcut(QKeySequence(QKeySequence::FullScreen), this, nullptr, nullptr, Qt::ApplicationShortcut); + QObject::connect(shortcut, &QShortcut::activated, this, &MainWindow::toggleFullScreen); + // TODO: add more shortcuts here. +} + +void MainWindow::undoStackIndexChanged(int index) { + int count = UndoStack::instance()->count(); + if (count >= 0) { + QString command_text; + if (index == count) { + command_text = (count == prev_undostack_count ? "Redo " : "") + UndoStack::instance()->text(index - 1); + } else if (index < prev_undostack_index) { + command_text = tr("Undo %1").arg(UndoStack::instance()->text(index)); + } else if (index > prev_undostack_index) { + command_text = tr("Redo %1").arg(UndoStack::instance()->text(index - 1)); + } + statusBar()->showMessage(command_text, 2000); + } + prev_undostack_index = index; + prev_undostack_count = count; + autoSave(); +} + +void MainWindow::undoStackCleanChanged(bool clean) { + if (clean) { + prev_undostack_index = 0; + prev_undostack_count = 0; + } + setWindowModified(!clean); +} + +void MainWindow::DBCFileChanged() { + UndoStack::instance()->clear(); + setWindowFilePath(QString("%1").arg(dbc()->name())); +} + +void MainWindow::openRoute() { + OpenRouteDialog dlg(this); + if (dlg.exec()) { + center_widget->clear(); + charts_widget->removeAll(); + statusBar()->showMessage(tr("Route %1 loaded").arg(can->routeName()), 2000); + } else if (dlg.failedToLoad()) { + close(); + } +} + +void MainWindow::newFile() { + remindSaveChanges(); + dbc()->open("untitled.dbc", ""); +} + +void MainWindow::openFile() { + remindSaveChanges(); + QString fn = QFileDialog::getOpenFileName(this, tr("Open File"), settings.last_dir, "DBC (*.dbc)"); + if (!fn.isEmpty()) { + loadFile(fn); + } +} + +void MainWindow::loadFile(const QString &fn) { + if (!fn.isEmpty()) { + QString dbc_fn = fn; + + // Prompt user to load auto saved file if it exists. + if (QFile::exists(fn + AUTO_SAVE_EXTENSION)) { + auto ret = QMessageBox::question(this, tr("Auto saved DBC found"), tr("Auto saved DBC file from previous session found. Do you want to load it instead?")); + if (ret == QMessageBox::Yes) { + dbc_fn += AUTO_SAVE_EXTENSION; + UndoStack::instance()->resetClean(); // Force user to save on close so the auto saved file is not lost + } + } + + QFile file(dbc_fn); + if (file.open(QIODevice::ReadOnly)) { + auto dbc_name = QFileInfo(fn).baseName(); + QString error; + bool ret = dbc()->open(dbc_name, file.readAll(), &error); + if (ret) { + setCurrentFile(fn); + statusBar()->showMessage(tr("DBC File %1 loaded").arg(fn), 2000); + } else { + QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC file"), tr("Failed to parse DBC file %1").arg(fn)); + msg_box.setDetailedText(error); + msg_box.exec(); + } + } + } +} + +void MainWindow::openOpendbcFile() { + if (auto action = qobject_cast(sender())) { + remindSaveChanges(); + loadDBCFromOpendbc(action->text()); + } +} + +void MainWindow::openRecentFile() { + if (auto action = qobject_cast(sender())) { + remindSaveChanges(); + loadFile(action->data().toString()); + } +} + +void MainWindow::loadDBCFromOpendbc(const QString &name) { + if (name != dbc()->name()) { + remindSaveChanges(); + dbc()->open(name); + } +} + +void MainWindow::loadDBCFromClipboard() { + remindSaveChanges(); + QString dbc_str = QGuiApplication::clipboard()->text(); + QString error; + bool ret = dbc()->open("clipboard", dbc_str, &error); + if (ret && dbc()->msgCount() > 0) { + QMessageBox::information(this, tr("Load From Clipboard"), tr("DBC Successfully Loaded!")); + } else { + QMessageBox msg_box(QMessageBox::Warning, tr("Failed to load DBC from clipboard"), tr("Make sure that you paste the text with correct format.")); + if (!error.isEmpty()) { + msg_box.setDetailedText(error); + } + msg_box.exec(); + } +} + +void MainWindow::loadDBCFromFingerprint() { + // Don't overwrite already loaded DBC + if (!dbc()->name().isEmpty()) { + return; + } + + remindSaveChanges(); + auto fingerprint = can->carFingerprint(); + if (can->liveStreaming()) { + video_dock->setWindowTitle(can->routeName()); + } else { + video_dock->setWindowTitle(tr("ROUTE: %1 FINGERPRINT: %2").arg(can->routeName()).arg(fingerprint.isEmpty() ? tr("Unknown Car") : fingerprint)); + } + if (!fingerprint.isEmpty()) { + auto dbc_name = fingerprint_to_dbc[fingerprint]; + if (dbc_name != QJsonValue::Undefined) { + loadDBCFromOpendbc(dbc_name.toString()); + return; + } + } + newFile(); +} + +void MainWindow::save() { + if (current_file.isEmpty()) { + saveAs(); + } else { + saveFile(current_file); + } +} + +void MainWindow::autoSave() { + if (!current_file.isEmpty() && !UndoStack::instance()->isClean()) { + QFile file(current_file + AUTO_SAVE_EXTENSION); + if (file.open(QIODevice::WriteOnly)) { + file.write(dbc()->generateDBC().toUtf8()); + } + } +} + +void MainWindow::cleanupAutoSaveFile() { + if (!current_file.isEmpty()) { + QFile::remove(current_file + AUTO_SAVE_EXTENSION); + } +} + +void MainWindow::saveFile(const QString &fn) { + cleanupAutoSaveFile(); + QFile file(fn); + if (file.open(QIODevice::WriteOnly)) { + file.write(dbc()->generateDBC().toUtf8()); + UndoStack::instance()->setClean(); + setCurrentFile(fn); + statusBar()->showMessage(tr("File saved"), 2000); + } +} + +void MainWindow::saveAs() { + QString fn = QFileDialog::getSaveFileName(this, tr("Save File"), QDir::cleanPath(settings.last_dir + "/untitled.dbc"), tr("DBC (*.dbc)")); + if (!fn.isEmpty()) { + saveFile(fn); + } +} + +void MainWindow::saveDBCToClipboard() { + QGuiApplication::clipboard()->setText(dbc()->generateDBC()); + QMessageBox::information(this, tr("Copy To Clipboard"), tr("DBC Successfully copied!")); +} + +void MainWindow::setCurrentFile(const QString &fn) { + current_file = fn; + setWindowFilePath(QString("%1").arg(fn)); + settings.recent_files.removeAll(fn); + settings.recent_files.prepend(fn); + while (settings.recent_files.size() > MAX_RECENT_FILES) { + settings.recent_files.removeLast(); + } + settings.last_dir = QFileInfo(fn).absolutePath(); + updateRecentFileActions(); +} + +void MainWindow::updateRecentFileActions() { + int num_recent_files = std::min(settings.recent_files.size(), MAX_RECENT_FILES); + + for (int i = 0; i < num_recent_files; ++i) { + QString text = tr("&%1 %2").arg(i + 1).arg(QFileInfo(settings.recent_files[i]).fileName()); + recent_files_acts[i]->setText(text); + recent_files_acts[i]->setData(settings.recent_files[i]); + recent_files_acts[i]->setVisible(true); + } + for (int i = num_recent_files; i < MAX_RECENT_FILES; ++i) { + recent_files_acts[i]->setVisible(false); + } + open_recent_menu->setEnabled(num_recent_files > 0); +} + +void MainWindow::remindSaveChanges() { + bool discard_changes = false; + while (!UndoStack::instance()->isClean() && !discard_changes) { + int ret = (QMessageBox::question(this, tr("Unsaved Changes"), + tr("You have unsaved changes. Press ok to save them, cancel to discard."), + QMessageBox::Ok | QMessageBox::Cancel)); + if (ret == QMessageBox::Ok) { + save(); + } else { + discard_changes = true; + } + } + UndoStack::instance()->clear(); + current_file = ""; +} + +void MainWindow::updateDownloadProgress(uint64_t cur, uint64_t total, bool success) { + if (success && cur < total) { + progress_bar->setValue((cur / (double)total) * 100); + progress_bar->setFormat(tr("Downloading %p% (%1)").arg(formattedDataSize(total).c_str())); + progress_bar->show(); + } else { + progress_bar->hide(); + } +} + +void MainWindow::updateStatus() { + status_label->setText(tr("Cached Minutes:%1 FPS:%2").arg(settings.max_cached_minutes).arg(settings.fps)); } void MainWindow::dockCharts(bool dock) { if (dock && floating_window) { floating_window->removeEventFilter(charts_widget); - r_layout->addWidget(charts_widget); + charts_layout->insertWidget(0, charts_widget, 1); floating_window->deleteLater(); floating_window = nullptr; } else if (!dock && !floating_window) { - floating_window = new QWidget(nullptr); + floating_window = new QWidget(this); + floating_window->setWindowFlags(Qt::Window); + floating_window->setWindowTitle("Charts"); floating_window->setLayout(new QVBoxLayout()); floating_window->layout()->addWidget(charts_widget); floating_window->installEventFilter(charts_widget); - floating_window->setMinimumSize(QGuiApplication::primaryScreen()->size() / 2); floating_window->showMaximized(); } } void MainWindow::closeEvent(QCloseEvent *event) { + cleanupAutoSaveFile(); + remindSaveChanges(); + + main_win = nullptr; if (floating_window) floating_window->deleteLater(); + + // save states + settings.geometry = saveGeometry(); + settings.window_state = saveState(); + if (!can->liveStreaming()) { + settings.video_splitter_state = video_splitter->saveState(); + } + settings.message_header_state = messages_widget->saveHeaderState(); + settings.save(); QWidget::closeEvent(event); } @@ -83,52 +492,79 @@ void MainWindow::setOption() { dlg.exec(); } -// SettingsDlg - -SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Settings")); - QVBoxLayout *main_layout = new QVBoxLayout(this); - QFormLayout *form_layout = new QFormLayout(); - - fps = new QSpinBox(this); - fps->setRange(10, 100); - fps->setSingleStep(10); - fps->setValue(settings.fps); - form_layout->addRow("FPS", fps); - - log_size = new QSpinBox(this); - log_size->setRange(50, 500); - log_size->setSingleStep(10); - log_size->setValue(settings.can_msg_log_size); - form_layout->addRow(tr("Log size"), log_size); - - cached_segment = new QSpinBox(this); - cached_segment->setRange(3, 60); - cached_segment->setSingleStep(1); - cached_segment->setValue(settings.cached_segment_limit); - form_layout->addRow(tr("Cached segments limit"), cached_segment); - - chart_height = new QSpinBox(this); - chart_height->setRange(100, 500); - chart_height->setSingleStep(10); - chart_height->setValue(settings.chart_height); - form_layout->addRow(tr("Chart height"), chart_height); - - main_layout->addLayout(form_layout); - - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); - - setFixedWidth(360); - connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +void MainWindow::findSimilarBits() { + FindSimilarBitsDlg *dlg = new FindSimilarBitsDlg(this); + QObject::connect(dlg, &FindSimilarBitsDlg::openMessage, messages_widget, &MessagesWidget::selectMessage); + dlg->show(); } -void SettingsDlg::save() { - settings.fps = fps->value(); - settings.can_msg_log_size = log_size->value(); - settings.cached_segment_limit = cached_segment->value(); - settings.chart_height = chart_height->value(); - settings.save(); - accept(); +void MainWindow::onlineHelp() { + if (auto help = findChild()) { + help->close(); + } else { + help = new HelpOverlay(this); + help->setGeometry(rect()); + help->show(); + help->raise(); + } +} + +void MainWindow::toggleFullScreen() { + if (isFullScreen()) { + menuBar()->show(); + statusBar()->show(); + showNormal(); + showMaximized(); + } else { + menuBar()->hide(); + statusBar()->hide(); + showFullScreen(); + } +} + +// HelpOverlay +HelpOverlay::HelpOverlay(MainWindow *parent) : QWidget(parent) { + setAttribute(Qt::WA_NoSystemBackground, true); + setAttribute(Qt::WA_TranslucentBackground, true); + setAttribute(Qt::WA_DeleteOnClose); + parent->installEventFilter(this); +} + +void HelpOverlay::paintEvent(QPaintEvent *event) { + QPainter painter(this); + painter.fillRect(rect(), QColor(0, 0, 0, 50)); + MainWindow *parent = (MainWindow *)parentWidget(); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); + drawHelpForWidget(painter, parent->findChild()); +} + +void HelpOverlay::drawHelpForWidget(QPainter &painter, QWidget *w) { + if (w && w->isVisible() && !w->whatsThis().isEmpty()) { + QPoint pt = mapFromGlobal(w->mapToGlobal(w->rect().center())); + if (rect().contains(pt)) { + QTextDocument document; + document.setHtml(w->whatsThis()); + QSize doc_size = document.size().toSize(); + QPoint topleft = {pt.x() - doc_size.width() / 2, pt.y() - doc_size.height() / 2}; + painter.translate(topleft); + painter.fillRect(QRect{{0, 0}, doc_size}, palette().toolTipBase()); + document.drawContents(&painter); + painter.translate(-topleft); + } + } +} + +bool HelpOverlay::eventFilter(QObject *obj, QEvent *event) { + if (obj == parentWidget() && event->type() == QEvent::Resize) { + QResizeEvent *resize_event = (QResizeEvent *)(event); + setGeometry(QRect{QPoint(0, 0), resize_event->size()}); + } + return false; +} + +void HelpOverlay::mouseReleaseEvent(QMouseEvent *event) { + close(); } diff --git a/tools/cabana/mainwin.h b/tools/cabana/mainwin.h index 594e608b59..3258b95cde 100644 --- a/tools/cabana/mainwin.h +++ b/tools/cabana/mainwin.h @@ -1,37 +1,96 @@ #pragma once +#include +#include +#include +#include +#include +#include + #include "tools/cabana/chartswidget.h" #include "tools/cabana/detailwidget.h" #include "tools/cabana/messageswidget.h" #include "tools/cabana/videowidget.h" +#include "tools/cabana/tools/findsimilarbits.h" -class MainWindow : public QWidget { +const QString AUTO_SAVE_EXTENSION = ".tmp"; + +class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(); void dockCharts(bool dock); + void showStatusMessage(const QString &msg, int timeout = 0) { statusBar()->showMessage(msg, timeout); } + void loadFile(const QString &fn); + +public slots: + void openRoute(); + void newFile(); + void openFile(); + void openRecentFile(); + void openOpendbcFile(); + void loadDBCFromOpendbc(const QString &name); + void loadDBCFromFingerprint(); + void loadDBCFromClipboard(); + void save(); + void saveAs(); + void saveDBCToClipboard(); + +signals: + void showMessage(const QString &msg, int timeout); + void updateProgressBar(uint64_t cur, uint64_t total, bool success); protected: + void remindSaveChanges(); + void saveFile(const QString &fn); + void autoSave(); + void cleanupAutoSaveFile(); + void setCurrentFile(const QString &fn); + void updateRecentFileActions(); + void createActions(); + void createDockWindows(); + void createStatusBar(); + void createShortcuts(); void closeEvent(QCloseEvent *event) override; + void DBCFileChanged(); + void updateDownloadProgress(uint64_t cur, uint64_t total, bool success); void setOption(); + void findSimilarBits(); + void undoStackCleanChanged(bool clean); + void undoStackIndexChanged(int index); + void onlineHelp(); + void toggleFullScreen(); + void updateStatus(); - VideoWidget *video_widget; + VideoWidget *video_widget = nullptr; + QDockWidget *video_dock; MessagesWidget *messages_widget; - DetailWidget *detail_widget; + CenterWidget *center_widget; ChartsWidget *charts_widget; QWidget *floating_window = nullptr; - QVBoxLayout *r_layout; + QVBoxLayout *charts_layout; + QProgressBar *progress_bar; + QLabel *status_label; + QJsonDocument fingerprint_to_dbc; + QSplitter *video_splitter;; + QString current_file = ""; + enum { MAX_RECENT_FILES = 15 }; + QAction *recent_files_acts[MAX_RECENT_FILES] = {}; + QMenu *open_recent_menu = nullptr; + int prev_undostack_index = 0; + int prev_undostack_count = 0; + friend class OnlineHelp; }; -class SettingsDlg : public QDialog { +class HelpOverlay : public QWidget { Q_OBJECT - public: - SettingsDlg(QWidget *parent); - void save(); - QSpinBox *fps; - QSpinBox *log_size ; - QSpinBox *cached_segment; - QSpinBox *chart_height; + HelpOverlay(MainWindow *parent); + +protected: + void drawHelpForWidget(QPainter &painter, QWidget *w); + void paintEvent(QPaintEvent *event) override; + void mouseReleaseEvent(QMouseEvent *event) override; + bool eventFilter(QObject *obj, QEvent *event) override; }; diff --git a/tools/cabana/messageswidget.cc b/tools/cabana/messageswidget.cc index 28f79adad3..8dd0bac820 100644 --- a/tools/cabana/messageswidget.cc +++ b/tools/cabana/messageswidget.cc @@ -1,162 +1,256 @@ #include "tools/cabana/messageswidget.h" -#include -#include -#include -#include -#include +#include #include -#include -#include #include -#include "tools/cabana/dbcmanager.h" - MessagesWidget::MessagesWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); - - // DBC file selector - QHBoxLayout *dbc_file_layout = new QHBoxLayout(); - dbc_combo = new QComboBox(this); - auto dbc_names = dbc()->allDBCNames(); - for (const auto &name : dbc_names) { - dbc_combo->addItem(QString::fromStdString(name)); - } - dbc_combo->model()->sort(0); - dbc_combo->setEditable(true); - dbc_combo->setCurrentText(QString()); - dbc_combo->setInsertPolicy(QComboBox::NoInsert); - dbc_combo->completer()->setCompletionMode(QCompleter::PopupCompletion); - QFont font; - font.setBold(true); - dbc_combo->lineEdit()->setFont(font); - dbc_file_layout->addWidget(dbc_combo); - - QPushButton *load_from_paste = new QPushButton(tr("Load from paste"), this); - dbc_file_layout->addWidget(load_from_paste); - - dbc_file_layout->addStretch(); - QPushButton *save_btn = new QPushButton(tr("Save DBC"), this); - dbc_file_layout->addWidget(save_btn); - main_layout->addLayout(dbc_file_layout); + main_layout->setContentsMargins(0 ,0, 0, 0); // message filter - QLineEdit *filter = new QLineEdit(this); + filter = new QLineEdit(this); + QRegularExpression re("\\S+"); + filter->setValidator(new QRegularExpressionValidator(re, this)); + filter->setClearButtonEnabled(true); filter->setPlaceholderText(tr("filter messages")); main_layout->addWidget(filter); // message table table_widget = new QTableView(this); model = new MessageListModel(this); - QSortFilterProxyModel *proxy_model = new QSortFilterProxyModel(this); - proxy_model->setSourceModel(model); - proxy_model->setFilterCaseSensitivity(Qt::CaseInsensitive); - proxy_model->setDynamicSortFilter(false); - table_widget->setModel(proxy_model); + table_widget->setModel(model); + table_widget->setItemDelegateForColumn(5, new MessageBytesDelegate(table_widget)); table_widget->setSelectionBehavior(QAbstractItemView::SelectRows); table_widget->setSelectionMode(QAbstractItemView::SingleSelection); - table_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Expanding); table_widget->setSortingEnabled(true); - table_widget->setColumnWidth(0, 250); - table_widget->setColumnWidth(1, 80); - table_widget->setColumnWidth(2, 80); + table_widget->sortByColumn(0, Qt::AscendingOrder); + table_widget->setColumnWidth(0, 150); + table_widget->setColumnWidth(1, 50); + table_widget->setColumnWidth(2, 50); + table_widget->setColumnWidth(3, 50); table_widget->horizontalHeader()->setStretchLastSection(true); table_widget->verticalHeader()->hide(); - table_widget->sortByColumn(0, Qt::AscendingOrder); main_layout->addWidget(table_widget); + // suppress + QHBoxLayout *suppress_layout = new QHBoxLayout(); + suppress_add = new QPushButton("Suppress Highlighted"); + suppress_clear = new QPushButton(); + suppress_layout->addWidget(suppress_add); + suppress_layout->addWidget(suppress_clear); + main_layout->addLayout(suppress_layout); + // signals/slots - QObject::connect(filter, &QLineEdit::textChanged, proxy_model, &QSortFilterProxyModel::setFilterFixedString); - QObject::connect(can, &CANMessages::updated, model, &MessageListModel::updateState); - QObject::connect(dbc_combo, SIGNAL(activated(const QString &)), SLOT(dbcSelectionChanged(const QString &))); - QObject::connect(load_from_paste, &QPushButton::clicked, this, &MessagesWidget::loadFromPaste); - QObject::connect(save_btn, &QPushButton::clicked, [=]() { - // TODO: save DBC to file - }); - QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { - if (current.isValid()) { - emit msgSelectionChanged(current.data(Qt::UserRole).toString()); + QObject::connect(filter, &QLineEdit::textEdited, model, &MessageListModel::setFilterString); + QObject::connect(can, &AbstractStream::msgsReceived, model, &MessageListModel::msgsReceived); + QObject::connect(can, &AbstractStream::streamStarted, this, &MessagesWidget::reset); + QObject::connect(dbc(), &DBCManager::DBCFileChanged, model, &MessageListModel::sortMessages); + QObject::connect(dbc(), &DBCManager::msgUpdated, model, &MessageListModel::sortMessages); + QObject::connect(dbc(), &DBCManager::msgRemoved, model, &MessageListModel::sortMessages); + QObject::connect(model, &MessageListModel::modelReset, [this]() { + if (current_msg_id) { + selectMessage(*current_msg_id); } }); + QObject::connect(table_widget->selectionModel(), &QItemSelectionModel::currentChanged, [=](const QModelIndex ¤t, const QModelIndex &previous) { + if (current.isValid() && current.row() < model->msgs.size()) { + auto &id = model->msgs[current.row()]; + if (!current_msg_id || id != *current_msg_id) { + current_msg_id = id; + emit msgSelectionChanged(*current_msg_id); + } + } + }); + QObject::connect(suppress_add, &QPushButton::clicked, [=]() { + model->suppress(); + updateSuppressedButtons(); + }); + QObject::connect(suppress_clear, &QPushButton::clicked, [=]() { + model->clearSuppress(); + updateSuppressedButtons(); + }); - // For test purpose - dbc_combo->setCurrentText("toyota_nodsu_pt_generated"); + updateSuppressedButtons(); + + setWhatsThis(tr(R"( + Message View
+ + Byte color
+ constant changing
+ increasing
+ decreasing + )")); } -void MessagesWidget::dbcSelectionChanged(const QString &dbc_file) { - dbc()->open(dbc_file); - // TODO: reset model? - table_widget->sortByColumn(0, Qt::AscendingOrder); -} - -void MessagesWidget::loadFromPaste() { - LoadDBCDialog dlg(this); - if (dlg.exec()) { - dbc()->open("from paste", dlg.dbc_edit->toPlainText()); - dbc_combo->setCurrentText("loaded from paste"); +void MessagesWidget::selectMessage(const MessageId &msg_id) { + if (int row = model->msgs.indexOf(msg_id); row != -1) { + table_widget->selectionModel()->setCurrentIndex(model->index(row, 0), QItemSelectionModel::Rows | QItemSelectionModel::ClearAndSelect); } } +void MessagesWidget::updateSuppressedButtons() { + if (model->suppressed_bytes.empty()) { + suppress_clear->setEnabled(false); + suppress_clear->setText("Clear Suppressed"); + } else { + suppress_clear->setEnabled(true); + suppress_clear->setText(QString("Clear Suppressed (%1)").arg(model->suppressed_bytes.size())); + } +} + +void MessagesWidget::reset() { + current_msg_id = std::nullopt; + table_widget->selectionModel()->clear(); + model->reset(); + filter->clear(); + updateSuppressedButtons(); +} + + // MessageListModel QVariant MessageListModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal && role == Qt::DisplayRole) - return (QString[]){"Name", "ID", "Count", "Bytes"}[section]; + return (QString[]){"Name", "Bus", "ID", "Freq", "Count", "Bytes"}[section]; return {}; } QVariant MessageListModel::data(const QModelIndex &index, int role) const { + const auto &id = msgs[index.row()]; + auto &can_data = can->lastMessage(id); + if (role == Qt::DisplayRole) { - auto it = std::next(can->can_msgs.begin(), index.row()); - if (it != can->can_msgs.end() && !it.value().empty()) { - const QString &msg_id = it.key(); - switch (index.column()) { - case 0: { - auto msg = dbc()->msg(msg_id); - return msg ? msg->name.c_str() : "untitled"; + switch (index.column()) { + case 0: return msgName(id); + case 1: return id.source; + case 2: return QString::number(id.address, 16);; + case 3: return can_data.freq; + case 4: return can_data.count; + case 5: return toHex(can_data.dat); + } + } else if (role == ColorsRole) { + QVector colors = can_data.colors; + if (!suppressed_bytes.empty()) { + for (int i = 0; i < colors.size(); i++) { + if (suppressed_bytes.contains({id, i})) { + colors[i] = QColor(255, 255, 255, 0); } - case 1: return msg_id; - case 2: return can->counters[msg_id]; - case 3: return toHex(it.value().front().dat); } } - } else if (role == Qt::UserRole) { - return std::next(can->can_msgs.begin(), index.row()).key(); - } else if (role == Qt::FontRole) { - if (index.column() == 3) { - return QFontDatabase::systemFont(QFontDatabase::FixedFont); - } + return QVariant::fromValue(colors); + } else if (role == BytesRole) { + return can_data.dat; } return {}; } -void MessageListModel::updateState() { - int prev_row_count = row_count; - row_count = can->can_msgs.size(); - int delta = row_count - prev_row_count; - if (delta > 0) { - beginInsertRows({}, prev_row_count, row_count - 1); - endInsertRows(); - } else if (delta < 0) { - beginRemoveRows({}, row_count, prev_row_count - 1); - endRemoveRows(); - } +void MessageListModel::setFilterString(const QString &string) { + auto contains = [](const MessageId &id, const QString &txt) { + auto cs = Qt::CaseInsensitive; + if (id.toString().contains(txt, cs) || msgName(id).contains(txt, cs)) return true; + // Search by signal name + if (const auto msg = dbc()->msg(id)) { + for (auto s : msg->getSignals()) { + if (s->name.contains(txt, cs)) return true; + } + } + return false; + }; - if (row_count > 0) { - emit dataChanged(index(0, 0), index(row_count - 1, 3), {Qt::DisplayRole}); + filter_str = string; + msgs.clear(); + for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { + if (filter_str.isEmpty() || contains(it.key(), filter_str)) { + msgs.push_back(it.key()); + } + } + sortMessages(); +} + +void MessageListModel::sortMessages() { + beginResetModel(); + if (sort_column == 0) { + std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { + auto ll = std::pair{msgName(l), l}; + auto rr = std::pair{msgName(r), r}; + return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + }); + } else if (sort_column == 1) { + std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { + auto ll = std::pair{l.source, l}; + auto rr = std::pair{r.source, r}; + return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + }); + } else if (sort_column == 2) { + std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { + auto ll = std::pair{l.address, l}; + auto rr = std::pair{r.address, r}; + return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + }); + } else if (sort_column == 3) { + std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { + auto ll = std::pair{can->lastMessage(l).freq, l}; + auto rr = std::pair{can->lastMessage(r).freq, r}; + return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + }); + } else if (sort_column == 4) { + std::sort(msgs.begin(), msgs.end(), [this](auto &l, auto &r) { + auto ll = std::pair{can->lastMessage(l).count, l}; + auto rr = std::pair{can->lastMessage(r).count, r}; + return sort_order == Qt::AscendingOrder ? ll < rr : ll > rr; + }); + } + endResetModel(); +} + +void MessageListModel::msgsReceived(const QHash *new_msgs) { + int prev_row_count = msgs.size(); + if (filter_str.isEmpty() && msgs.size() != can->last_msgs.size()) { + msgs = can->last_msgs.keys(); + } + if (msgs.size() != prev_row_count) { + sortMessages(); + return; + } + for (int i = 0; i < msgs.size(); ++i) { + if (new_msgs->contains(msgs[i])) { + for (int col = 3; col < columnCount(); ++col) + emit dataChanged(index(i, col), index(i, col), {Qt::DisplayRole}); + } } } -LoadDBCDialog::LoadDBCDialog(QWidget *parent) : QDialog(parent) { - QVBoxLayout *main_layout = new QVBoxLayout(this); - dbc_edit = new QTextEdit(this); - dbc_edit->setAcceptRichText(false); - dbc_edit->setPlaceholderText(tr("paste DBC file here")); - main_layout->addWidget(dbc_edit); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); - - setFixedWidth(640); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +void MessageListModel::sort(int column, Qt::SortOrder order) { + if (column != columnCount() - 1) { + sort_column = column; + sort_order = order; + sortMessages(); + } +} + +void MessageListModel::suppress() { + const double cur_ts = can->currentSec(); + + for (auto &id : msgs) { + auto &can_data = can->lastMessage(id); + for (int i = 0; i < can_data.dat.size(); i++) { + const double dt = cur_ts - can_data.last_change_t[i]; + if (dt < 2.0) { + suppressed_bytes.insert({id, i}); + } + } + } +} + +void MessageListModel::clearSuppress() { + suppressed_bytes.clear(); +} + +void MessageListModel::reset() { + beginResetModel(); + filter_str = ""; + msgs.clear(); + clearSuppress(); + endResetModel(); } diff --git a/tools/cabana/messageswidget.h b/tools/cabana/messageswidget.h index 1184772f3b..08dac0d69a 100644 --- a/tools/cabana/messageswidget.h +++ b/tools/cabana/messageswidget.h @@ -1,20 +1,13 @@ #pragma once #include -#include -#include +#include +#include +#include #include -#include -#include "tools/cabana/canmessages.h" - -class LoadDBCDialog : public QDialog { - Q_OBJECT - -public: - LoadDBCDialog(QWidget *parent); - QTextEdit *dbc_edit; -}; +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" class MessageListModel : public QAbstractTableModel { Q_OBJECT @@ -22,13 +15,23 @@ Q_OBJECT public: MessageListModel(QObject *parent) : QAbstractTableModel(parent) {} QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 4; } + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 6; } QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - int rowCount(const QModelIndex &parent = QModelIndex()) const override { return row_count; } - void updateState(); + int rowCount(const QModelIndex &parent = QModelIndex()) const override { return msgs.size(); } + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override; + void setFilterString(const QString &string); + void msgsReceived(const QHash *new_msgs = nullptr); + void sortMessages(); + void suppress(); + void clearSuppress(); + void reset(); + QList msgs; + QSet> suppressed_bytes; private: - int row_count = 0; + QString filter_str; + int sort_column = 0; + Qt::SortOrder sort_order = Qt::AscendingOrder; }; class MessagesWidget : public QWidget { @@ -36,16 +39,21 @@ class MessagesWidget : public QWidget { public: MessagesWidget(QWidget *parent); - -public slots: - void dbcSelectionChanged(const QString &dbc_file); - void loadFromPaste(); + void selectMessage(const MessageId &message_id); + QByteArray saveHeaderState() const { return table_widget->horizontalHeader()->saveState(); } + bool restoreHeaderState(const QByteArray &state) const { return table_widget->horizontalHeader()->restoreState(state); } + void updateSuppressedButtons(); + void reset(); signals: - void msgSelectionChanged(const QString &message_id); + void msgSelectionChanged(const MessageId &message_id); protected: QTableView *table_widget; - QComboBox *dbc_combo; + std::optional current_msg_id; + QLineEdit *filter; MessageListModel *model; + QPushButton *suppress_add; + QPushButton *suppress_clear; + }; diff --git a/tools/cabana/route.cc b/tools/cabana/route.cc new file mode 100644 index 0000000000..ab322cdf90 --- /dev/null +++ b/tools/cabana/route.cc @@ -0,0 +1,68 @@ +#include "tools/cabana/route.h" + +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/streams/replaystream.h" + +OpenRouteDialog::OpenRouteDialog(QWidget *parent) : QDialog(parent) { + // TODO: get route list from api.comma.ai + QHBoxLayout *edit_layout = new QHBoxLayout; + edit_layout->addWidget(new QLabel(tr("Route:"))); + edit_layout->addWidget(route_edit = new QLineEdit(this)); + route_edit->setPlaceholderText(tr("Enter remote route name or click browse to select a local route")); + auto file_btn = new QPushButton(tr("Browse..."), this); + edit_layout->addWidget(file_btn); + + btn_box = new QDialogButtonBox(QDialogButtonBox::Open | QDialogButtonBox::Cancel); + btn_box->button(QDialogButtonBox::Open)->setEnabled(false); + + QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addStretch(0); + main_layout->addLayout(edit_layout); + main_layout->addStretch(0); + main_layout->addWidget(btn_box); + setMinimumSize({550, 120}); + + QObject::connect(btn_box, &QDialogButtonBox::accepted, this, &OpenRouteDialog::loadRoute); + QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + QObject::connect(route_edit, &QLineEdit::textChanged, [this]() { + btn_box->button(QDialogButtonBox::Open)->setEnabled(!route_edit->text().isEmpty()); + }); + QObject::connect(file_btn, &QPushButton::clicked, [=]() { + QString dir = QFileDialog::getExistingDirectory(this, tr("Open Local Route"), settings.last_route_dir); + if (!dir.isEmpty()) { + route_edit->setText(dir); + settings.last_route_dir = QFileInfo(dir).absolutePath(); + } + }); +} + +void OpenRouteDialog::loadRoute() { + btn_box->setEnabled(false); + + QString route = route_edit->text(); + QString data_dir; + if (int idx = route.lastIndexOf('/'); idx != -1) { + data_dir = route.mid(0, idx + 1); + route = route.mid(idx + 1); + } + + bool is_valid_format = Route::parseRoute(route).str.size() > 0; + if (!is_valid_format) { + QMessageBox::warning(nullptr, tr("Warning"), tr("Invalid route format: '%1'").arg(route)); + } else { + failed_to_load = !dynamic_cast(can)->loadRoute(route, data_dir); + if (failed_to_load) { + QMessageBox::warning(nullptr, tr("Warning"), tr("Failed to load route: '%1'").arg(route)); + } else { + accept(); + } + } + + btn_box->setEnabled(true); +} diff --git a/tools/cabana/route.h b/tools/cabana/route.h new file mode 100644 index 0000000000..ceda71d585 --- /dev/null +++ b/tools/cabana/route.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + +class OpenRouteDialog : public QDialog { + Q_OBJECT + +public: + OpenRouteDialog(QWidget *parent); + void loadRoute(); + inline bool failedToLoad() const { return failed_to_load; } + +private: + QLineEdit *route_edit; + QDialogButtonBox *btn_box; + bool failed_to_load = false; +}; diff --git a/tools/cabana/settings.cc b/tools/cabana/settings.cc new file mode 100644 index 0000000000..00bbb94de5 --- /dev/null +++ b/tools/cabana/settings.cc @@ -0,0 +1,94 @@ +#include "tools/cabana/settings.h" + +#include +#include +#include +#include + +// Settings +Settings settings; + +Settings::Settings() { + load(); +} + +void Settings::save() { + QSettings s("settings", QSettings::IniFormat); + s.setValue("fps", fps); + s.setValue("max_cached_minutes", max_cached_minutes); + s.setValue("chart_height", chart_height); + s.setValue("chart_range", chart_range); + s.setValue("chart_column_count", chart_column_count); + s.setValue("last_dir", last_dir); + s.setValue("last_route_dir", last_route_dir); + s.setValue("window_state", window_state); + s.setValue("geometry", geometry); + s.setValue("video_splitter_state", video_splitter_state); + s.setValue("recent_files", recent_files); + s.setValue("message_header_state", message_header_state); + s.setValue("chart_series_type", chart_series_type); +} + +void Settings::load() { + QSettings s("settings", QSettings::IniFormat); + fps = s.value("fps", 10).toInt(); + max_cached_minutes = s.value("max_cached_minutes", 30).toInt(); + chart_height = s.value("chart_height", 200).toInt(); + chart_range = s.value("chart_range", 3 * 60).toInt(); + chart_column_count = s.value("chart_column_count", 1).toInt(); + last_dir = s.value("last_dir", QDir::homePath()).toString(); + last_route_dir = s.value("last_route_dir", QDir::homePath()).toString(); + window_state = s.value("window_state").toByteArray(); + geometry = s.value("geometry").toByteArray(); + video_splitter_state = s.value("video_splitter_state").toByteArray(); + recent_files = s.value("recent_files").toStringList(); + message_header_state = s.value("message_header_state").toByteArray(); + chart_series_type = s.value("chart_series_type", 0).toInt(); +} + +// SettingsDlg + +SettingsDlg::SettingsDlg(QWidget *parent) : QDialog(parent) { + setWindowTitle(tr("Settings")); + QFormLayout *form_layout = new QFormLayout(this); + + fps = new QSpinBox(this); + fps->setRange(10, 100); + fps->setSingleStep(10); + fps->setValue(settings.fps); + form_layout->addRow("FPS", fps); + + cached_minutes = new QSpinBox(this); + cached_minutes->setRange(5, 60); + cached_minutes->setSingleStep(1); + cached_minutes->setValue(settings.max_cached_minutes); + form_layout->addRow(tr("Max Cached Minutes"), cached_minutes); + + chart_series_type = new QComboBox(this); + chart_series_type->addItems({tr("Line"), tr("Step Line"), tr("Scatter")}); + chart_series_type->setCurrentIndex(settings.chart_series_type); + form_layout->addRow(tr("Chart Default Series Type"), chart_series_type); + + chart_height = new QSpinBox(this); + chart_height->setRange(100, 500); + chart_height->setSingleStep(10); + chart_height->setValue(settings.chart_height); + form_layout->addRow(tr("Chart Height"), chart_height); + + auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + form_layout->addRow(buttonBox); + + setFixedWidth(360); + connect(buttonBox, &QDialogButtonBox::accepted, this, &SettingsDlg::save); + connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +void SettingsDlg::save() { + settings.fps = fps->value(); + settings.max_cached_minutes = cached_minutes->value(); + settings.chart_series_type = chart_series_type->currentIndex(); + settings.chart_height = chart_height->value(); + settings.save(); + accept(); + emit settings.changed(); +} diff --git a/tools/cabana/settings.h b/tools/cabana/settings.h new file mode 100644 index 0000000000..c3ba906576 --- /dev/null +++ b/tools/cabana/settings.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include +#include + +class Settings : public QObject { + Q_OBJECT + +public: + Settings(); + void save(); + void load(); + + int fps = 10; + int max_cached_minutes = 30; + int chart_height = 200; + int chart_column_count = 1; + int chart_range = 3 * 60; // e minutes + int chart_series_type = 0; + QString last_dir; + QString last_route_dir; + QByteArray geometry; + QByteArray video_splitter_state; + QByteArray window_state; + QStringList recent_files; + QByteArray message_header_state; + +signals: + void changed(); +}; + +class SettingsDlg : public QDialog { + Q_OBJECT + +public: + SettingsDlg(QWidget *parent); + void save(); + QSpinBox *fps; + QSpinBox *cached_minutes; + QSpinBox *chart_height; + QComboBox *chart_series_type; +}; + +extern Settings settings; diff --git a/tools/cabana/signaledit.cc b/tools/cabana/signaledit.cc index 7cace48402..99ec09195b 100644 --- a/tools/cabana/signaledit.cc +++ b/tools/cabana/signaledit.cc @@ -1,161 +1,602 @@ #include "tools/cabana/signaledit.h" +#include +#include #include -#include #include +#include +#include +#include #include -// SignalForm +#include "tools/cabana/commands.h" -SignalForm::SignalForm(const Signal &sig, QWidget *parent) : start_bit(sig.start_bit), QWidget(parent) { - QFormLayout *form_layout = new QFormLayout(this); +// SignalModel - name = new QLineEdit(sig.name.c_str()); - form_layout->addRow(tr("Name"), name); - - size = new QSpinBox(); - size->setMinimum(1); - size->setValue(sig.size); - form_layout->addRow(tr("Size"), size); - - endianness = new QComboBox(); - endianness->addItems({"Little", "Big"}); - endianness->setCurrentIndex(sig.is_little_endian ? 0 : 1); - form_layout->addRow(tr("Endianness"), endianness); - - form_layout->addRow(tr("lsb"), new QLabel(QString::number(sig.lsb))); - form_layout->addRow(tr("msb"), new QLabel(QString::number(sig.msb))); - - sign = new QComboBox(); - sign->addItems({"Signed", "Unsigned"}); - sign->setCurrentIndex(sig.is_signed ? 0 : 1); - form_layout->addRow(tr("sign"), sign); - - factor = new QDoubleSpinBox(); - factor->setDecimals(3); - factor->setValue(sig.factor); - form_layout->addRow(tr("Factor"), factor); - - offset = new QSpinBox(); - offset->setValue(sig.offset); - form_layout->addRow(tr("Offset"), offset); - - // TODO: parse the following parameters in opendbc - unit = new QLineEdit(); - form_layout->addRow(tr("Unit"), unit); - comment = new QLineEdit(); - form_layout->addRow(tr("Comment"), comment); - min_val = new QDoubleSpinBox(); - factor->setDecimals(3); - form_layout->addRow(tr("Minimum value"), min_val); - max_val = new QDoubleSpinBox(); - factor->setDecimals(3); - form_layout->addRow(tr("Maximum value"), max_val); - val_desc = new QLineEdit(); - form_layout->addRow(tr("Value descriptions"), val_desc); +SignalModel::SignalModel(QObject *parent) : root(new Item), QAbstractItemModel(parent) { + QObject::connect(dbc(), &DBCManager::DBCFileChanged, this, &SignalModel::refresh); + QObject::connect(dbc(), &DBCManager::msgUpdated, this, &SignalModel::handleMsgChanged); + QObject::connect(dbc(), &DBCManager::msgRemoved, this, &SignalModel::handleMsgChanged); + QObject::connect(dbc(), &DBCManager::signalAdded, this, &SignalModel::handleSignalAdded); + QObject::connect(dbc(), &DBCManager::signalUpdated, this, &SignalModel::handleSignalUpdated); + QObject::connect(dbc(), &DBCManager::signalRemoved, this, &SignalModel::handleSignalRemoved); + QObject::connect(can, &AbstractStream::msgsReceived, this, &SignalModel::updateState); } -Signal SignalForm::getSignal() { - // TODO: Check if the size is valid, and no duplicate name - Signal sig = {}; - sig.start_bit = start_bit; - sig.name = name->text().toStdString(); - sig.size = size->text().toInt(); - sig.offset = offset->text().toDouble(); - sig.factor = factor->text().toDouble(); - sig.is_signed = sign->currentIndex() == 0; - sig.is_little_endian = endianness->currentIndex() == 0; - if (sig.is_little_endian) { - sig.lsb = sig.start_bit; - sig.msb = sig.start_bit + sig.size - 1; - } else { - sig.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(sig.start_bit) + sig.size - 1); - sig.msb = sig.start_bit; +void SignalModel::insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig) { + Item *item = new Item{.sig = sig, .parent = parent_item, .title = sig->name, .type = Item::Sig}; + parent_item->children.insert(pos, item); + QString titles[]{"Name", "Size", "Little Endian", "Signed", "Offset", "Factor", "Extra Info", "Unit", "Comment", "Minimum Value", "Maximum Value", "Value Descriptions"}; + for (int i = 0; i < std::size(titles); ++i) { + item->children.push_back(new Item{.sig = sig, .parent = item, .title = titles[i], .type = (Item::Type)(i + Item::Name)}); } - return sig; } -// SignalEdit +void SignalModel::setMessage(const MessageId &id) { + msg_id = id; + filter_str = ""; + refresh(); + updateState(nullptr); +} + +void SignalModel::setFilter(const QString &txt) { + filter_str = txt; + refresh(); +} + +void SignalModel::refresh() { + beginResetModel(); + root.reset(new SignalModel::Item); + if (auto msg = dbc()->msg(msg_id)) { + for (auto s : msg->getSignals()) { + if (filter_str.isEmpty() || s->name.contains(filter_str, Qt::CaseInsensitive)) { + insertItem(root.get(), root->children.size(), s); + } + } + } + endResetModel(); +} + +void SignalModel::updateState(const QHash *msgs) { + if (!msgs || msgs->contains(msg_id)) { + auto &dat = can->lastMessage(msg_id).dat; + int row = 0; + for (auto item : root->children) { + QString value = QString::number(get_raw_value((uint8_t *)dat.begin(), dat.size(), *item->sig), 'f', item->sig->precision); + if (!item->sig->unit.isEmpty()){ + value += " " + item->sig->unit; + } + if (value != item->sig_val) { + item->sig_val = value; + emit dataChanged(index(row, 1), index(row, 1), {Qt::DisplayRole}); + } + ++row; + } + } +} + +SignalModel::Item *SignalModel::getItem(const QModelIndex &index) const { + SignalModel::Item *item = nullptr; + if (index.isValid()) { + item = (SignalModel::Item *)index.internalPointer(); + } + return item ? item : root.get(); +} + +int SignalModel::rowCount(const QModelIndex &parent) const { + if (parent.isValid() && parent.column() > 0) return 0; + + auto parent_item = getItem(parent); + int row_count = parent_item->children.size(); + if (parent_item->type == Item::Sig && !parent_item->extra_expanded) { + row_count -= (Item::Desc - Item::ExtraInfo); + } + return row_count; +} + +Qt::ItemFlags SignalModel::flags(const QModelIndex &index) const { + if (!index.isValid()) return Qt::NoItemFlags; + + auto item = getItem(index); + Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled; + if (index.column() == 1 && item->type != Item::Sig && item->type != Item::ExtraInfo) { + flags |= (item->type == Item::Endian || item->type == Item::Signed) ? Qt::ItemIsUserCheckable : Qt::ItemIsEditable; + } + return flags; +} + +int SignalModel::signalRow(const cabana::Signal *sig) const { + for (int i = 0; i < root->children.size(); ++i) { + if (root->children[i]->sig == sig) return i; + } + return -1; +} + +QModelIndex SignalModel::index(int row, int column, const QModelIndex &parent) const { + if (parent.isValid() && parent.column() != 0) return {}; + + auto parent_item = getItem(parent); + if (parent_item && row < parent_item->children.size()) { + return createIndex(row, column, parent_item->children[row]); + } + return {}; +} + +QModelIndex SignalModel::parent(const QModelIndex &index) const { + if (!index.isValid()) return {}; + Item *parent_item = getItem(index)->parent; + return !parent_item || parent_item == root.get() ? QModelIndex() : createIndex(parent_item->row(), 0, parent_item); +} + +QVariant SignalModel::data(const QModelIndex &index, int role) const { + if (index.isValid()) { + const Item *item = getItem(index); + if (role == Qt::DisplayRole || role == Qt::EditRole) { + if (index.column() == 0) { + return item->type == Item::Sig ? item->sig->name : item->title; + } else { + switch (item->type) { + case Item::Sig: return item->sig_val; + case Item::Name: return item->sig->name; + case Item::Size: return item->sig->size; + case Item::Offset: return QString::number(item->sig->offset, 'f', 6); + case Item::Factor: return QString::number(item->sig->factor, 'f', 6); + case Item::Unit: return item->sig->unit; + case Item::Comment: return item->sig->comment; + case Item::Min: return item->sig->min; + case Item::Max: return item->sig->max; + case Item::Desc: { + QStringList val_desc; + for (auto &[val, desc] : item->sig->val_desc) { + val_desc << QString("%1 \"%2\"").arg(val, desc); + } + return val_desc.join(" "); + } + default: break; + } + } + } else if (role == Qt::CheckStateRole && index.column() == 1) { + if (item->type == Item::Endian) return item->sig->is_little_endian ? Qt::Checked : Qt::Unchecked; + if (item->type == Item::Signed) return item->sig->is_signed ? Qt::Checked : Qt::Unchecked; + } else if (role == Qt::DecorationRole && index.column() == 0 && item->type == Item::ExtraInfo) { + return utils::icon(item->parent->extra_expanded ? "chevron-compact-down" : "chevron-compact-up"); + } + } + return {}; +} + +bool SignalModel::setData(const QModelIndex &index, const QVariant &value, int role) { + if (role != Qt::EditRole && role != Qt::CheckStateRole) return false; + + Item *item = getItem(index); + cabana::Signal s = *item->sig; + switch (item->type) { + case Item::Name: s.name = value.toString(); break; + case Item::Size: s.size = value.toInt(); break; + case Item::Endian: s.is_little_endian = value.toBool(); break; + case Item::Signed: s.is_signed = value.toBool(); break; + case Item::Offset: s.offset = value.toDouble(); break; + case Item::Factor: s.factor = value.toDouble(); break; + case Item::Unit: s.unit = value.toString(); break; + case Item::Comment: s.comment = value.toString(); break; + case Item::Min: s.min = value.toString(); break; + case Item::Max: s.max = value.toString(); break; + case Item::Desc: s.val_desc = value.value(); break; + default: return false; + } + s.updatePrecision(); + bool ret = saveSignal(item->sig, s); + emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole}); + return ret; +} + +void SignalModel::showExtraInfo(const QModelIndex &index) { + auto item = getItem(index); + if (item->type == Item::ExtraInfo) { + if (!item->parent->extra_expanded) { + item->parent->extra_expanded = true; + beginInsertRows(index.parent(), 7, 13); + endInsertRows(); + } else { + item->parent->extra_expanded = false; + beginRemoveRows(index.parent(), 7, 13); + endRemoveRows(); + } + } +} + +bool SignalModel::saveSignal(const cabana::Signal *origin_s, cabana::Signal &s) { + auto msg = dbc()->msg(msg_id); + if (s.name != origin_s->name && msg->sig(s.name) != nullptr) { + QString text = tr("There is already a signal with the same name '%1'").arg(s.name); + QMessageBox::warning(nullptr, tr("Failed to save signal"), text); + return false; + } + + if (s.is_little_endian != origin_s->is_little_endian) { + int start = std::floor(s.start_bit / 8); + if (s.is_little_endian) { + int end = std::floor((s.start_bit - s.size + 1) / 8); + s.start_bit = start == end ? s.start_bit - s.size + 1 : bigEndianStartBitsIndex(s.start_bit); + } else { + int end = std::floor((s.start_bit + s.size - 1) / 8); + s.start_bit = start == end ? s.start_bit + s.size - 1 : bigEndianBitIndex(s.start_bit); + } + } + if (s.is_little_endian) { + s.lsb = s.start_bit; + s.msb = s.start_bit + s.size - 1; + } else { + s.lsb = bigEndianStartBitsIndex(bigEndianBitIndex(s.start_bit) + s.size - 1); + s.msb = s.start_bit; + } + + UndoStack::push(new EditSignalCommand(msg_id, origin_s, s)); + return true; +} + +void SignalModel::addSignal(int start_bit, int size, bool little_endian) { + auto msg = dbc()->msg(msg_id); + for (int i = 0; !msg; ++i) { + QString name = QString("NEW_MSG_") + QString::number(msg_id.address, 16).toUpper(); + if (!dbc()->msg(msg_id.source, name)) { + UndoStack::push(new EditMsgCommand(msg_id, name, can->lastMessage(msg_id).dat.size())); + msg = dbc()->msg(msg_id); + } + } + + cabana::Signal sig = {.is_little_endian = little_endian, .factor = 1, .min = "0", .max = QString::number(std::pow(2, size) - 1)}; + for (int i = 1; /**/; ++i) { + sig.name = QString("NEW_SIGNAL_%1").arg(i); + if (msg->sig(sig.name) == nullptr) break; + } + updateSigSizeParamsFromRange(sig, start_bit, size); + UndoStack::push(new AddSigCommand(msg_id, sig)); +} + +void SignalModel::resizeSignal(const cabana::Signal *sig, int start_bit, int size) { + cabana::Signal s = *sig; + updateSigSizeParamsFromRange(s, start_bit, size); + saveSignal(sig, s); +} + +void SignalModel::removeSignal(const cabana::Signal *sig) { + UndoStack::push(new RemoveSigCommand(msg_id, sig)); +} + +void SignalModel::handleMsgChanged(MessageId id) { + if (id == msg_id) { + refresh(); + } +} + +void SignalModel::handleSignalAdded(MessageId id, const cabana::Signal *sig) { + if (id == msg_id) { + int i = 0; + for (; i < root->children.size(); ++i) { + if (sig->start_bit < root->children[i]->sig->start_bit) break; + } + beginInsertRows({}, i, i); + insertItem(root.get(), i, sig); + endInsertRows(); + updateState(nullptr); + } +} + +void SignalModel::handleSignalUpdated(const cabana::Signal *sig) { + if (int row = signalRow(sig); row != -1) { + emit dataChanged(index(row, 0), index(row, 1), {Qt::DisplayRole, Qt::EditRole, Qt::CheckStateRole}); + } +} + +void SignalModel::handleSignalRemoved(const cabana::Signal *sig) { + if (int row = signalRow(sig); row != -1) { + beginRemoveRows({}, row, row); + delete root->children.takeAt(row); + endRemoveRows(); + } +} + +// SignalItemDelegate + +SignalItemDelegate::SignalItemDelegate(QObject *parent) : QStyledItemDelegate(parent) { + name_validator = new NameValidator(this); + double_validator = new QDoubleValidator(this); + double_validator->setLocale(QLocale::C); // Match locale of QString::toDouble() instead of system + small_font.setPointSize(8); +} + +QSize SignalItemDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const { + int width = option.widget->size().width() / 2; + if (index.column() == 0) { + auto text = index.data(Qt::DisplayRole).toString(); + auto it = width_cache.find(text); + if (it == width_cache.end()) { + int spacing = option.widget->style()->pixelMetric(QStyle::PM_TreeViewIndentation) + color_label_width + 8; + it = width_cache.insert(text, option.fontMetrics.width(text) + spacing); + } + width = std::min(width, it.value()); + } + return {width, QApplication::fontMetrics().height()}; +} + +void SignalItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto item = (SignalModel::Item *)index.internalPointer(); + if (index.column() == 0 && item && item->type == SignalModel::Item::Sig) { + painter->save(); + painter->setRenderHint(QPainter::Antialiasing); + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } + + // color label + auto bg_color = getColor(item->sig); + int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin) + 1; + int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); + QRect rc{option.rect.left() + h_margin, option.rect.top(), color_label_width, option.rect.height()}; + painter->setPen(Qt::NoPen); + painter->setBrush(item->highlight ? bg_color.darker(125) : bg_color); + painter->drawRoundedRect(rc.adjusted(0, v_margin, 0, -v_margin), 3, 3); + painter->setPen(item->highlight ? Qt::white : Qt::black); + painter->setFont(small_font); + painter->drawText(rc, Qt::AlignCenter, QString::number(item->row() + 1)); + + // signal name + painter->setFont(option.font); + painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); + QString text = index.data(Qt::DisplayRole).toString(); + QRect text_rect = option.rect; + text_rect.setLeft(rc.right() + h_margin * 2); + text = painter->fontMetrics().elidedText(text, Qt::ElideRight, text_rect.width()); + painter->drawText(text_rect, option.displayAlignment, text); + painter->restore(); + } else if (index.column() == 1 && item && item->type == SignalModel::Item::Sig) { + // draw signal value + if (option.state & QStyle::State_Selected) { + painter->fillRect(option.rect, option.palette.highlight()); + } + painter->setPen(option.palette.color(option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text)); + QRect rc = option.rect.adjusted(0, 0, -70, 0); + auto text = painter->fontMetrics().elidedText(index.data(Qt::DisplayRole).toString(), Qt::ElideRight, rc.width()); + painter->drawText(rc, Qt::AlignRight | Qt::AlignVCenter, text); + } else { + QStyledItemDelegate::paint(painter, option, index); + } +} + +QWidget *SignalItemDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto item = (SignalModel::Item *)index.internalPointer(); + if (item->type == SignalModel::Item::Name || item->type == SignalModel::Item::Offset || + item->type == SignalModel::Item::Factor || item->type == SignalModel::Item::Min || item->type == SignalModel::Item::Max) { + QLineEdit *e = new QLineEdit(parent); + e->setFrame(false); + e->setValidator(index.row() == 0 ? name_validator : double_validator); + + if (item->type == SignalModel::Item::Name) { + QCompleter *completer = new QCompleter(dbc()->signalNames()); + completer->setCaseSensitivity(Qt::CaseInsensitive); + completer->setFilterMode(Qt::MatchContains); + e->setCompleter(completer); + } + + return e; + } else if (item->type == SignalModel::Item::Size) { + QSpinBox *spin = new QSpinBox(parent); + spin->setFrame(false); + spin->setRange(1, 64); + return spin; + } else if (item->type == SignalModel::Item::Desc) { + ValueDescriptionDlg dlg(item->sig->val_desc, parent); + dlg.setWindowTitle(item->sig->name); + if (dlg.exec()) { + ((QAbstractItemModel *)index.model())->setData(index, QVariant::fromValue(dlg.val_desc)); + } + return nullptr; + } + return QStyledItemDelegate::createEditor(parent, option, index); +} + +// SignalView + +SignalView::SignalView(ChartsWidget *charts, QWidget *parent) : charts(charts), QFrame(parent) { + setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + // title bar + QWidget *title_bar = new QWidget(this); + QHBoxLayout *hl = new QHBoxLayout(title_bar); + hl->addWidget(signal_count_lb = new QLabel()); + filter_edit = new QLineEdit(this); + QRegularExpression re("\\S+"); + filter_edit->setValidator(new QRegularExpressionValidator(re, this)); + filter_edit->setClearButtonEnabled(true); + filter_edit->setPlaceholderText(tr("filter signals")); + hl->addWidget(filter_edit); + hl->addStretch(1); + auto collapse_btn = toolButton("dash-square", tr("Collapse All")); + collapse_btn->setIconSize({12, 12}); + hl->addWidget(collapse_btn); + + // tree view + tree = new TreeView(this); + tree->setModel(model = new SignalModel(this)); + tree->setItemDelegate(new SignalItemDelegate(this)); + tree->setFrameShape(QFrame::NoFrame); + tree->setHeaderHidden(true); + tree->setMouseTracking(true); + tree->setExpandsOnDoubleClick(false); + tree->header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + tree->header()->setStretchLastSection(true); + tree->setMinimumHeight(300); + tree->setStyleSheet("QSpinBox{background-color:white;border:none;} QLineEdit{background-color:white;}"); -SignalEdit::SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent) - : sig_name(sig.name.c_str()), QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); + main_layout->setSpacing(0); + main_layout->addWidget(title_bar); + main_layout->addWidget(tree); - // title bar - QHBoxLayout *title_layout = new QHBoxLayout(); - icon = new QLabel(">"); - icon->setStyleSheet("font-weight:bold"); - title_layout->addWidget(icon); - title = new ElidedLabel(this); - title->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); - title->setText(QString("%1. %2").arg(index + 1).arg(sig_name)); - title->setStyleSheet(QString("font-weight:bold; color:%1").arg(getColor(index))); - title_layout->addWidget(title, 1); + QObject::connect(filter_edit, &QLineEdit::textEdited, model, &SignalModel::setFilter); + QObject::connect(collapse_btn, &QPushButton::clicked, tree, &QTreeView::collapseAll); + QObject::connect(tree, &QAbstractItemView::clicked, this, &SignalView::rowClicked); + QObject::connect(tree, &QTreeView::viewportEntered, [this]() { emit highlight(nullptr); }); + QObject::connect(tree, &QTreeView::entered, [this](const QModelIndex &index) { emit highlight(model->getItem(index)->sig); }); + QObject::connect(model, &QAbstractItemModel::modelReset, this, &SignalView::rowsChanged); + QObject::connect(model, &QAbstractItemModel::rowsRemoved, this, &SignalView::rowsChanged); + QObject::connect(dbc(), &DBCManager::signalAdded, [this](MessageId id, const cabana::Signal *sig) { selectSignal(sig); }); - QPushButton *plot_btn = new QPushButton("📈"); - plot_btn->setToolTip(tr("Show Plot")); - plot_btn->setFixedSize(20, 20); - QObject::connect(plot_btn, &QPushButton::clicked, this, &SignalEdit::showChart); - title_layout->addWidget(plot_btn); - main_layout->addLayout(title_layout); - - // signal form - form_container = new QWidget(this); - QVBoxLayout *v_layout = new QVBoxLayout(form_container); - form = new SignalForm(sig, this); - v_layout->addWidget(form); - - QHBoxLayout *h = new QHBoxLayout(); - QPushButton *remove_btn = new QPushButton(tr("Remove Signal")); - h->addWidget(remove_btn); - h->addStretch(); - QPushButton *save_btn = new QPushButton(tr("Save")); - h->addWidget(save_btn); - v_layout->addLayout(h); - - form_container->setVisible(false); - main_layout->addWidget(form_container); - - // bottom line - QFrame *hline = new QFrame(); - hline->setFrameShape(QFrame::HLine); - hline->setFrameShadow(QFrame::Sunken); - main_layout->addWidget(hline); - - QObject::connect(remove_btn, &QPushButton::clicked, this, &SignalEdit::remove); - QObject::connect(save_btn, &QPushButton::clicked, [=]() { - QString new_name = form->getSignal().name.c_str(); - title->setText(QString("%1. %2").arg(index + 1).arg(new_name)); - emit save(); - sig_name = new_name; - }); - QObject::connect(title, &ElidedLabel::clicked, this, &SignalEdit::showFormClicked); + setWhatsThis(tr(R"( + Signal view
+ + )")); } -void SignalEdit::setFormVisible(bool visible) { - form_container->setVisible(visible); - icon->setText(visible ? "▼" : ">"); +void SignalView::setMessage(const MessageId &id) { + msg_id = id; + filter_edit->clear(); + model->setMessage(id); } -// AddSignalDialog +void SignalView::rowsChanged() { + for (int i = 0; i < model->rowCount(); ++i) { + auto index = model->index(i, 1); + if (!tree->indexWidget(index)) { + QWidget *w = new QWidget(this); + QHBoxLayout *h = new QHBoxLayout(w); + int v_margin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin); + int h_margin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + h->setContentsMargins(0, v_margin, -h_margin, v_margin); + h->setSpacing(style()->pixelMetric(QStyle::PM_ToolBarItemSpacing)); + h->addStretch(0); -AddSignalDialog::AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent) : QDialog(parent) { - setWindowTitle(tr("Add signal to %1").arg(dbc()->msg(id)->name.c_str())); + auto remove_btn = toolButton("x", tr("Remove signal")); + auto plot_btn = toolButton("graph-up", ""); + plot_btn->setCheckable(true); + h->addWidget(plot_btn); + h->addWidget(remove_btn); + + tree->setIndexWidget(index, w); + auto sig = model->getItem(index)->sig; + QObject::connect(remove_btn, &QToolButton::clicked, [=]() { model->removeSignal(sig); }); + QObject::connect(plot_btn, &QToolButton::clicked, [=](bool checked) { + emit showChart(msg_id, sig, checked, QGuiApplication::keyboardModifiers() & Qt::ShiftModifier); + }); + } + } + signal_count_lb->setText(tr("Signals: %1").arg(model->rowCount())); + updateChartState(); +} + +void SignalView::rowClicked(const QModelIndex &index) { + auto item = model->getItem(index); + if (item->type == SignalModel::Item::Sig) { + auto sig_index = model->index(index.row(), 0, index.parent()); + tree->setExpanded(sig_index, !tree->isExpanded(sig_index)); + } else if (item->type == SignalModel::Item::ExtraInfo) { + model->showExtraInfo(index); + } +} + +void SignalView::selectSignal(const cabana::Signal *sig, bool expand) { + if (int row = model->signalRow(sig); row != -1) { + auto idx = model->index(row, 0); + if (expand) { + tree->setExpanded(idx, !tree->isExpanded(idx)); + } + tree->scrollTo(idx, QAbstractItemView::PositionAtTop); + tree->setCurrentIndex(idx); + } +} + +void SignalView::updateChartState() { + int i = 0; + for (auto item : model->root->children) { + bool chart_opened = charts->hasSignal(msg_id, item->sig); + auto buttons = tree->indexWidget(model->index(i, 1))->findChildren(); + if (buttons.size() > 0) { + buttons[0]->setChecked(chart_opened); + buttons[0]->setToolTip(chart_opened ? tr("Close Plot") : tr("Show Plot\nSHIFT click to add to previous opened plot")); + } + ++i; + } +} + +void SignalView::signalHovered(const cabana::Signal *sig) { + auto &children = model->root->children; + for (int i = 0; i < children.size(); ++i) { + bool highlight = children[i]->sig == sig; + if (std::exchange(children[i]->highlight, highlight) != highlight) { + emit model->dataChanged(model->index(i, 0), model->index(i, 0), {Qt::DecorationRole}); + } + } +} + +void SignalView::leaveEvent(QEvent *event) { + emit highlight(nullptr); + QWidget::leaveEvent(event); +} + +// ValueDescriptionDlg + +ValueDescriptionDlg::ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent) : QDialog(parent) { + QHBoxLayout *toolbar_layout = new QHBoxLayout(); + QPushButton *add = new QPushButton(utils::icon("plus"), ""); + QPushButton *remove = new QPushButton(utils::icon("dash"), ""); + remove->setEnabled(false); + toolbar_layout->addWidget(add); + toolbar_layout->addWidget(remove); + toolbar_layout->addStretch(0); + + table = new QTableWidget(descriptions.size(), 2, this); + table->setItemDelegate(new Delegate(this)); + table->setHorizontalHeaderLabels({"Value", "Description"}); + table->horizontalHeader()->setStretchLastSection(true); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSelectionMode(QAbstractItemView::SingleSelection); + table->setEditTriggers(QAbstractItemView::DoubleClicked | QAbstractItemView::EditKeyPressed); + table->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + + int row = 0; + for (auto &[val, desc] : descriptions) { + table->setItem(row, 0, new QTableWidgetItem(val)); + table->setItem(row, 1, new QTableWidgetItem(desc)); + ++row; + } + + auto btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); QVBoxLayout *main_layout = new QVBoxLayout(this); + main_layout->addLayout(toolbar_layout); + main_layout->addWidget(table); + main_layout->addWidget(btn_box); + setMinimumWidth(500); - Signal sig = { - .name = "untitled", - .start_bit = bigEndianBitIndex(start_bit), - .is_little_endian = false, - .size = size, - }; - form = new SignalForm(sig, this); - main_layout->addWidget(form); - auto buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - main_layout->addWidget(buttonBox); - setFixedWidth(parent->width() * 0.9); + QObject::connect(btn_box, &QDialogButtonBox::accepted, this, &ValueDescriptionDlg::save); + QObject::connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); + QObject::connect(add, &QPushButton::clicked, [this]() { + table->setRowCount(table->rowCount() + 1); + table->setItem(table->rowCount() - 1, 0, new QTableWidgetItem); + table->setItem(table->rowCount() - 1, 1, new QTableWidgetItem); + }); + QObject::connect(remove, &QPushButton::clicked, [this]() { table->removeRow(table->currentRow()); }); + QObject::connect(table, &QTableWidget::itemSelectionChanged, [=]() { + remove->setEnabled(table->currentRow() != -1); + }); +} - connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); - connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); +void ValueDescriptionDlg::save() { + for (int i = 0; i < table->rowCount(); ++i) { + QString val = table->item(i, 0)->text().trimmed(); + QString desc = table->item(i, 1)->text().trimmed(); + if (!val.isEmpty() && !desc.isEmpty()) { + val_desc.push_back({val, desc}); + } + } + QDialog::accept(); +} + +QWidget *ValueDescriptionDlg::Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { + QLineEdit *edit = new QLineEdit(parent); + edit->setFrame(false); + if (index.column() == 0) { + edit->setValidator(new QIntValidator(edit)); + } + return edit; } diff --git a/tools/cabana/signaledit.h b/tools/cabana/signaledit.h index f31408657f..d6bad6d88c 100644 --- a/tools/cabana/signaledit.h +++ b/tools/cabana/signaledit.h @@ -1,53 +1,125 @@ #pragma once -#include -#include +#include #include #include -#include -#include +#include +#include +#include -#include "selfdrive/ui/qt/widgets/controls.h" +#include "tools/cabana/chartswidget.h" -#include "tools/cabana/canmessages.h" -#include "tools/cabana/dbcmanager.h" - -class SignalForm : public QWidget { +class SignalModel : public QAbstractItemModel { + Q_OBJECT public: - SignalForm(const Signal &sig, QWidget *parent); - Signal getSignal(); + struct Item { + enum Type {Root, Sig, Name, Size, Endian, Signed, Offset, Factor, ExtraInfo, Unit, Comment, Min, Max, Desc }; + ~Item() { qDeleteAll(children); } + inline int row() { return parent->children.indexOf(this); } - QLineEdit *name, *unit, *comment, *val_desc; - QSpinBox *size, *offset; - QDoubleSpinBox *factor, *min_val, *max_val; - QComboBox *sign, *endianness; - int start_bit = 0; + Type type = Type::Root; + Item *parent = nullptr; + QList children; + + const cabana::Signal *sig = nullptr; + QString title; + bool highlight = false; + bool extra_expanded = false; + QString sig_val = "-"; + }; + + SignalModel(QObject *parent); + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override { return 2; } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &index) const override; + Qt::ItemFlags flags(const QModelIndex &index) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + void setMessage(const MessageId &id); + void setFilter(const QString &txt); + void addSignal(int start_bit, int size, bool little_endian); + bool saveSignal(const cabana::Signal *origin_s, cabana::Signal &s); + void resizeSignal(const cabana::Signal *sig, int start_bit, int size); + void removeSignal(const cabana::Signal *sig); + Item *getItem(const QModelIndex &index) const; + int signalRow(const cabana::Signal *sig) const; + void showExtraInfo(const QModelIndex &index); + +private: + void insertItem(SignalModel::Item *parent_item, int pos, const cabana::Signal *sig); + void handleSignalAdded(MessageId id, const cabana::Signal *sig); + void handleSignalUpdated(const cabana::Signal *sig); + void handleSignalRemoved(const cabana::Signal *sig); + void handleMsgChanged(MessageId id); + void refresh(); + void updateState(const QHash *msgs); + + MessageId msg_id; + QString filter_str; + std::unique_ptr root; + friend class SignalView; }; -class SignalEdit : public QWidget { +class ValueDescriptionDlg : public QDialog { +public: + ValueDescriptionDlg(const ValueDescription &descriptions, QWidget *parent); + ValueDescription val_desc; + +private: + struct Delegate : public QStyledItemDelegate { + Delegate(QWidget *parent) : QStyledItemDelegate(parent) {} + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + }; + + void save(); + QTableWidget *table; +}; + +class SignalItemDelegate : public QStyledItemDelegate { +public: + SignalItemDelegate(QObject *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QValidator *name_validator, *double_validator; + QFont small_font; + const int color_label_width = 18; + mutable QHash width_cache; +}; + +class SignalView : public QFrame { Q_OBJECT public: - SignalEdit(int index, const QString &msg_id, const Signal &sig, QWidget *parent = nullptr); - void setFormVisible(bool show); - inline bool isFormVisible() const { return form_container->isVisible(); } - QString sig_name; - SignalForm *form; + SignalView(ChartsWidget *charts, QWidget *parent); + void setMessage(const MessageId &id); + void signalHovered(const cabana::Signal *sig); + void updateChartState(); + void selectSignal(const cabana::Signal *sig, bool expand = false); + void rowClicked(const QModelIndex &index); + SignalModel *model = nullptr; signals: - void showChart(); - void showFormClicked(); - void remove(); - void save(); + void highlight(const cabana::Signal *sig); + void showChart(const MessageId &id, const cabana::Signal *sig, bool show, bool merge); -protected: - ElidedLabel *title; - QWidget *form_container; - QLabel *icon; -}; +private: + void rowsChanged(); + void leaveEvent(QEvent *event); -class AddSignalDialog : public QDialog { -public: - AddSignalDialog(const QString &id, int start_bit, int size, QWidget *parent); - SignalForm *form; + struct TreeView : public QTreeView { + TreeView(QWidget *parent) : QTreeView(parent) {} + void rowsInserted(const QModelIndex &parent, int start, int end) override { + ((SignalView *)parentWidget())->rowsChanged(); + // update widget geometries in QTreeView::rowsInserted + QTreeView::rowsInserted(parent, start, end); + } + }; + + MessageId msg_id; + TreeView *tree; + QLineEdit *filter_edit; + ChartsWidget *charts; + QLabel *signal_count_lb; }; diff --git a/tools/cabana/streams/abstractstream.cc b/tools/cabana/streams/abstractstream.cc new file mode 100644 index 0000000000..d60beed0c8 --- /dev/null +++ b/tools/cabana/streams/abstractstream.cc @@ -0,0 +1,128 @@ +#include "tools/cabana/streams/abstractstream.h" +#include + +AbstractStream *can = nullptr; + +AbstractStream::AbstractStream(QObject *parent, bool is_live_streaming) : is_live_streaming(is_live_streaming), QObject(parent) { + can = this; + new_msgs = std::make_unique>(); + QObject::connect(this, &AbstractStream::received, this, &AbstractStream::process, Qt::QueuedConnection); + QObject::connect(this, &AbstractStream::seekedTo, this, &AbstractStream::updateLastMsgsTo); +} + +void AbstractStream::process(QHash *messages) { + for (auto it = messages->begin(); it != messages->end(); ++it) { + last_msgs[it.key()] = it.value(); + } + emit updated(); + emit msgsReceived(messages); + delete messages; + processing = false; +} + +bool AbstractStream::updateEvent(const Event *event) { + static double prev_update_ts = 0; + + if (event->which == cereal::Event::Which::CAN) { + double current_sec = event->mono_time / 1e9 - routeStartTime(); + for (const auto &c : event->event.getCan()) { + MessageId id = {.source = c.getSrc(), .address = c.getAddress()}; + CanData &data = (*new_msgs)[id]; + data.ts = current_sec; + data.dat = QByteArray((char *)c.getDat().begin(), c.getDat().size()); + data.count = ++counters[id]; + data.freq = data.count / std::max(1.0, current_sec); + + auto &tracker = change_trackers[id]; + tracker.compute(data.dat, data.ts, data.freq); + data.colors = tracker.colors; + data.last_change_t = tracker.last_change_t; + data.bit_change_counts = tracker.bit_change_counts; + + if (!sources.contains(id.source)) { + sources.insert(id.source); + emit sourcesUpdated(sources); + } + } + + double ts = millis_since_boot(); + if ((ts - prev_update_ts) > (1000.0 / settings.fps) && !processing && !new_msgs->isEmpty()) { + // delay posting CAN message if UI thread is busy + processing = true; + prev_update_ts = ts; + // use pointer to avoid data copy in queued connection. + emit received(new_msgs.release()); + new_msgs.reset(new QHash); + new_msgs->reserve(100); + } + } + return true; +} + +const CanData &AbstractStream::lastMessage(const MessageId &id) { + static CanData empty_data; + auto it = last_msgs.find(id); + return it != last_msgs.end() ? it.value() : empty_data; +} + +// it is thread safe to update data in updateLastMsgsTo. +// updateEvent will not be called before replayStream::seekedTo return. +void AbstractStream::updateLastMsgsTo(double sec) { + new_msgs->clear(); + change_trackers.clear(); + last_msgs.clear(); + counters.clear(); + + CanEvent last_event = {.mono_time = uint64_t((sec + routeStartTime()) * 1e9)}; + for (auto &[id, e] : events_) { + auto it = std::lower_bound(e.crbegin(), e.crend(), last_event, std::greater()); + if (it != e.crend()) { + auto &m = last_msgs[id]; + m.dat = QByteArray((const char *)it->dat, it->size); + m.ts = it->mono_time / 1e9 - routeStartTime(); + m.count = std::distance(it, e.crend()); + m.freq = m.count / std::max(1.0, m.ts); + m.last_change_t = QVector(m.dat.size(), m.ts); + m.colors = QVector(m.dat.size(), QColor(0, 0, 0, 0)); + m.bit_change_counts = QVector>(m.dat.size()); + counters[id] = m.count; + } + } + QTimer::singleShot(0, [this]() { + emit updated(); + emit msgsReceived(&last_msgs); + }); +} + +void AbstractStream::parseEvents(std::unordered_map> &msgs, + std::vector::const_iterator first, std::vector::const_iterator last) { + for (; first != last; ++first) { + if ((*first)->which == cereal::Event::Which::CAN) { + for (const auto &c : (*first)->event.getCan()) { + auto dat = c.getDat(); + auto &m = msgs[{.source = c.getSrc(), .address = c.getAddress()}].emplace_back(); + m.size = std::min(dat.size(), std::size(m.dat)); + memcpy(m.dat, (uint8_t *)dat.begin(), m.size); + m.mono_time = (*first)->mono_time; + } + last_event_ts = std::max(last_event_ts, (*first)->mono_time); + } + } +} + +void AbstractStream::mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last, bool append) { + if (first == last) return; + + if (append) { + parseEvents(events_, first, last); + } else { + std::unordered_map> new_events; + parseEvents(new_events, first, last); + for (auto &[id, new_e] : new_events) { + auto &e = events_[id]; + auto it = std::upper_bound(e.cbegin(), e.cend(), new_e.front()); + e.insert(it, new_e.cbegin(), new_e.cend()); + } + } + emit eventsMerged(); +} diff --git a/tools/cabana/streams/abstractstream.h b/tools/cabana/streams/abstractstream.h new file mode 100644 index 0000000000..8e19f69d2a --- /dev/null +++ b/tools/cabana/streams/abstractstream.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/settings.h" +#include "tools/cabana/util.h" +#include "tools/replay/replay.h" + +struct CanData { + double ts = 0.; + uint32_t count = 0; + uint32_t freq = 0; + QByteArray dat; + QVector colors; + QVector last_change_t; + QVector> bit_change_counts; +}; + +struct CanEvent { + uint64_t mono_time; + uint8_t size; + uint8_t dat[64]; + inline bool operator<(const CanEvent &r) const { return mono_time < r.mono_time; } + inline bool operator>(const CanEvent &r) const { return mono_time > r.mono_time; } +}; + +class AbstractStream : public QObject { + Q_OBJECT + +public: + AbstractStream(QObject *parent, bool is_live_streaming); + virtual ~AbstractStream() {}; + inline bool liveStreaming() const { return is_live_streaming; } + inline double lastEventSecond() const { return last_event_ts / 1e9 - routeStartTime(); } + virtual void seekTo(double ts) {} + virtual QString routeName() const = 0; + virtual QString carFingerprint() const { return ""; } + virtual double totalSeconds() const { return 0; } + virtual double routeStartTime() const { return 0; } + virtual double currentSec() const = 0; + virtual QDateTime currentDateTime() const { return {}; } + virtual const CanData &lastMessage(const MessageId &id); + virtual VisionStreamType visionStreamType() const { return VISION_STREAM_ROAD; } + virtual const Route *route() const { return nullptr; } + virtual void setSpeed(float speed) {} + virtual bool isPaused() const { return false; } + virtual void pause(bool pause) {} + virtual const std::vector *rawEvents() const { return nullptr; } + const std::unordered_map> &events() const { return events_; } + virtual const std::vector> getTimeline() { return {}; } + void mergeEvents(std::vector::const_iterator first, std::vector::const_iterator last, bool append); + +signals: + void paused(); + void resume(); + void seekedTo(double sec); + void streamStarted(); + void eventsMerged(); + void updated(); + void msgsReceived(const QHash *); + void received(QHash *); + void sourcesUpdated(const QSet &s); + +public: + QHash last_msgs; + QSet sources; + +protected: + virtual void process(QHash *); + bool updateEvent(const Event *event); + void updateLastMsgsTo(double sec); + void parseEvents(std::unordered_map> &msgs, std::vector::const_iterator first, std::vector::const_iterator last); + + bool is_live_streaming = false; + std::atomic processing = false; + QHash counters; + std::unique_ptr> new_msgs; + QHash change_trackers; + std::unordered_map> events_; + uint64_t last_event_ts = 0; +}; + +// A global pointer referring to the unique AbstractStream object +extern AbstractStream *can; diff --git a/tools/cabana/streams/livestream.cc b/tools/cabana/streams/livestream.cc new file mode 100644 index 0000000000..62ad635152 --- /dev/null +++ b/tools/cabana/streams/livestream.cc @@ -0,0 +1,86 @@ +#include "tools/cabana/streams/livestream.h" + +#include + +LiveStream::LiveStream(QObject *parent, QString address) : zmq_address(address), AbstractStream(parent, true) { + stream_thread = new QThread(this); + QObject::connect(stream_thread, &QThread::started, [=]() { streamThread(); }); + QObject::connect(stream_thread, &QThread::finished, stream_thread, &QThread::deleteLater); + QTimer::singleShot(0, [this]() { stream_thread->start(); }); +} + +LiveStream::~LiveStream() { + stream_thread->requestInterruption(); + stream_thread->quit(); + stream_thread->wait(); +} + +void LiveStream::streamThread() { + if (!zmq_address.isEmpty()) { + setenv("ZMQ", "1", 1); + } + std::unique_ptr context(Context::create()); + std::string address = zmq_address.isEmpty() ? "127.0.0.1" : zmq_address.toStdString(); + std::unique_ptr sock(SubSocket::create(context.get(), "can", address)); + assert(sock != NULL); + sock->setTimeout(50); + // run as fast as messages come in + while (!QThread::currentThread()->isInterruptionRequested()) { + Message *msg = sock->receive(true); + if (!msg) { + QThread::msleep(50); + continue; + } + std::lock_guard lk(lock); + handleEvent(messages.emplace_back(msg).event); + // TODO: write stream to log file to replay it with cabana --data_dir flag. + } +} + +void LiveStream::handleEvent(Event *evt) { + if (start_ts == 0 || evt->mono_time < start_ts) { + if (evt->mono_time < start_ts) { + qDebug() << "stream is looping back to old time stamp"; + } + start_ts = current_ts = evt->mono_time; + emit streamStarted(); + } + + received.push_back(evt); + if (!pause_) { + if (speed_ < 1 && last_update_ts > 0) { + auto it = std::upper_bound(received.cbegin(), received.cend(), current_ts, [](uint64_t ts, auto &e) { + return ts < e->mono_time; + }); + if (it != received.cend()) { + bool skip = (nanos_since_boot() - last_update_ts) < ((*it)->mono_time - current_ts) / speed_; + if (skip) return; + + evt = *it; + } + } + current_ts = evt->mono_time; + last_update_ts = nanos_since_boot(); + updateEvent(evt); + } +} + +void LiveStream::process(QHash *last_messages) { + { + std::lock_guard lk(lock); + auto first = std::upper_bound(received.cbegin(), received.cend(), last_event_ts, [](uint64_t ts, auto &e) { + return ts < e->mono_time; + }); + mergeEvents(first, received.cend(), true); + if (speed_ == 1) { + received.clear(); + messages.clear(); + } + } + AbstractStream::process(last_messages); +} + +void LiveStream::pause(bool pause) { + pause_ = pause; + emit paused(); +} diff --git a/tools/cabana/streams/livestream.h b/tools/cabana/streams/livestream.h new file mode 100644 index 0000000000..598c0b9365 --- /dev/null +++ b/tools/cabana/streams/livestream.h @@ -0,0 +1,46 @@ +#pragma once + +#include "tools/cabana/streams/abstractstream.h" + +class LiveStream : public AbstractStream { + Q_OBJECT + +public: + LiveStream(QObject *parent, QString address = {}); + virtual ~LiveStream(); + inline QString routeName() const override { + return QString("Live Streaming From %1").arg(zmq_address.isEmpty() ? "127.0.0.1" : zmq_address); + } + inline double routeStartTime() const override { return start_ts / (double)1e9; } + inline double currentSec() const override { return (current_ts - start_ts) / (double)1e9; } + void setSpeed(float speed) override { speed_ = std::min(1.0, speed); } + bool isPaused() const override { return pause_; } + void pause(bool pause) override; + +protected: + virtual void handleEvent(Event *evt); + virtual void streamThread(); + void process(QHash *) override; + + struct Msg { + Msg(Message *m) { + event = ::new Event(aligned_buf.align(m)); + delete m; + } + ~Msg() { ::delete event; } + Event *event; + AlignedBuffer aligned_buf; + }; + + mutable std::mutex lock; + std::vector received; + std::deque messages; + std::atomic start_ts = 0; + std::atomic current_ts = 0; + std::atomic speed_ = 1; + std::atomic pause_ = false; + uint64_t last_update_ts = 0; + + const QString zmq_address; + QThread *stream_thread; +}; diff --git a/tools/cabana/streams/replaystream.cc b/tools/cabana/streams/replaystream.cc new file mode 100644 index 0000000000..8bc9af8ab2 --- /dev/null +++ b/tools/cabana/streams/replaystream.cc @@ -0,0 +1,52 @@ +#include "tools/cabana/streams/replaystream.h" + +ReplayStream::ReplayStream(uint32_t replay_flags, QObject *parent) : replay_flags(replay_flags), AbstractStream(parent, false) { + QObject::connect(&settings, &Settings::changed, [this]() { + if (replay) replay->setSegmentCacheLimit(settings.max_cached_minutes); + }); +} + +ReplayStream::~ReplayStream() { + if (replay) replay->stop(); +} + +static bool event_filter(const Event *e, void *opaque) { + return ((ReplayStream *)opaque)->eventFilter(e); +} + +void ReplayStream::mergeSegments() { + for (auto &[n, seg] : replay->segments()) { + if (seg && seg->isLoaded() && !processed_segments.count(n)) { + const auto &events = seg->log->events; + bool append = processed_segments.empty() || *processed_segments.rbegin() < n; + processed_segments.insert(n); + mergeEvents(events.cbegin(), events.cend(), append); + } + } +} + +bool ReplayStream::loadRoute(const QString &route, const QString &data_dir) { + replay.reset(new Replay(route, {"can", "roadEncodeIdx", "wideRoadEncodeIdx", "carParams"}, {}, nullptr, replay_flags, data_dir, this)); + replay->setSegmentCacheLimit(settings.max_cached_minutes); + replay->installEventFilter(event_filter, this); + QObject::connect(replay.get(), &Replay::seekedTo, this, &AbstractStream::seekedTo); + QObject::connect(replay.get(), &Replay::streamStarted, this, &AbstractStream::streamStarted); + QObject::connect(replay.get(), &Replay::segmentsMerged, this, &ReplayStream::mergeSegments); + if (replay->load()) { + replay->start(); + return true; + } + return false; +} + +bool ReplayStream::eventFilter(const Event *event) { + if (event->which == cereal::Event::Which::CAN) { + updateEvent(event); + } + return true; +} + +void ReplayStream::pause(bool pause) { + replay->pause(pause); + emit(pause ? paused() : resume()); +} diff --git a/tools/cabana/streams/replaystream.h b/tools/cabana/streams/replaystream.h new file mode 100644 index 0000000000..10dc804716 --- /dev/null +++ b/tools/cabana/streams/replaystream.h @@ -0,0 +1,34 @@ +#pragma once + +#include "tools/cabana/streams/abstractstream.h" +#include "tools/cabana/settings.h" + +class ReplayStream : public AbstractStream { + Q_OBJECT + +public: + ReplayStream(uint32_t replay_flags, QObject *parent); + ~ReplayStream(); + bool loadRoute(const QString &route, const QString &data_dir); + bool eventFilter(const Event *event); + void seekTo(double ts) override { replay->seekTo(std::max(double(0), ts), false); }; + inline QString routeName() const override { return replay->route()->name(); } + inline QString carFingerprint() const override { return replay->carFingerprint().c_str(); } + inline VisionStreamType visionStreamType() const override { return replay->hasFlag(REPLAY_FLAG_ECAM) ? VISION_STREAM_WIDE_ROAD : VISION_STREAM_ROAD; } + inline double totalSeconds() const override { return replay->totalSeconds(); } + inline double routeStartTime() const override { return replay->routeStartTime() / (double)1e9; } + inline double currentSec() const override { return replay->currentSeconds(); } + inline QDateTime currentDateTime() const override { return replay->currentDateTime(); } + inline const Route *route() const override { return replay->route(); } + inline void setSpeed(float speed) override { replay->setSpeed(speed); } + inline bool isPaused() const override { return replay->isPaused(); } + void pause(bool pause) override; + const std::vector *rawEvents() const override { return replay->events(); } + inline const std::vector> getTimeline() override { return replay->getTimeline(); } + +private: + void mergeSegments(); + std::unique_ptr replay = nullptr; + uint32_t replay_flags = REPLAY_FLAG_NONE; + std::set processed_segments; +}; diff --git a/tools/cabana/tests/test_cabana b/tools/cabana/tests/test_cabana new file mode 100755 index 0000000000..bac242fbdd --- /dev/null +++ b/tools/cabana/tests/test_cabana @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +export LD_LIBRARY_PATH="../../../opendbc/can:$LD_LIBRARY_PATH" +exec ./_test_cabana "$1" diff --git a/tools/cabana/tests/test_cabana.cc b/tools/cabana/tests/test_cabana.cc new file mode 100644 index 0000000000..8445867acb --- /dev/null +++ b/tools/cabana/tests/test_cabana.cc @@ -0,0 +1,71 @@ + +#include "opendbc/can/common.h" +#undef INFO +#include "catch2/catch.hpp" +#include "tools/replay/logreader.h" +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" + +// demo route, first segment +const std::string TEST_RLOG_URL = "https://commadata2.blob.core.windows.net/commadata2/4cf7a6ad03080c90/2021-09-29--13-46-36/0/rlog.bz2"; + +TEST_CASE("DBCManager::generateDBC") { + DBCManager dbc_origin(nullptr); + dbc_origin.open("toyota_new_mc_pt_generated"); + DBCManager dbc_from_generated(nullptr); + dbc_from_generated.open("", dbc_origin.generateDBC()); + + REQUIRE(dbc_origin.msgCount() == dbc_from_generated.msgCount()); + auto msgs = dbc_origin.getMessages(0); + auto new_msgs = dbc_from_generated.getMessages(0); + for (auto &[id, m] : msgs) { + auto &new_m = new_msgs.at(id); + REQUIRE(m.name == new_m.name); + REQUIRE(m.size == new_m.size); + REQUIRE(m.getSignals().size() == new_m.getSignals().size()); + auto sigs = m.getSignals(); + auto new_sigs = new_m.getSignals(); + for (int i = 0; i < sigs.size(); ++i) { + REQUIRE(*sigs[i] == *new_sigs[i]); + } + } +} + +TEST_CASE("Parse can messages") { + DBCManager dbc(nullptr); + dbc.open("toyota_new_mc_pt_generated"); + CANParser can_parser(0, "toyota_new_mc_pt_generated", {}, {}); + + LogReader log; + REQUIRE(log.load(TEST_RLOG_URL, nullptr, {}, true)); + REQUIRE(log.events.size() > 0); + for (auto e : log.events) { + if (e->which == cereal::Event::Which::CAN) { + std::map, std::vector> values_1; + for (const auto &c : e->event.getCan()) { + const auto msg = dbc.msg({.source = c.getSrc(), .address = c.getAddress()}); + if (c.getSrc() == 0 && msg) { + for (auto sig : msg->getSignals()) { + double val = get_raw_value((uint8_t *)c.getDat().begin(), c.getDat().size(), *sig); + values_1[{c.getAddress(), sig->name}].push_back(val); + } + } + } + + can_parser.UpdateCans(e->mono_time, e->event.getCan()); + auto values_2 = can_parser.query_latest(); + for (auto &[key, v1] : values_1) { + bool found = false; + for (auto &v2 : values_2) { + if (v2.address == key.first && key.second == v2.name.c_str()) { + REQUIRE(v2.all_values.size() == v1.size()); + REQUIRE(v2.all_values == v1); + found = true; + break; + } + } + REQUIRE(found); + } + } + } +} diff --git a/tools/cabana/tests/test_runner.cc b/tools/cabana/tests/test_runner.cc new file mode 100644 index 0000000000..b20ac86c64 --- /dev/null +++ b/tools/cabana/tests/test_runner.cc @@ -0,0 +1,10 @@ +#define CATCH_CONFIG_RUNNER +#include "catch2/catch.hpp" +#include + +int main(int argc, char **argv) { + // unit tests for Qt + QCoreApplication app(argc, argv); + const int res = Catch::Session().run(argc, argv); + return (res < 0xff ? res : 0xff); +} diff --git a/tools/cabana/tools/findsimilarbits.cc b/tools/cabana/tools/findsimilarbits.cc new file mode 100644 index 0000000000..a44ac76059 --- /dev/null +++ b/tools/cabana/tools/findsimilarbits.cc @@ -0,0 +1,171 @@ +#include "tools/cabana/tools/findsimilarbits.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" + +FindSimilarBitsDlg::FindSimilarBitsDlg(QWidget *parent) : QDialog(parent, Qt::WindowFlags() | Qt::Window) { + setWindowTitle(tr("Find similar bits")); + setAttribute(Qt::WA_DeleteOnClose); + + QVBoxLayout *main_layout = new QVBoxLayout(this); + + QHBoxLayout *src_layout = new QHBoxLayout(); + src_bus_combo = new QComboBox(this); + find_bus_combo = new QComboBox(this); + QSet bus_set; + for (auto it = can->last_msgs.begin(); it != can->last_msgs.end(); ++it) { + bus_set << it.key().source; + } + for (auto cb : {src_bus_combo, find_bus_combo}) { + for (uint8_t bus : bus_set) { + cb->addItem(QString::number(bus), bus); + } + cb->model()->sort(0); + cb->setCurrentIndex(0); + } + + msg_cb = new QComboBox(this); + // TODO: update when src_bus_combo changes + for (auto &[id, msg] : dbc()->getMessages(0)) { + msg_cb->addItem(msg.name, id.address); + } + msg_cb->model()->sort(0); + msg_cb->setCurrentIndex(0); + + byte_idx_sb = new QSpinBox(this); + byte_idx_sb->setFixedWidth(50); + byte_idx_sb->setRange(0, 63); + + bit_idx_sb = new QSpinBox(this); + bit_idx_sb->setFixedWidth(50); + bit_idx_sb->setRange(0, 7); + + src_layout->addWidget(new QLabel(tr("Bus"))); + src_layout->addWidget(src_bus_combo); + src_layout->addWidget(msg_cb); + src_layout->addWidget(new QLabel(tr("Byte Index"))); + src_layout->addWidget(byte_idx_sb); + src_layout->addWidget(new QLabel(tr("Bit Index"))); + src_layout->addWidget(bit_idx_sb); + src_layout->addStretch(0); + + QHBoxLayout *find_layout = new QHBoxLayout(); + find_layout->addWidget(new QLabel(tr("Bus"))); + find_layout->addWidget(find_bus_combo); + find_layout->addWidget(new QLabel(tr("Equal"))); + equal_combo = new QComboBox(this); + equal_combo->addItems({"Yes", "No"}); + find_layout->addWidget(equal_combo); + min_msgs = new QLineEdit(this); + min_msgs->setValidator(new QIntValidator(this)); + min_msgs->setText("100"); + find_layout->addWidget(new QLabel(tr("Min msg count"))); + find_layout->addWidget(min_msgs); + search_btn = new QPushButton(tr("&Find"), this); + find_layout->addWidget(search_btn); + find_layout->addStretch(0); + + QGridLayout *grid_layout = new QGridLayout(); + grid_layout->addWidget(new QLabel("Find From:"), 0, 0); + grid_layout->addLayout(src_layout, 0, 1); + grid_layout->addWidget(new QLabel("Find In:"), 1, 0); + grid_layout->addLayout(find_layout, 1, 1); + main_layout->addLayout(grid_layout); + + table = new QTableWidget(this); + table->setSelectionBehavior(QAbstractItemView::SelectRows); + table->setSelectionMode(QAbstractItemView::SingleSelection); + table->setEditTriggers(QAbstractItemView::NoEditTriggers); + table->horizontalHeader()->setStretchLastSection(true); + main_layout->addWidget(table); + + setMinimumSize({700, 500}); + QObject::connect(search_btn, &QPushButton::clicked, this, &FindSimilarBitsDlg::find); + QObject::connect(table, &QTableWidget::doubleClicked, [this](const QModelIndex &index) { + if (index.isValid()) { + MessageId msg_id = {.source = (uint8_t)find_bus_combo->currentData().toUInt(), .address = table->item(index.row(), 0)->text().toUInt(0, 16)}; + emit openMessage(msg_id); + } + }); +} + +void FindSimilarBitsDlg::find() { + search_btn->setEnabled(false); + table->clear(); + uint32_t selected_address = msg_cb->currentData().toUInt(); + auto msg_mismatched = calcBits(src_bus_combo->currentText().toUInt(), selected_address, byte_idx_sb->value(), bit_idx_sb->value(), + find_bus_combo->currentText().toUInt(), equal_combo->currentIndex() == 0, min_msgs->text().toInt()); + table->setRowCount(msg_mismatched.size()); + table->setColumnCount(6); + table->setHorizontalHeaderLabels({"address", "byte idx", "bit idx", "mismatches", "total msgs", "% mismatched"}); + for (int i = 0; i < msg_mismatched.size(); ++i) { + auto &m = msg_mismatched[i]; + table->setItem(i, 0, new QTableWidgetItem(QString("%1").arg(m.address, 1, 16))); + table->setItem(i, 1, new QTableWidgetItem(QString::number(m.byte_idx))); + table->setItem(i, 2, new QTableWidgetItem(QString::number(m.bit_idx))); + table->setItem(i, 3, new QTableWidgetItem(QString::number(m.mismatches))); + table->setItem(i, 4, new QTableWidgetItem(QString::number(m.total))); + table->setItem(i, 5, new QTableWidgetItem(QString::number(m.perc, 'f', 2))); + } + search_btn->setEnabled(true); +} + +QList FindSimilarBitsDlg::calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, + int bit_idx, uint8_t find_bus, bool equal, int min_msgs_cnt) { + QHash> mismatches; + QHash msg_count; + auto events = can->rawEvents(); + int bit_to_find = -1; + for (auto e : *events) { + if (e->which == cereal::Event::Which::CAN) { + for (const auto &c : e->event.getCan()) { + uint8_t src = c.getSrc(); + uint32_t address = c.getAddress(); + const auto dat = c.getDat(); + if (src == bus) { + if (address == selected_address && dat.size() > byte_idx) { + bit_to_find = ((dat[byte_idx] >> (7 - bit_idx)) & 1) != 0; + } + } + if (src == find_bus) { + ++msg_count[address]; + if (bit_to_find == -1) continue; + + auto &mismatched = mismatches[address]; + if (mismatched.size() < dat.size() * 8) { + mismatched.resize(dat.size() * 8); + } + for (int i = 0; i < dat.size(); ++i) { + for (int j = 0; j < 8; ++j) { + int bit = ((dat[i] >> (7 - j)) & 1) != 0; + mismatched[i * 8 + j] += equal ? (bit != bit_to_find) : (bit == bit_to_find); + } + } + } + } + } + } + + QList result; + result.reserve(mismatches.size()); + for (auto it = mismatches.begin(); it != mismatches.end(); ++it) { + if (auto cnt = msg_count[it.key()]; cnt > min_msgs_cnt) { + auto &mismatched = it.value(); + for (int i = 0; i < mismatched.size(); ++i) { + if (float perc = (mismatched[i] / (double)cnt) * 100; perc < 50) { + result.push_back({it.key(), (uint32_t)i / 8, (uint32_t)i % 8, mismatched[i], cnt, perc}); + } + } + } + } + std::sort(result.begin(), result.end(), [](auto &l, auto &r) { return l.perc < r.perc; }); + return result; +} diff --git a/tools/cabana/tools/findsimilarbits.h b/tools/cabana/tools/findsimilarbits.h new file mode 100644 index 0000000000..a40e2cd8df --- /dev/null +++ b/tools/cabana/tools/findsimilarbits.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "tools/cabana/dbcmanager.h" + +class FindSimilarBitsDlg : public QDialog { + Q_OBJECT + +public: + FindSimilarBitsDlg(QWidget *parent); + +signals: + void openMessage(const MessageId &msg_id); + +private: + struct mismatched_struct { + uint32_t address, byte_idx, bit_idx, mismatches, total; + float perc; + }; + QList calcBits(uint8_t bus, uint32_t selected_address, int byte_idx, int bit_idx, uint8_t find_bus, + bool equal, int min_msgs_cnt); + void find(); + + QTableWidget *table; + QComboBox *src_bus_combo, *find_bus_combo, *msg_cb, *equal_combo; + QSpinBox *byte_idx_sb, *bit_idx_sb; + QPushButton *search_btn; + QLineEdit *min_msgs; +}; diff --git a/tools/cabana/util.cc b/tools/cabana/util.cc new file mode 100644 index 0000000000..e5a9749103 --- /dev/null +++ b/tools/cabana/util.cc @@ -0,0 +1,192 @@ +#include "tools/cabana/util.h" + +#include +#include +#include +#include +#include +#include + +#include "selfdrive/ui/qt/util.h" + +static QColor blend(QColor a, QColor b) { + return QColor((a.red() + b.red()) / 2, (a.green() + b.green()) / 2, (a.blue() + b.blue()) / 2, (a.alpha() + b.alpha()) / 2); +} + +void ChangeTracker::compute(const QByteArray &dat, double ts, uint32_t freq) { + if (prev_dat.size() != dat.size()) { + colors.resize(dat.size()); + last_change_t.resize(dat.size()); + bit_change_counts.resize(dat.size()); + std::fill(colors.begin(), colors.end(), QColor(0, 0, 0, 0)); + std::fill(last_change_t.begin(), last_change_t.end(), ts); + } else { + for (int i = 0; i < dat.size(); ++i) { + const uint8_t last = prev_dat[i]; + const uint8_t cur = dat[i]; + + if (last != cur) { + double delta_t = ts - last_change_t[i]; + if (delta_t * freq > periodic_threshold) { + // Last change was while ago, choose color based on delta up or down + if (cur > last) { + colors[i] = QColor(0, 187, 255, start_alpha); // Cyan + } else { + colors[i] = QColor(255, 0, 0, start_alpha); // Red + } + } else { + // Periodic changes + colors[i] = blend(colors[i], QColor(102, 86, 169, start_alpha / 2)); // Greyish/Blue + } + + // Track bit level changes + for (int bit = 0; bit < 8; bit++) { + if ((cur ^ last) & (1 << bit)) { + bit_change_counts[i][bit] += 1; + } + } + + last_change_t[i] = ts; + } else { + // Fade out + float alpha_delta = 1.0 / (freq + 1) / fade_time; + colors[i].setAlphaF(std::max(0.0, colors[i].alphaF() - alpha_delta)); + } + } + } + + prev_dat = dat; +} + +void ChangeTracker::clear() { + prev_dat.clear(); + last_change_t.clear(); + bit_change_counts.clear(); + colors.clear(); +} + +// SegmentTree + +void SegmentTree::build(const QVector &arr) { + size = arr.size(); + tree.resize(4 * size); // size of the tree is 4 times the size of the array + if (size > 0) { + build_tree(arr, 1, 0, size - 1); + } +} + +void SegmentTree::build_tree(const QVector &arr, int n, int left, int right) { + if (left == right) { + const double y = arr[left].y(); + tree[n] = {y, y}; + } else { + const int mid = (left + right) >> 1; + build_tree(arr, 2 * n, left, mid); + build_tree(arr, 2 * n + 1, mid + 1, right); + tree[n] = {std::min(tree[2 * n].first, tree[2 * n + 1].first), std::max(tree[2 * n].second, tree[2 * n + 1].second)}; + } +} + +std::pair SegmentTree::get_minmax(int n, int left, int right, int range_left, int range_right) const { + if (range_left > right || range_right < left) + return {std::numeric_limits::max(), std::numeric_limits::lowest()}; + if (range_left <= left && range_right >= right) + return tree[n]; + int mid = (left + right) >> 1; + auto l = get_minmax(2 * n, left, mid, range_left, range_right); + auto r = get_minmax(2 * n + 1, mid + 1, right, range_left, range_right); + return {std::min(l.first, r.first), std::max(l.second, r.second)}; +} + +// MessageBytesDelegate + +MessageBytesDelegate::MessageBytesDelegate(QObject *parent) : QStyledItemDelegate(parent) { + fixed_font = QFontDatabase::systemFont(QFontDatabase::FixedFont); + byte_width = QFontMetrics(fixed_font).width("00 "); +} + +void MessageBytesDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { + auto colors = index.data(ColorsRole).value>(); + auto byte_list = index.data(BytesRole).toByteArray(); + + int v_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameVMargin); + int h_margin = option.widget->style()->pixelMetric(QStyle::PM_FocusFrameHMargin); + QRect rc{option.rect.left() + h_margin, option.rect.top() + v_margin, byte_width, option.rect.height() - 2 * v_margin}; + + auto color_role = option.state & QStyle::State_Selected ? QPalette::HighlightedText : QPalette::Text; + painter->setPen(option.palette.color(color_role)); + painter->setFont(fixed_font); + for (int i = 0; i < byte_list.size(); ++i) { + if (i < colors.size() && colors[i].alpha() > 0) { + painter->fillRect(rc, colors[i]); + } + painter->drawText(rc, Qt::AlignCenter, toHex(byte_list[i])); + rc.moveLeft(rc.right() + 1); + } +} + +QColor getColor(const cabana::Signal *sig) { + float h = 19 * (float)sig->lsb / 64.0; + h = fmod(h, 1.0); + + size_t hash = qHash(sig->name); + float s = 0.25 + 0.25 * (float)(hash & 0xff) / 255.0; + float v = 0.75 + 0.25 * (float)((hash >> 8) & 0xff) / 255.0; + + return QColor::fromHsvF(h, s, v); +} + +NameValidator::NameValidator(QObject *parent) : QRegExpValidator(QRegExp("^(\\w+)"), parent) {} + +QValidator::State NameValidator::validate(QString &input, int &pos) const { + input.replace(' ', '_'); + return QRegExpValidator::validate(input, pos); +} + +namespace utils { +QPixmap icon(const QString &id) { + static bool dark_theme = QApplication::palette().color(QPalette::WindowText).value() > + QApplication::palette().color(QPalette::Background).value(); + QPixmap pm; + QString key = "bootstrap_" % id % (dark_theme ? "1" : "0"); + if (!QPixmapCache::find(key, &pm)) { + pm = bootstrapPixmap(id); + if (dark_theme) { + QPainter p(&pm); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(pm.rect(), Qt::lightGray); + } + QPixmapCache::insert(key, pm); + } + return pm; +} +} // namespace utils + +QToolButton *toolButton(const QString &icon, const QString &tooltip) { + auto btn = new QToolButton(); + btn->setIcon(utils::icon(icon)); + btn->setToolTip(tooltip); + btn->setAutoRaise(true); + const int metric = qApp->style()->pixelMetric(QStyle::PM_SmallIconSize); + btn->setIconSize({metric, metric}); + return btn; +}; + +QString toHex(uint8_t byte) { + static std::array hex = []() { + std::array ret; + for (int i = 0; i < 256; ++i) ret[i] = QStringLiteral("%1").arg(i, 2, 16, QLatin1Char('0')).toUpper(); + return ret; + }(); + return hex[byte]; +} + +int num_decimals(double num) { + const QString string = QString::number(num); + const QStringList split = string.split('.'); + if (split.size() == 1) { + return 0; + } else { + return split[1].size(); + } +} diff --git a/tools/cabana/util.h b/tools/cabana/util.h new file mode 100644 index 0000000000..330da77179 --- /dev/null +++ b/tools/cabana/util.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tools/cabana/dbc.h" + +class ChangeTracker { +public: + void compute(const QByteArray &dat, double ts, uint32_t freq); + void clear(); + + QVector last_change_t; + QVector colors; + QVector> bit_change_counts; + +private: + const int periodic_threshold = 10; + const int start_alpha = 128; + const float fade_time = 2.0; + QByteArray prev_dat; +}; + +class LogSlider : public QSlider { + Q_OBJECT + +public: + LogSlider(double factor, Qt::Orientation orientation, QWidget *parent = nullptr) : factor(factor), QSlider(orientation, parent) {}; + + void setRange(double min, double max) { + log_min = factor * std::log10(min); + log_max = factor * std::log10(max); + QSlider::setRange(min, max); + setValue(QSlider::value()); + } + int value() const { + double v = log_min + (log_max - log_min) * ((QSlider::value() - minimum()) / double(maximum() - minimum())); + return std::lround(std::pow(10, v / factor)); + } + void setValue(int v) { + double log_v = std::clamp(factor * std::log10(v), log_min, log_max); + v = minimum() + (maximum() - minimum()) * ((log_v - log_min) / (log_max - log_min)); + QSlider::setValue(v); + } + +private: + double factor, log_min = 0, log_max = 1; +}; + +enum { + ColorsRole = Qt::UserRole + 1, + BytesRole = Qt::UserRole + 2 +}; + +class SegmentTree { +public: + SegmentTree() = default; + void build(const QVector &arr); + inline std::pair minmax(int left, int right) const { return get_minmax(1, 0, size - 1, left, right); } + +private: + std::pair get_minmax(int n, int left, int right, int range_left, int range_right) const; + void build_tree(const QVector &arr, int n, int left, int right); + std::vector> tree; + int size = 0; +}; + +class MessageBytesDelegate : public QStyledItemDelegate { + Q_OBJECT +public: + MessageBytesDelegate(QObject *parent); + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QFont fixed_font; + int byte_width; +}; + +inline QString toHex(const QByteArray &dat) { return dat.toHex(' ').toUpper(); } +QString toHex(uint8_t byte); +QColor getColor(const cabana::Signal *sig); + +class NameValidator : public QRegExpValidator { + Q_OBJECT + +public: + NameValidator(QObject *parent=nullptr); + QValidator::State validate(QString &input, int &pos) const override; +}; + +namespace utils { +QPixmap icon(const QString &id); +} + +QToolButton *toolButton(const QString &icon, const QString &tooltip); +int num_decimals(double num); diff --git a/tools/cabana/videowidget.cc b/tools/cabana/videowidget.cc index 78965e3e8c..e36e62131f 100644 --- a/tools/cabana/videowidget.cc +++ b/tools/cabana/videowidget.cc @@ -1,51 +1,55 @@ #include "tools/cabana/videowidget.h" +#include #include #include -#include #include #include +#include #include -#include +#include +#include #include +#include -inline QString formatTime(int seconds) { +const int MIN_VIDEO_HEIGHT = 100; + +static const QColor timeline_colors[] = { + [(int)TimelineType::None] = QColor(111, 143, 175), + [(int)TimelineType::Engaged] = QColor(0, 163, 108), + [(int)TimelineType::UserFlag] = Qt::magenta, + [(int)TimelineType::AlertInfo] = Qt::green, + [(int)TimelineType::AlertWarning] = QColor(255, 195, 0), + [(int)TimelineType::AlertCritical] = QColor(199, 0, 57), +}; + +static inline QString formatTime(int seconds) { return QDateTime::fromTime_t(seconds).toString(seconds > 60 * 60 ? "hh:mm:ss" : "mm:ss"); } VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { QVBoxLayout *main_layout = new QVBoxLayout(this); main_layout->setContentsMargins(0, 0, 0, 0); + QFrame *frame = new QFrame(this); + frame->setFrameStyle(QFrame::StyledPanel | QFrame::Plain); + main_layout->addWidget(frame); - // TODO: figure out why the CameraWidget crashed occasionally. - cam_widget = new CameraWidget("camerad", VISION_STREAM_ROAD, false, this); - cam_widget->setFixedSize(parent->width(), parent->width() / 1.596); - main_layout->addWidget(cam_widget); - - // slider controls - QHBoxLayout *slider_layout = new QHBoxLayout(); - QLabel *time_label = new QLabel("00:00"); - slider_layout->addWidget(time_label); - - slider = new Slider(this); - slider->setSingleStep(0); - slider->setMinimum(0); - slider->setMaximum(can->totalSeconds() * 1000); - slider_layout->addWidget(slider); - - end_time_label = new QLabel(formatTime(can->totalSeconds())); - slider_layout->addWidget(end_time_label); - main_layout->addLayout(slider_layout); + QVBoxLayout *frame_layout = new QVBoxLayout(frame); + if (!can->liveStreaming()) { + frame_layout->addWidget(createCameraWidget()); + } // btn controls QHBoxLayout *control_layout = new QHBoxLayout(); - play_btn = new QPushButton("⏸"); - play_btn->setStyleSheet("font-weight:bold"); + play_btn = new QPushButton(); + play_btn->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); control_layout->addWidget(play_btn); QButtonGroup *group = new QButtonGroup(this); group->setExclusive(true); for (float speed : {0.1, 0.5, 1., 2.}) { + if (can->liveStreaming() && speed > 1) continue; + QPushButton *btn = new QPushButton(QString("%1x").arg(speed), this); btn->setCheckable(true); QObject::connect(btn, &QPushButton::clicked, [=]() { can->setSpeed(speed); }); @@ -53,31 +57,101 @@ VideoWidget::VideoWidget(QWidget *parent) : QWidget(parent) { group->addButton(btn); if (speed == 1.0) btn->setChecked(true); } - main_layout->addLayout(control_layout); + frame_layout->addLayout(control_layout); - setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + QObject::connect(play_btn, &QPushButton::clicked, []() { can->pause(!can->isPaused()); }); + QObject::connect(can, &AbstractStream::paused, this, &VideoWidget::updatePlayBtnState); + QObject::connect(can, &AbstractStream::resume, this, &VideoWidget::updatePlayBtnState); + updatePlayBtnState(); - QObject::connect(can, &CANMessages::rangeChanged, this, &VideoWidget::rangeChanged); - QObject::connect(can, &CANMessages::updated, this, &VideoWidget::updateState); + setWhatsThis(tr(R"( + Video
+ + Timeline color + + + + + + + +
Disengaged Engaged
User Flag Info
Warning Critical
+ Shortcuts
+ Pause/Resume:  space  + )").arg(timeline_colors[(int)TimelineType::None].name(), + timeline_colors[(int)TimelineType::Engaged].name(), + timeline_colors[(int)TimelineType::UserFlag].name(), + timeline_colors[(int)TimelineType::AlertInfo].name(), + timeline_colors[(int)TimelineType::AlertWarning].name(), + timeline_colors[(int)TimelineType::AlertCritical].name())); +} + +QWidget *VideoWidget::createCameraWidget() { + QWidget *w = new QWidget(this); + QVBoxLayout *l = new QVBoxLayout(w); + l->setContentsMargins(0, 0, 0, 0); + cam_widget = new CameraWidget("camerad", can->visionStreamType(), false); + l->addWidget(cam_widget); + + // slider controls + slider_layout = new QHBoxLayout(); + time_label = new ElidedLabel("00:00"); + time_label->setToolTip(tr("Click to set current time")); + slider_layout->addWidget(time_label); + + slider = new Slider(this); + slider->setSingleStep(0); + slider_layout->addWidget(slider); + + end_time_label = new QLabel(this); + slider_layout->addWidget(end_time_label); + l->addLayout(slider_layout); + + cam_widget->setMinimumHeight(MIN_VIDEO_HEIGHT); + cam_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum); + + QObject::connect(time_label, &ElidedLabel::clicked, this, &VideoWidget::timeLabelClicked); QObject::connect(slider, &QSlider::sliderReleased, [this]() { can->seekTo(slider->value() / 1000.0); }); QObject::connect(slider, &QSlider::valueChanged, [=](int value) { time_label->setText(formatTime(value / 1000)); }); - QObject::connect(cam_widget, &CameraWidget::clicked, [this]() { pause(!can->isPaused()); }); - QObject::connect(play_btn, &QPushButton::clicked, [=]() { pause(!can->isPaused()); }); + QObject::connect(cam_widget, &CameraWidget::clicked, []() { can->pause(!can->isPaused()); }); + QObject::connect(can, &AbstractStream::updated, this, &VideoWidget::updateState); + QObject::connect(can, &AbstractStream::streamStarted, [this]() { + end_time_label->setText(formatTime(can->totalSeconds())); + slider->setRange(0, can->totalSeconds() * 1000); + }); + return w; } -void VideoWidget::pause(bool pause) { - play_btn->setText(!pause ? "⏸" : "▶"); - can->pause(pause); +void VideoWidget::timeLabelClicked() { + auto time_edit = new QTimeEdit(this); + auto init_date_time = can->currentDateTime(); + time_edit->setDateTime(init_date_time); + time_edit->setDisplayFormat("hh:mm:ss"); + time_label->setVisible(false); + slider_layout->insertWidget(0, time_edit); + QTimer::singleShot(0, [=]() { time_edit->setFocus(); }); + + QObject::connect(time_edit, &QTimeEdit::editingFinished, [=]() { + if (time_edit->dateTime() != init_date_time) { + int seconds = can->route()->datetime().secsTo(time_edit->dateTime()); + can->seekTo(seconds); + } + time_edit->setVisible(false); + time_label->setVisible(true); + time_edit->deleteLater(); + }); } -void VideoWidget::rangeChanged(double min, double max) { - if (!can->isZoomed()) { +void VideoWidget::rangeChanged(double min, double max, bool is_zoomed) { + if (can->liveStreaming()) return; + + if (!is_zoomed) { min = 0; max = can->totalSeconds(); } end_time_label->setText(formatTime(max)); - slider->setMinimum(min * 1000); - slider->setMaximum(max * 1000); + slider->setRange(min * 1000, max * 1000); } void VideoWidget::updateState() { @@ -85,15 +159,66 @@ void VideoWidget::updateState() { slider->setValue(can->currentSec() * 1000); } +void VideoWidget::updatePlayBtnState() { + play_btn->setIcon(utils::icon(can->isPaused() ? "play" : "pause")); + play_btn->setToolTip(can->isPaused() ? tr("Play") : tr("Pause")); +} + // Slider -Slider::Slider(QWidget *parent) : QSlider(Qt::Horizontal, parent) { - QTimer *timer = new QTimer(this); - timer->setInterval(2000); - timer->callOnTimeout([this]() { +Slider::Slider(QWidget *parent) : timer(this), QSlider(Qt::Horizontal, parent) { + timer.callOnTimeout([this]() { timeline = can->getTimeline(); update(); }); - timer->start(); + setMouseTracking(true); + QObject::connect(can, &AbstractStream::streamStarted, this, &Slider::streamStarted); +} + +Slider::~Slider() { + abort_load_thumbnail = true; + thumnail_future.waitForFinished(); +} + +void Slider::streamStarted() { + abort_load_thumbnail = true; + thumnail_future.waitForFinished(); + abort_load_thumbnail = false; + thumbnails.clear(); + timeline.clear(); + timer.start(2000); + thumnail_future = QtConcurrent::run(this, &Slider::loadThumbnails); +} + +void Slider::loadThumbnails() { + const auto &segments = can->route()->segments(); + for (auto it = segments.rbegin(); it != segments.rend() && !abort_load_thumbnail; ++it) { + std::string qlog = it->second.qlog.toStdString(); + if (!qlog.empty()) { + LogReader log; + if (log.load(qlog, &abort_load_thumbnail, {cereal::Event::Which::THUMBNAIL}, true, 0, 3)) { + for (auto ev = log.events.cbegin(); ev != log.events.cend() && !abort_load_thumbnail; ++ev) { + auto thumb = (*ev)->event.getThumbnail(); + QString str = getThumbnailString(thumb.getThumbnail()); + std::lock_guard lk(thumbnail_lock); + thumbnails[thumb.getTimestampEof()] = str; + } + } + } + } +} + +QString Slider::getThumbnailString(const capnp::Data::Reader &data) { + QPixmap thumb; + if (thumb.loadFromData(data.begin(), data.size(), "jpeg")) { + thumb = thumb.scaled({thumb.width()/3, thumb.height()/3}, Qt::KeepAspectRatio); + thumbnail_size = thumb.size(); + QByteArray bytes; + QBuffer buffer(&bytes); + buffer.open(QIODevice::WriteOnly); + thumb.save(&buffer, "png"); + return QString("").arg(QString(bytes.toBase64())); + } + return {}; } void Slider::sliderChange(QAbstractSlider::SliderChange change) { @@ -109,17 +234,9 @@ void Slider::sliderChange(QAbstractSlider::SliderChange change) { } void Slider::paintEvent(QPaintEvent *ev) { - static const QColor colors[] = { - [(int)TimelineType::None] = QColor(111, 143, 175), - [(int)TimelineType::Engaged] = QColor(0, 163, 108), - [(int)TimelineType::UserFlag] = Qt::white, - [(int)TimelineType::AlertInfo] = Qt::green, - [(int)TimelineType::AlertWarning] = QColor(255, 195, 0), - [(int)TimelineType::AlertCritical] = QColor(199, 0, 57)}; - QPainter p(this); QRect r = rect().adjusted(0, 4, 0, -4); - p.fillRect(r, colors[(int)TimelineType::None]); + p.fillRect(r, timeline_colors[(int)TimelineType::None]); double min = minimum() / 1000.0; double max = maximum() / 1000.0; for (auto [begin, end, type] : timeline) { @@ -127,7 +244,7 @@ void Slider::paintEvent(QPaintEvent *ev) { continue; r.setLeft(((std::max(min, (double)begin) - min) / (max - min)) * width()); r.setRight(((std::min(max, (double)end) - min) / (max - min)) * width()); - p.fillRect(r, colors[(int)type]); + p.fillRect(r, timeline_colors[(int)type]); } QStyleOptionSlider opt; @@ -147,3 +264,23 @@ void Slider::mousePressEvent(QMouseEvent *e) { emit sliderReleased(); } } + +void Slider::mouseMoveEvent(QMouseEvent *e) { + QString thumb; + { + double seconds = (minimum() + e->pos().x() * ((maximum() - minimum()) / (double)width())) / 1000.0; + std::lock_guard lk(thumbnail_lock); + auto it = thumbnails.lowerBound((seconds + can->routeStartTime()) * 1e9); + if (it != thumbnails.end()) { + thumb = it.value(); + } + } + QPoint pt = mapToGlobal({e->pos().x() - thumbnail_size.width() / 2, -thumbnail_size.height() - 30}); + QToolTip::showText(pt, thumb, this, rect()); + QSlider::mouseMoveEvent(e); +} + +void Slider::leaveEvent(QEvent *event) { + QToolTip::hideText(); + QSlider::leaveEvent(event); +} diff --git a/tools/cabana/videowidget.h b/tools/cabana/videowidget.h index d6d036c461..a5ce8c345f 100644 --- a/tools/cabana/videowidget.h +++ b/tools/cabana/videowidget.h @@ -1,24 +1,45 @@ #pragma once +#include +#include + +#include +#include #include #include #include -#include +#include #include "selfdrive/ui/qt/widgets/cameraview.h" -#include "tools/cabana/canmessages.h" +#include "selfdrive/ui/qt/widgets/controls.h" +#include "tools/cabana/dbcmanager.h" +#include "tools/cabana/streams/abstractstream.h" class Slider : public QSlider { Q_OBJECT public: Slider(QWidget *parent); + ~Slider(); + +private: void mousePressEvent(QMouseEvent *e) override; + void mouseMoveEvent(QMouseEvent *e) override; + void leaveEvent(QEvent *event) override; void sliderChange(QAbstractSlider::SliderChange change) override; void paintEvent(QPaintEvent *ev) override; + void streamStarted(); + void loadThumbnails(); + QString getThumbnailString(const capnp::Data::Reader &data); int slider_x = -1; std::vector> timeline; + std::mutex thumbnail_lock; + std::atomic abort_load_thumbnail = false; + QMap thumbnails; + QFuture thumnail_future; + QSize thumbnail_size = {}; + QTimer timer; }; class VideoWidget : public QWidget { @@ -26,14 +47,18 @@ class VideoWidget : public QWidget { public: VideoWidget(QWidget *parnet = nullptr); + void rangeChanged(double min, double max, bool is_zommed); protected: - void rangeChanged(double min, double max); void updateState(); - void pause(bool pause); + void updatePlayBtnState(); + void timeLabelClicked(); + QWidget *createCameraWidget(); CameraWidget *cam_widget; QLabel *end_time_label; + ElidedLabel *time_label; + QHBoxLayout *slider_layout; QPushButton *play_btn; Slider *slider; }; diff --git a/tools/camerastream/compressed_vipc.py b/tools/camerastream/compressed_vipc.py index 42a416985e..cab11493f2 100755 --- a/tools/camerastream/compressed_vipc.py +++ b/tools/camerastream/compressed_vipc.py @@ -91,9 +91,14 @@ def main(addr, cams, nvidia=False): vipc_server.create_buffers(vst, 4, False, W, H) vipc_server.start_listener() + procs = [] for k, v in cams.items(): - multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)).start() + p = multiprocessing.Process(target=decoder, args=(addr, k, vipc_server, v, nvidia)) + p.start() + procs.append(p) + for p in procs: + p.join() if __name__ == "__main__": parser = argparse.ArgumentParser(description="Decode video streams and broadcast on VisionIPC") diff --git a/tools/gpstest/.gitignore b/tools/gpstest/.gitignore index f11597286e..992088ef34 100644 --- a/tools/gpstest/.gitignore +++ b/tools/gpstest/.gitignore @@ -1,2 +1,4 @@ LimeGPS/ -LimeSuite/ \ No newline at end of file +LimeSuite/ +hackrf/ +gps-sdr-sim/ diff --git a/tools/gpstest/README.md b/tools/gpstest/README.md index 5aff0ee3d7..01f44df0ce 100644 --- a/tools/gpstest/README.md +++ b/tools/gpstest/README.md @@ -3,12 +3,9 @@ Testing the GPS receiver using GPS spoofing. At the moment only static location relpay is supported. # Usage -``` -# on host, start gps signal simulation -./run_static_lime.py -``` +on C3 run `rpc_server.py`, on host PC run `fuzzy_testing.py` -`run_static_lime.py` downloads the latest ephemeris file from +`simulate_gps_signal.py` downloads the latest ephemeris file from https://cddis.nasa.gov/archive/gnss/data/daily/20xx/brdc/. diff --git a/tools/gpstest/fuzzy_testing.py b/tools/gpstest/fuzzy_testing.py index 216e7d0dde..a2e130342c 100755 --- a/tools/gpstest/fuzzy_testing.py +++ b/tools/gpstest/fuzzy_testing.py @@ -1,142 +1,115 @@ #!/usr/bin/env python3 -import sys -import time -import random -import datetime as dt -import subprocess as sp +import argparse import multiprocessing -import threading -from typing import Tuple, Any +import rpyc # pylint: disable=import-error +from collections import defaultdict -from laika.downloader import download_nav -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId +from helper import download_rinex, exec_LimeGPS_bin +from helper import get_random_coords, get_continuous_coords -cache_dir = '/tmp/gpstest/' +#------------------------------------------------------------------------------ +# this script is supposed to run on HOST PC +# limeSDR is unreliable via c3 USB +#------------------------------------------------------------------------------ -def download_rinex(): - # TODO: check if there is a better way to get the full brdc file for LimeGPS - gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) - utc_time = dt.datetime.utcnow() - dt.timedelta(1) - gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) - return download_nav(gps_time, cache_dir, ConstellationId.GPS) - - -def exec_LimeGPS_bin(rinex_file: str, location: str, duration: int): - # this functions should never return, cause return means, timeout is - # reached or it crashed - try: - cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", location] - sp.check_output(cmd, timeout=duration) - except sp.TimeoutExpired: - print("LimeGPS timeout reached!") - except Exception as e: - print(f"LimeGPS crashed: {str(e)}") - - -def run_lime_gps(rinex_file: str, location: str, duration: int): - print(f"LimeGPS {location} {duration}") - +def run_lime_gps(rinex_file: str, location: str, timeout: int): + # needs to run longer than the checker + timeout += 10 + print(f"LimeGPS {location} {timeout}") p = multiprocessing.Process(target=exec_LimeGPS_bin, - args=(rinex_file, location, duration)) + args=(rinex_file, location, timeout)) p.start() return p +con = None +def run_remote_checker(lat, lon, alt, duration, ip_addr): + global con + try: + con = rpyc.connect(ip_addr, 18861) + con._config['sync_request_timeout'] = duration+20 + except ConnectionRefusedError: + print("could not run remote checker is 'rpc_server.py' running???") + return False, None, None -def get_random_coords(lat, lon) -> Tuple[int, int]: - # jump around the world - # max values, lat: -90 to 90, lon: -180 to 180 + matched, log, info = con.root.exposed_run_checker(lat, lon, alt, + timeout=duration, + use_laikad=True) + con.close() # TODO: might wanna fetch more logs here + con = None - lat_add = random.random()*20 + 10 - lon_add = random.random()*20 + 20 - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - return round(lat, 5), round(lon, 5) - -def get_continuous_coords(lat, lon) -> Tuple[int, int]: - # continuously move around the world - - lat_add = random.random()*0.01 - lon_add = random.random()*0.01 - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - return round(lat, 5), round(lon, 5) - -rc_p: Any = None -def exec_remote_checker(lat, lon, duration): - global rc_p - # TODO: good enough for testing - remote_cmd = "export PYTHONPATH=/data/pythonpath:/data/pythonpath/pyextra && " - remote_cmd += "cd /data/openpilot && " - remote_cmd += f"timeout {duration} /usr/local/pyenv/shims/python tools/gpstest/remote_checker.py " - remote_cmd += f"{lat} {lon}" - - ssh_cmd = ['ssh', '-i', '/home/batman/openpilot/xx/phone/key/id_rsa', - 'comma@192.168.60.130'] - ssh_cmd += [remote_cmd] - - rc_p = sp.Popen(ssh_cmd, stdout=sp.PIPE) - rc_p.wait() - rc_output = rc_p.stdout.read() - print(f"Checker Result: {rc_output.strip().decode('utf-8')}") + print(f"Remote Checker: {log} {info}") + return matched, log, info -def run_remote_checker(spoof_proc, lat, lon, duration) -> bool: - checker_thread = threading.Thread(target=exec_remote_checker, args=(lat, lon, duration)) - checker_thread.start() +stats = defaultdict(int) # type: ignore +keys = ['success', 'failed', 'ublox_fail', 'laikad_fail', 'proc_crash', 'checker_crash'] - tcnt = 0 - while True: - if not checker_thread.is_alive(): - # assume this only happens when the signal got matched - return True - - # the spoofing process has a timeout, kill checker if reached - if not spoof_proc.is_alive(): - rc_p.kill() - # spoofing process died, assume timeout - print("Spoofing process timeout") - return False - - print(f"Time elapsed: {tcnt}[s]", end = "\r") - time.sleep(1) - tcnt += 1 +def print_report(): + print("\nFuzzy testing report summary:") + for k in keys: + print(f" {k}: {stats[k]}") -def main(): - continuous_mode = False - if len(sys.argv) == 2 and sys.argv[1] == '-c': - print("Continuous Mode!") - continuous_mode = True +def update_stats(matched, log, info): + if matched: + stats['success'] += 1 + return + stats['failed'] += 1 + if log == "PROC CRASH": + stats['proc_crash'] += 1 + if log == "CHECKER CRASHED": + stats['checker_crash'] += 1 + if log == "TIMEOUT": + if "LAIKAD" in info: + stats['laikad_fail'] += 1 + else: # "UBLOX" in info + stats['ublox_fail'] += 1 + + +def main(ip_addr, continuous_mode, timeout, pos): rinex_file = download_rinex() - duration = 60*3 # max runtime in seconds - lat, lon = get_random_coords(47.2020, 15.7403) + lat, lon, alt = pos + if lat == 0 and lon == 0 and alt == 0: + lat, lon, alt = get_random_coords(47.2020, 15.7403) - while True: - # spoof random location - spoof_proc = run_lime_gps(rinex_file, f"{lat},{lon},100", duration) - start_time = time.monotonic() + try: + while True: + # spoof random location + spoof_proc = run_lime_gps(rinex_file, f"{lat},{lon},{alt}", timeout) - # remote checker runs blocking - if not run_remote_checker(spoof_proc, lat, lon, duration): - # location could not be matched by ublox module - pass + # remote checker execs blocking + matched, log, info = run_remote_checker(lat, lon, alt, timeout, ip_addr) + update_stats(matched, log, info) + spoof_proc.terminate() + spoof_proc = None - end_time = time.monotonic() - spoof_proc.terminate() + if continuous_mode: + lat, lon, alt = get_continuous_coords(lat, lon, alt) + else: + lat, lon, alt = get_random_coords(lat, lon) + except KeyboardInterrupt: + if spoof_proc is not None: + spoof_proc.terminate() - # -1 to count process startup - print(f"Time to get Signal: {round(end_time - start_time - 1, 4)}") + if con is not None and not con.closed: + con.root.exposed_kill_procs() + con.close() + + print_report() - if continuous_mode: - lat, lon = get_continuous_coords(lat, lon) - else: - lat, lon = get_random_coords(lat, lon) if __name__ == "__main__": - main() + parser = argparse.ArgumentParser(description="Fuzzy test GPS stack with random locations.") + parser.add_argument("ip_addr", type=str) + parser.add_argument("-c", "--contin", type=bool, nargs='?', default=False, help='Continous location change') + parser.add_argument("-t", "--timeout", type=int, nargs='?', default=180, help='Timeout to get location') + + # for replaying a location + parser.add_argument("lat", type=float, nargs='?', default=0) + parser.add_argument("lon", type=float, nargs='?', default=0) + parser.add_argument("alt", type=float, nargs='?', default=0) + args = parser.parse_args() + main(args.ip_addr, args.contin, args.timeout, (args.lat, args.lon, args.alt)) diff --git a/tools/gpstest/helper.py b/tools/gpstest/helper.py new file mode 100644 index 0000000000..4f62e60db0 --- /dev/null +++ b/tools/gpstest/helper.py @@ -0,0 +1,53 @@ +import random +import datetime as dt +import subprocess as sp +from typing import Tuple + +from laika.downloader import download_nav +from laika.gps_time import GPSTime +from laika.helpers import ConstellationId + + +def download_rinex(): + # TODO: check if there is a better way to get the full brdc file for LimeGPS + gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) + utc_time = dt.datetime.utcnow() - dt.timedelta(1) + gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) + return download_nav(gps_time, '/tmp/gpstest/', ConstellationId.GPS) + + +def exec_LimeGPS_bin(rinex_file: str, location: str, duration: int): + # this functions should never return, cause return means, timeout is + # reached or it crashed + try: + cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", location] + sp.check_output(cmd, timeout=duration) + except sp.TimeoutExpired: + print("LimeGPS timeout reached!") + except Exception as e: + print(f"LimeGPS crashed: {str(e)}") + + +def get_random_coords(lat, lon) -> Tuple[float, float, int]: + # jump around the world + # max values, lat: -90 to 90, lon: -180 to 180 + + lat_add = random.random()*20 + 10 + lon_add = random.random()*20 + 20 + alt = random.randint(-10**3, 4*10**3) + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + return round(lat, 5), round(lon, 5), alt + + +def get_continuous_coords(lat, lon, alt) -> Tuple[float, float, int]: + # continuously move around the world + lat_add = random.random()*0.01 + lon_add = random.random()*0.01 + alt_add = random.randint(-100, 100) + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + alt += alt_add + return round(lat, 5), round(lon, 5), alt diff --git a/tools/gpstest/patches/hackrf.patch b/tools/gpstest/patches/hackrf.patch new file mode 100644 index 0000000000..afc9ac437b --- /dev/null +++ b/tools/gpstest/patches/hackrf.patch @@ -0,0 +1,44 @@ +diff --git a/host/hackrf-tools/src/CMakeLists.txt b/host/hackrf-tools/src/CMakeLists.txt +index 7115151c..a51388ba 100644 +--- a/host/hackrf-tools/src/CMakeLists.txt ++++ b/host/hackrf-tools/src/CMakeLists.txt +@@ -23,20 +23,20 @@ + + set(INSTALL_DEFAULT_BINDIR "bin" CACHE STRING "Appended to CMAKE_INSTALL_PREFIX") + +-find_package(FFTW REQUIRED) +-include_directories(${FFTW_INCLUDES}) +-get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) +-link_directories(${FFTW_LIBRARY_DIRS}) ++#find_package(FFTW REQUIRED) ++#include_directories(${FFTW_INCLUDES}) ++#get_filename_component(FFTW_LIBRARY_DIRS ${FFTW_LIBRARIES} DIRECTORY) ++#link_directories(${FFTW_LIBRARY_DIRS}) + + SET(TOOLS + hackrf_transfer +- hackrf_spiflash +- hackrf_cpldjtag ++ #hackrf_spiflash ++ #hackrf_cpldjtag + hackrf_info +- hackrf_debug +- hackrf_clock +- hackrf_sweep +- hackrf_operacake ++ #hackrf_debug ++ #hackrf_clock ++ #hackrf_sweep ++ #hackrf_operacake + ) + + if(MSVC) +@@ -45,7 +45,7 @@ if(MSVC) + ) + LIST(APPEND TOOLS_LINK_LIBS ${FFTW_LIBRARIES}) + else() +- LIST(APPEND TOOLS_LINK_LIBS m fftw3f) ++ LIST(APPEND TOOLS_LINK_LIBS m)# fftw3f) + endif() + + if(NOT libhackrf_SOURCE_DIR) diff --git a/tools/gpstest/remote_checker.py b/tools/gpstest/remote_checker.py deleted file mode 100644 index 84f6c0c3d9..0000000000 --- a/tools/gpstest/remote_checker.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import sys -import time -from typing import List - -import cereal.messaging as messaging -from selfdrive.manager.process_config import managed_processes - -DELTA = 0.001 -# assume running openpilot for now -procs: List[str] = []#"ubloxd", "pigeond"] - - -def main(): - if len(sys.argv) < 3: - print("args: ") - return - - sol_lat = float(sys.argv[1]) - sol_lon = float(sys.argv[2]) - - for p in procs: - managed_processes[p].start() - time.sleep(0.5) # give time to startup - - gps_sock = messaging.sub_sock('gpsLocationExternal', timeout=0.1) - - # analyze until the location changed - while True: - events = messaging.drain_sock(gps_sock) - for e in events: - lat = e.gpsLocationExternal.latitude - lon = e.gpsLocationExternal.longitude - - if abs(lat - sol_lat) < DELTA and abs(lon - sol_lon) < DELTA: - print("MATCH") - return - - for p in procs: - if not managed_processes[p].proc.is_alive(): - print(f"ERROR: '{p}' died") - return - - -if __name__ == "__main__": - main() - for p in procs: - managed_processes[p].stop() diff --git a/tools/gpstest/rpc_server.py b/tools/gpstest/rpc_server.py new file mode 100644 index 0000000000..178e8b2c3c --- /dev/null +++ b/tools/gpstest/rpc_server.py @@ -0,0 +1,185 @@ +import os +import time +import shutil +from datetime import datetime +from collections import defaultdict + +import rpyc # pylint: disable=import-error +from rpyc.utils.server import ThreadedServer # pylint: disable=import-error + +#from common.params import Params +import cereal.messaging as messaging +from selfdrive.manager.process_config import managed_processes +from laika.lib.coordinates import ecef2geodetic + +DELTA = 0.001 +ALT_DELTA = 30 +MATCH_NUM = 10 +REPORT_STATS = 10 + +EPHEM_CACHE = "/data/params/d/LaikadEphemerisV3" +DOWNLOAD_CACHE = "/tmp/comma_download_cache" + +SERVER_LOG_FILE = "/tmp/fuzzy_server.log" +server_log = open(SERVER_LOG_FILE, "w+") + +def slog(msg): + server_log.write(f"{datetime.now().strftime('%H:%M:%S.%f')} | {msg}\n") + server_log.flush() + +def handle_laikad(msg): + if not hasattr(msg, 'correctedMeasurements'): + return None + + num_corr = len(msg.correctedMeasurements) + pos_ecef = msg.positionECEF.value + pos_geo = [] + if len(pos_ecef) > 0: + pos_geo = ecef2geodetic(pos_ecef) + + pos_std = msg.positionECEF.std + pos_valid = msg.positionECEF.valid + + slog(f"{num_corr} {pos_geo} {pos_ecef} {pos_std} {pos_valid}") + return pos_geo, (num_corr, pos_geo, list(pos_ecef), list(msg.positionECEF.std)) + +hw_msgs = 0 +ephem_msgs: dict = defaultdict(int) +def handle_ublox(msg): + global hw_msgs + + d = msg.to_dict() + + if 'hwStatus2' in d: + hw_msgs += 1 + + if 'ephemeris' in d: + ephem_msgs[msg.ephemeris.svId] += 1 + + num_meas = None + if 'measurementReport' in d: + num_meas = msg.measurementReport.numMeas + + return [hw_msgs, ephem_msgs, num_meas] + + +def start_procs(procs): + for p in procs: + managed_processes[p].start() + time.sleep(1) + +def kill_procs(procs, no_retry=False): + for p in procs: + managed_processes[p].stop() + time.sleep(1) + + if not no_retry: + for p in procs: + mp = managed_processes[p].proc + if mp is not None and mp.is_alive(): + managed_processes[p].stop() + time.sleep(3) + +def check_alive_procs(procs): + for p in procs: + mp = managed_processes[p].proc + if mp is None or not mp.is_alive(): + return False, p + return True, None + + +class RemoteCheckerService(rpyc.Service): + def on_connect(self, conn): + pass + + def on_disconnect(self, conn): + #kill_procs(self.procs, no_retry=False) + # this execution is delayed, it will kill the next run of laikad + # TODO: add polling to wait for everything is killed + pass + + def run_checker(self, slat, slon, salt, sockets, procs, timeout): + global hw_msgs, ephem_msgs + hw_msgs = 0 + ephem_msgs = defaultdict(int) + + slog(f"Run test: {slat} {slon} {salt}") + + # quectel_mod = Params().get_bool("UbloxAvailable") + + match_cnt = 0 + msg_cnt = 0 + stats_laikad = [] + stats_ublox = [] + + self.procs = procs + start_procs(procs) + sm = messaging.SubMaster(sockets) + + start_time = time.monotonic() + while True: + sm.update() + + if sm.updated['ubloxGnss']: + stats_ublox.append(handle_ublox(sm['ubloxGnss'])) + + if sm.updated['gnssMeasurements']: + pos_geo, stats = handle_laikad(sm['gnssMeasurements']) + if pos_geo is None or len(pos_geo) == 0: + continue + + match = all(abs(g-s) < DELTA for g,s in zip(pos_geo[:2], [slat, slon])) + match &= abs(pos_geo[2] - salt) < ALT_DELTA + if match: + match_cnt += 1 + if match_cnt >= MATCH_NUM: + return True, "MATCH", f"After: {round(time.monotonic() - start_time, 4)}" + + # keep some stats for error reporting + stats_laikad.append(stats) + + if (msg_cnt % 10) == 0: + a, p = check_alive_procs(procs) + if not a: + return False, "PROC CRASH", f"{p}" + msg_cnt += 1 + + if (time.monotonic() - start_time) > timeout: + h = f"LAIKAD: {stats_laikad[-REPORT_STATS:]}" + if len(h) == 0: + h = f"UBLOX: {stats_ublox[-REPORT_STATS:]}" + return False, "TIMEOUT", h + + + def exposed_run_checker(self, slat, slon, salt, timeout=180, use_laikad=True): + try: + procs = [] + sockets = [] + + if use_laikad: + procs.append("laikad") # pigeond, ubloxd # might wanna keep them running + sockets += ['ubloxGnss', 'gnssMeasurements'] + + if os.path.exists(EPHEM_CACHE): + os.remove(EPHEM_CACHE) + shutil.rmtree(DOWNLOAD_CACHE, ignore_errors=True) + + ret = self.run_checker(slat, slon, salt, sockets, procs, timeout) + kill_procs(procs) + return ret + + except Exception as e: + # always make sure processes get killed + kill_procs(procs) + return False, "CHECKER CRASHED", f"{str(e)}" + + + def exposed_kill_procs(self): + kill_procs(self.procs, no_retry=True) + + +if __name__ == "__main__": + print(f"Sever Log written to: {SERVER_LOG_FILE}") + t = ThreadedServer(RemoteCheckerService, port=18861) + t.start() + diff --git a/tools/gpstest/run_static_gps_signal.py b/tools/gpstest/run_static_gps_signal.py deleted file mode 100755 index 3787038f13..0000000000 --- a/tools/gpstest/run_static_gps_signal.py +++ /dev/null @@ -1,83 +0,0 @@ -#!/usr/bin/env python3 -import os -import sys -import random -import datetime as dt -import subprocess as sp -from typing import Tuple - -from laika.downloader import download_nav -from laika.gps_time import GPSTime -from laika.helpers import ConstellationId - -cache_dir = '/tmp/gpstest/' - - -def download_rinex(): - # TODO: check if there is a better way to get the full brdc file for LimeGPS - gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) - utc_time = dt.datetime.utcnow() - dt.timedelta(1) - gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) - return download_nav(gps_time, cache_dir, ConstellationId.GPS) - - -def get_random_coords(lat, lon) -> Tuple[int, int]: - # jump around the world - # max values, lat: -90 to 90, lon: -180 to 180 - - lat_add = random.random()*20 + 10 - lon_add = random.random()*20 + 20 - - lat = ((lat + lat_add + 90) % 180) - 90 - lon = ((lon + lon_add + 180) % 360) - 180 - return round(lat, 5), round(lon, 5) - - -def check_availability() -> bool: - cmd = ["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"] - output = sp.check_output(cmd) - - if output.strip() == b"": - return False - - print(f"Device: {output.strip().decode('utf-8')}") - return True - - -def main(): - if not os.path.exists('LimeGPS'): - print("LimeGPS not found run 'setup.sh' first") - return - - if not os.path.exists('LimeSuite'): - print("LimeSuite not found run 'setup.sh' first") - return - - if not check_availability(): - print("No limeSDR device found!") - return - - rinex_file = download_rinex() - lat, lon = get_random_coords(47.2020, 15.7403) - - if len(sys.argv) == 3: - lat = float(sys.argv[1]) - lon = float(sys.argv[2]) - - try: - print(f"starting LimeGPS, Location: {lat},{lon}") - cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", f"{lat},{lon},100"] - sp.check_output(cmd, stderr=sp.PIPE) - except KeyboardInterrupt: - print("stopping LimeGPS") - except Exception as e: - out_stderr = e.stderr.decode('utf-8')# pylint:disable=no-member - if "Device is busy." in out_stderr: - print("GPS simulation is already running, Device is busy!") - return - - print(f"LimeGPS crashed: {str(e)}") - print(f"stderr:\n{e.stderr.decode('utf-8')}")# pylint:disable=no-member - -if __name__ == "__main__": - main() diff --git a/tools/gpstest/run_unittest.sh b/tools/gpstest/run_unittest.sh new file mode 100755 index 0000000000..e0ca017a6d --- /dev/null +++ b/tools/gpstest/run_unittest.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +# NOTE: can only run inside limeGPS test box! + +# run limeGPS with random static location +timeout 300 ./simulate_gps_signal.py 32.7518 -117.1962 & +gps_PID=$(ps -aux | grep -m 1 "timeout 300" | awk '{print $2}') + +echo "starting limeGPS..." +sleep 10 + +# run unit tests (skipped when module not present) +python -m unittest test_gps.py +python -m unittest test_gps_qcom.py + +kill $gps_PID diff --git a/tools/gpstest/setup_hackrf.sh b/tools/gpstest/setup_hackrf.sh new file mode 100755 index 0000000000..e504ec9447 --- /dev/null +++ b/tools/gpstest/setup_hackrf.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -e + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +cd $DIR + +if [ ! -d gps-sdr-sim ]; then + git clone https://github.com/osqzss/gps-sdr-sim.git + cd gps-sdr-sim + make + cd .. +fi + +if [ ! -d hackrf ]; then + git clone https://github.com/greatscottgadgets/hackrf.git + cd hackrf/host + git apply ../../patches/hackrf.patch + cmake . + make +fi + diff --git a/tools/gpstest/simulate_gps_signal.py b/tools/gpstest/simulate_gps_signal.py new file mode 100755 index 0000000000..da0f64eaca --- /dev/null +++ b/tools/gpstest/simulate_gps_signal.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +import os +import random +import argparse +import datetime as dt +import subprocess as sp +from typing import Tuple + +from laika.downloader import download_nav +from laika.gps_time import GPSTime +from laika.helpers import ConstellationId + +cache_dir = '/tmp/gpstest/' + + +def download_rinex(): + # TODO: check if there is a better way to get the full brdc file for LimeGPS + gps_time = GPSTime.from_datetime(dt.datetime.utcnow()) + utc_time = dt.datetime.utcnow()# - dt.timedelta(1) + gps_time = GPSTime.from_datetime(dt.datetime(utc_time.year, utc_time.month, utc_time.day)) + return download_nav(gps_time, cache_dir, ConstellationId.GPS) + +def get_coords(lat, lon, s1, s2, o1=0, o2=0) -> Tuple[int, int]: + lat_add = random.random()*s1 + o1 + lon_add = random.random()*s2 + o2 + + lat = ((lat + lat_add + 90) % 180) - 90 + lon = ((lon + lon_add + 180) % 360) - 180 + return round(lat, 5), round(lon, 5) + +def get_continuous_coords(lat, lon) -> Tuple[int, int]: + # continuously move around the world + return get_coords(lat, lon, 0.01, 0.01) + +def get_random_coords(lat, lon) -> Tuple[int, int]: + # jump around the world + return get_coords(lat, lon, 20, 20, 10, 20) + +def run_limeSDR_loop(lat, lon, alt, contin_sim, rinex_file, timeout): + while True: + try: + # TODO: add starttime setting and altitude + # -t 2023/01/15,00:00:00 -T 2023/01/15,00:00:00 + # this needs to match the date of the navigation file + print(f"starting LimeGPS, Location: {lat} {lon} {alt}") + cmd = ["LimeGPS/LimeGPS", "-e", rinex_file, "-l", f"{lat},{lon},{alt}"] + print(f"CMD: {cmd}") + sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) + except KeyboardInterrupt: + print("stopping LimeGPS") + return + except sp.TimeoutExpired: + print("LimeGPS timeout reached!") + except Exception as e: + out_stderr = e.stderr.decode('utf-8')# pylint:disable=no-member + if "Device is busy." in out_stderr: + print("GPS simulation is already running, Device is busy!") + return + + print(f"LimeGPS crashed: {str(e)}") + print(f"stderr:\n{e.stderr.decode('utf-8')}")# pylint:disable=no-member + return + + if contin_sim: + lat, lon = get_continuous_coords(lat, lon) + else: + lat, lon = get_random_coords(lat, lon) + +def run_hackRF_loop(lat, lon, rinex_file, timeout): + + if timeout is not None: + print("no jump mode for hackrf!") + return + + try: + print(f"starting gps-sdr-sim, Location: {lat},{lon}") + # create 30second file and replay with hackrf endless + cmd = ["gps-sdr-sim/gps-sdr-sim", "-e", rinex_file, "-l", f"{lat},{lon},-200", "-d", "30"] + sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) + # created in current working directory + except Exception: + print("Failed to generate gpssim.bin") + + try: + print("starting hackrf_transfer") + # create 30second file and replay with hackrf endless + cmd = ["hackrf/host/hackrf-tools/src/hackrf_transfer", "-t", "gpssim.bin", + "-f", "1575420000", "-s", "2600000", "-a", "1", "-R"] + sp.check_output(cmd, stderr=sp.PIPE, timeout=timeout) + except KeyboardInterrupt: + print("stopping hackrf_transfer") + return + except Exception as e: + print(f"hackrf_transfer crashed:{str(e)}") + + +def main(lat, lon, alt, jump_sim, contin_sim, hackrf_mode): + + if hackrf_mode: + if not os.path.exists('hackrf'): + print("hackrf not found run 'setup_hackrf.sh' first") + return + + if not os.path.exists('gps-sdr-sim'): + print("gps-sdr-sim not found run 'setup_hackrf.sh' first") + return + + output = sp.check_output(["hackrf/host/hackrf-tools/src/hackrf_info"]) + if output.strip() == b"" or b"No HackRF boards found." in output: + print("No HackRF boards found!") + return + + else: + if not os.path.exists('LimeGPS'): + print("LimeGPS not found run 'setup.sh' first") + return + + if not os.path.exists('LimeSuite'): + print("LimeSuite not found run 'setup.sh' first") + return + + output = sp.check_output(["LimeSuite/builddir/LimeUtil/LimeUtil", "--find"]) + if output.strip() == b"": + print("No LimeSDR device found!") + return + print(f"Device: {output.strip().decode('utf-8')}") + + if lat == 0 and lon == 0: + lat, lon = get_random_coords(47.2020, 15.7403) + + rinex_file = download_rinex() + + timeout = None + if jump_sim: + timeout = 30 + + if hackrf_mode: + run_hackRF_loop(lat, lon, rinex_file, timeout) + else: + run_limeSDR_loop(lat, lon, alt, contin_sim, rinex_file, timeout) + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Simulate static [or random jumping] GPS signal.") + parser.add_argument("lat", type=float, nargs='?', default=0) + parser.add_argument("lon", type=float, nargs='?', default=0) + parser.add_argument("alt", type=float, nargs='?', default=0) + parser.add_argument("--jump", action="store_true", help="signal that jumps around the world") + parser.add_argument("--contin", action="store_true", help="continuously/slowly moving around the world") + parser.add_argument("--hackrf", action="store_true", help="hackrf mode (DEFAULT: LimeSDR)") + args = parser.parse_args() + main(args.lat, args.lon, args.alt, args.jump, args.contin, args.hackrf) diff --git a/tools/gpstest/test_gps.py b/tools/gpstest/test_gps.py index f5e19372f7..2c08c105a1 100644 --- a/tools/gpstest/test_gps.py +++ b/tools/gpstest/test_gps.py @@ -2,10 +2,10 @@ import time import unittest import struct -import numpy as np +from common.params import Params import cereal.messaging as messaging -import selfdrive.sensord.pigeond as pd +import system.sensord.pigeond as pd from system.hardware import TICI from selfdrive.test.helpers import with_processes @@ -22,7 +22,20 @@ def read_events(service, duration_sec): return events -def verify_ubloxgnss_data(socket: messaging.SubSocket): +def create_backup(pigeon): + # controlled GNSS stop + pigeon.send(b"\xB5\x62\x06\x04\x04\x00\x00\x00\x08\x00\x16\x74") + + # store almanac in flash + pigeon.send(b"\xB5\x62\x09\x14\x04\x00\x00\x00\x00\x00\x21\xEC") + try: + if not pigeon.wait_for_ack(ack=pd.UBLOX_SOS_ACK, nack=pd.UBLOX_SOS_NACK): + assert False, "Could not store almanac" + except TimeoutError: + pass + + +def verify_ubloxgnss_data(socket: messaging.SubSocket, max_time: int): start_time = 0 end_time = 0 events = messaging.drain_sock(socket) @@ -42,7 +55,7 @@ def verify_ubloxgnss_data(socket: messaging.SubSocket): assert end_time != 0, "no ublox measurements received!" ttfm = (end_time - start_time)/1e9 - assert ttfm < 35, f"Time to first measurement > 35s, {ttfm}" + assert ttfm < max_time, f"Time to first measurement > {max_time}s, {ttfm}" # check for satellite count in measurements sat_count = [] @@ -52,42 +65,31 @@ def verify_ubloxgnss_data(socket: messaging.SubSocket): sat_count.append(event.ubloxGnss.measurementReport.numMeas) num_sat = int(sum(sat_count)/len(sat_count)) - assert num_sat > 8, f"Not enough satellites {num_sat} (TestBox setup!)" + assert num_sat >= 5, f"Not enough satellites {num_sat} (TestBox setup!)" -def verify_gps_location(socket: messaging.SubSocket): - buf_lon = [0]*10 - buf_lat = [0]*10 - buf_i = 0 +def verify_gps_location(socket: messaging.SubSocket, max_time: int): events = messaging.drain_sock(socket) assert len(events) != 0, "no gpsLocationExternal measurements" start_time = events[0].logMonoTime end_time = 0 for event in events: - buf_lon[buf_i % 10] = event.gpsLocationExternal.longitude - buf_lat[buf_i % 10] = event.gpsLocationExternal.latitude - buf_i += 1 + gps_valid = event.gpsLocationExternal.flags % 2 - if buf_i < 9: - continue - - if any([lat == 0 or lon == 0 for lat,lon in zip(buf_lat, buf_lon)]): - continue - - if np.std(buf_lon) < 1e-5 and np.std(buf_lat) < 1e-5: + if gps_valid: end_time = event.logMonoTime break assert end_time != 0, "GPS location never converged!" ttfl = (end_time - start_time)/1e9 - assert ttfl < 40, f"Time to first location > 40s, {ttfl}" + assert ttfl < max_time, f"Time to first location > {max_time}s, {ttfl}" hacc = events[-1].gpsLocationExternal.accuracy vacc = events[-1].gpsLocationExternal.verticalAccuracy - assert hacc < 15, f"Horizontal accuracy too high, {hacc}" - assert vacc < 43, f"Vertical accuracy too high, {vacc}" + assert hacc < 20, f"Horizontal accuracy too high, {hacc}" + assert vacc < 45, f"Vertical accuracy too high, {vacc}" def verify_time_to_first_fix(pigeon): @@ -111,11 +113,16 @@ class TestGPS(unittest.TestCase): if not TICI: raise unittest.SkipTest + ublox_available = Params().get_bool("UbloxAvailable") + if not ublox_available: + raise unittest.SkipTest + + def tearDown(self): pd.set_power(False) @with_processes(['ubloxd']) - def test_ublox_reset(self): + def test_a_ublox_reset(self): pigeon, pm = pd.create_pigeon() pd.init_baudrate(pigeon) @@ -127,14 +134,58 @@ class TestGPS(unittest.TestCase): gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) # receive some messages (restart after cold start takes up to 30seconds) - pd.run_receiving(pigeon, pm, 40) + pd.run_receiving(pigeon, pm, 60) - verify_ubloxgnss_data(ugs) - verify_gps_location(gle) + # store almanac for next test + create_backup(pigeon) + + verify_ubloxgnss_data(ugs, 60) + verify_gps_location(gle, 60) # skip for now, this might hang for a while #verify_time_to_first_fix(pigeon) + @with_processes(['ubloxd']) + def test_b_ublox_almanac(self): + pigeon, pm = pd.create_pigeon() + pd.init_baudrate(pigeon) + + # device cold start + pigeon.send(b"\xb5\x62\x06\x04\x04\x00\xff\xff\x00\x00\x0c\x5d") + time.sleep(1) # wait for cold start + pd.init_baudrate(pigeon) + + # clear configuration + pigeon.send_with_ack(b"\xb5\x62\x06\x09\x0d\x00\x00\x00\x1f\x1f\x00\x00\x00\x00\x00\x00\x00\x00\x17\x71\x5b") + + # restoring almanac backup + pigeon.send(b"\xB5\x62\x09\x14\x00\x00\x1D\x60") + status = pigeon.wait_for_backup_restore_status() + assert status == 2, "Could not restore almanac backup" + + pd.initialize_pigeon(pigeon) + + ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) + gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) + + pd.run_receiving(pigeon, pm, 15) + verify_ubloxgnss_data(ugs, 15) + verify_gps_location(gle, 20) + + + @with_processes(['ubloxd']) + def test_c_ublox_startup(self): + pigeon, pm = pd.create_pigeon() + pd.init_baudrate(pigeon) + pd.initialize_pigeon(pigeon) + + ugs = messaging.sub_sock("ubloxGnss", timeout=0.1) + gle = messaging.sub_sock("gpsLocationExternal", timeout=0.1) + pd.run_receiving(pigeon, pm, 10) + verify_ubloxgnss_data(ugs, 10) + verify_gps_location(gle, 10) + + if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() diff --git a/tools/gpstest/test_gps_qcom.py b/tools/gpstest/test_gps_qcom.py new file mode 100644 index 0000000000..b3ce93fc81 --- /dev/null +++ b/tools/gpstest/test_gps_qcom.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +import time +import unittest +import subprocess as sp + +from common.params import Params +from system.hardware import TICI +import cereal.messaging as messaging +from selfdrive.manager.process_config import managed_processes + + +def exec_mmcli(cmd): + cmd = "mmcli -m 0 " + cmd + p = sp.Popen(cmd, shell=True, stdout=sp.PIPE, stderr=sp.PIPE) + return p.communicate() + + +def wait_for_location(socket, timeout): + while True: + events = messaging.drain_sock(socket) + for event in events: + if event.gpsLocation.flags % 2: + return False + + timeout -= 1 + if timeout <= 0: + return True + + time.sleep(0.1) + continue + + +class TestGPS(unittest.TestCase): + @classmethod + def setUpClass(cls): + if not TICI: + raise unittest.SkipTest + + ublox_available = Params().get_bool("UbloxAvailable") + if ublox_available: + raise unittest.SkipTest + + def test_a_quectel_cold_start(self): + # delete assistance data to enforce cold start for GNSS + # testing shows that this takes up to 20min + + _, err = exec_mmcli("--command='AT+QGPSDEL=0'") + assert len(err) == 0, f"GPSDEL failed: {err}" + + managed_processes['rawgpsd'].start() + start_time = time.monotonic() + glo = messaging.sub_sock("gpsLocation", timeout=0.1) + + timeout = 10*60*3 # 3 minute + timedout = wait_for_location(glo, timeout) + managed_processes['rawgpsd'].stop() + + assert timedout is False, "Waiting for location timed out (3min)!" + + duration = time.monotonic() - start_time + assert duration < 60, f"Received GPS location {duration}!" + + + def test_b_quectel_startup(self): + managed_processes['rawgpsd'].start() + start_time = time.monotonic() + glo = messaging.sub_sock("gpsLocation", timeout=0.1) + + timeout = 10*60 # 1 minute + timedout = wait_for_location(glo, timeout) + managed_processes['rawgpsd'].stop() + + assert timedout is False, "Waiting for location timed out (3min)!" + + duration = time.monotonic() - start_time + assert duration < 60, f"Received GPS location {duration}!" + + +if __name__ == "__main__": + unittest.main() diff --git a/tools/gpstest/test_laikad.py b/tools/gpstest/test_laikad.py new file mode 100644 index 0000000000..689b0f0b9f --- /dev/null +++ b/tools/gpstest/test_laikad.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +import os +import time +import unittest + +import cereal.messaging as messaging +import system.sensord.pigeond as pd + +from common.params import Params +from system.hardware import TICI +from selfdrive.manager.process_config import managed_processes +from selfdrive.test.helpers import with_processes + + +def wait_for_location(sm, timeout, con=10): + cons_meas = 0 + start_time = time.monotonic() + while (time.monotonic() - start_time) < timeout: + sm.update() + if not sm.updated["gnssMeasurements"]: + continue + + msg = sm["gnssMeasurements"] + cons_meas = (cons_meas + 1) if 'positionECEF' in msg.to_dict() else 0 + if cons_meas >= con: + return True + return False + + +class TestLaikad(unittest.TestCase): + @classmethod + def setUpClass(self): + if not TICI: + raise unittest.SkipTest + + ublox_available = Params().get_bool("UbloxAvailable") + if not ublox_available: + raise unittest.SkipTest + + def setUp(self): + # ensure laikad cold start + Params().remove("LaikadEphemerisV3") + os.environ["LAIKAD_NO_INTERNET"] = "1" + managed_processes['laikad'].start() + + def tearDown(self): + managed_processes['laikad'].stop() + + + @with_processes(['pigeond', 'ubloxd']) + def test_laikad_cold_start(self): + time.sleep(5) + + start_time = time.monotonic() + sm = messaging.SubMaster(["gnssMeasurements"]) + + success = wait_for_location(sm, 60*2, con=10) + duration = time.monotonic() - start_time + + assert success, "Waiting for location timed out (2min)!" + assert duration < 60, f"Received Location {duration}!" + + + @with_processes(['ubloxd']) + def test_laikad_ublox_reset_start(self): + time.sleep(2) + + pigeon, pm = pd.create_pigeon() + pd.init_baudrate(pigeon) + assert pigeon.reset_device(), "Could not reset device!" + + laikad_sock = messaging.sub_sock("gnssMeasurements", timeout=0.1) + ublox_gnss_sock = messaging.sub_sock("ubloxGnss", timeout=0.1) + + pd.init_baudrate(pigeon) + pd.initialize_pigeon(pigeon) + pd.run_receiving(pigeon, pm, 180) + + ublox_msgs = messaging.drain_sock(ublox_gnss_sock) + laikad_msgs = messaging.drain_sock(laikad_sock) + + gps_ephem_cnt = 0 + glonass_ephem_cnt = 0 + for um in ublox_msgs: + if um.ubloxGnss.which() == 'ephemeris': + gps_ephem_cnt += 1 + elif um.ubloxGnss.which() == 'glonassEphemeris': + glonass_ephem_cnt += 1 + + assert gps_ephem_cnt > 0, "NO gps ephemeris collected!" + assert glonass_ephem_cnt > 0, "NO glonass ephemeris collected!" + + pos_meas = 0 + duration = -1 + for lm in laikad_msgs: + pos_meas = (pos_meas + 1) if 'positionECEF' in lm.gnssMeasurements.to_dict() else 0 + if pos_meas > 5: + duration = (lm.logMonoTime - laikad_msgs[0].logMonoTime)*1e-9 + break + + assert pos_meas > 5, "NOT enough positions at end of read!" + assert duration < 120, "Laikad took too long to get a Position!" + +if __name__ == "__main__": + unittest.main() diff --git a/tools/lib/README.md b/tools/lib/README.md index 3cf239d2df..d77eef5ac5 100644 --- a/tools/lib/README.md +++ b/tools/lib/README.md @@ -1,6 +1,6 @@ ## LogReader -Route is a class for conveniently accessing all the [logs](/selfdrive/loggerd/) from your routes. The LogReader class reads the non-video logs, i.e. rlog.bz2 and qlog.bz2. There's also a matching FrameReader class for reading the videos. +Route is a class for conveniently accessing all the [logs](/system/loggerd/) from your routes. The LogReader class reads the non-video logs, i.e. rlog.bz2 and qlog.bz2. There's also a matching FrameReader class for reading the videos. ```python from tools.lib.route import Route diff --git a/tools/mac_setup.sh b/tools/mac_setup.sh index 214a917f8b..dd2041e0cb 100755 --- a/tools/mac_setup.sh +++ b/tools/mac_setup.sh @@ -52,14 +52,9 @@ brew "zeromq" brew "protobuf" brew "protobuf-c" brew "swig" +cask "gcc-arm-embedded" EOS -# Install gcc-arm-embedded 10.3-2021.10. 11.x is broken on M1 Macs with Xcode 13.3~ -brew uninstall gcc-arm-embedded || true -curl -L https://github.com/Homebrew/homebrew-cask/raw/d407663b8017a0a062c7fc0b929faf2e16abd1ff/Casks/gcc-arm-embedded.rb > /tmp/gcc-arm-embedded.rb -brew install --cask /tmp/gcc-arm-embedded.rb -rm /tmp/gcc-arm-embedded.rb - echo "[ ] finished brew install t=$SECONDS" BREW_PREFIX=$(brew --prefix) @@ -88,7 +83,7 @@ eval "$(pyenv init --path)" echo "[ ] installed python dependencies t=$SECONDS" # install casadi -VENV=`pipenv --venv` +VENV=`poetry env info --path` PYTHON_VER=3.8 PYTHON_VERSION=$(cat $ROOT/.python-version) if [ ! -f "$VENV/include/casadi/casadi.hpp" ]; then diff --git a/tools/plotjuggler/juggle.py b/tools/plotjuggler/juggle.py index b7c32d9bf1..1e592da3b1 100755 --- a/tools/plotjuggler/juggle.py +++ b/tools/plotjuggler/juggle.py @@ -89,7 +89,7 @@ def juggle_route(route_or_segment_name, segment_count, qlog, can, layout, dbc=No query = parse_qs(urlparse(route_or_segment_name).query) route_or_segment_name = query["route"][0] - if route_or_segment_name.startswith(("http://", "https://")) or os.path.isfile(route_or_segment_name): + if route_or_segment_name.startswith(("http://", "https://", "cd:/")) or os.path.isfile(route_or_segment_name): logs = [route_or_segment_name] elif ci: route_or_segment_name = SegmentName(route_or_segment_name, allow_route_name=True) diff --git a/tools/plotjuggler/layouts/controls_mismatch_debug.xml b/tools/plotjuggler/layouts/controls_mismatch_debug.xml index adca3eed29..54586d6e7b 100644 --- a/tools/plotjuggler/layouts/controls_mismatch_debug.xml +++ b/tools/plotjuggler/layouts/controls_mismatch_debug.xml @@ -1,37 +1,44 @@ - + - + - - + + - - + + - - + + - + - - + + - + - - + + - - + + + + + + + + + @@ -46,8 +53,8 @@ - - + + diff --git a/tools/plotjuggler/layouts/longitudinal.xml b/tools/plotjuggler/layouts/longitudinal.xml new file mode 100644 index 0000000000..ff8f3ad193 --- /dev/null +++ b/tools/plotjuggler/layouts/longitudinal.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/plotjuggler/layouts/thermal_debug.xml b/tools/plotjuggler/layouts/thermal_debug.xml index cebda81753..c10b78f1c5 100644 --- a/tools/plotjuggler/layouts/thermal_debug.xml +++ b/tools/plotjuggler/layouts/thermal_debug.xml @@ -1,82 +1,83 @@ - + - + - - + + - - - - - - - - + + + + + + + + - - + + - - - - + + + + - - + + - + - + - - + + - + - - + + - + + - - + + - + - + - - + + - - - - + + + + - - + + - - - - + + + + @@ -92,8 +93,8 @@ - - + + diff --git a/tools/plotjuggler/test_plotjuggler.py b/tools/plotjuggler/test_plotjuggler.py index edaec9c80a..8fc032042a 100755 --- a/tools/plotjuggler/test_plotjuggler.py +++ b/tools/plotjuggler/test_plotjuggler.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import os +import glob import signal import subprocess import time @@ -9,12 +10,14 @@ from common.basedir import BASEDIR from common.timeout import Timeout from tools.plotjuggler.juggle import install +PJ_DIR = os.path.join(BASEDIR, "tools/plotjuggler") + class TestPlotJuggler(unittest.TestCase): def test_demo(self): install() - pj = os.path.join(BASEDIR, "tools/plotjuggler/juggle.py") + pj = os.path.join(PJ_DIR, "juggle.py") p = subprocess.Popen(f'QT_QPA_PLATFORM=offscreen {pj} --demo None 1 --qlog', stderr=subprocess.PIPE, shell=True, start_new_session=True) @@ -29,5 +32,22 @@ class TestPlotJuggler(unittest.TestCase): self.assertEqual(p.poll(), None) os.killpg(os.getpgid(p.pid), signal.SIGTERM) + # TODO: also test that layouts successfully load + def test_layouts(self): + bad_strings = ( + # if a previously loaded file is defined, + # PJ will throw a warning when loading the layout + "fileInfo", + "previouslyLoaded_Datafiles", + ) + for fn in glob.glob(os.path.join(PJ_DIR, "layouts/*")): + name = os.path.basename(fn) + with self.subTest(layout=name): + with open(fn) as f: + layout = f.read() + violations = [s for s in bad_strings if s in layout] + assert len(violations) == 0, f"These should be stripped out of the layout: {str(violations)}" + + if __name__ == "__main__": unittest.main() diff --git a/tools/replay/camera.cc b/tools/replay/camera.cc index 72b385dca8..66898c9244 100644 --- a/tools/replay/camera.cc +++ b/tools/replay/camera.cc @@ -56,7 +56,7 @@ void CameraServer::cameraThread(Camera &cam) { .timestamp_eof = eidx.getTimestampEof(), }; yuv->set_frame_id(eidx.getFrameId()); - vipc_server_->send(yuv, &extra, false); + vipc_server_->send(yuv, &extra); } else { rError("camera[%d] failed to get frame: %lu", cam.type, eidx.getSegmentId()); } diff --git a/tools/replay/consoleui.cc b/tools/replay/consoleui.cc index 5f165ac312..077861ff96 100644 --- a/tools/replay/consoleui.cc +++ b/tools/replay/consoleui.cc @@ -50,14 +50,6 @@ void add_str(WINDOW *w, const char *str, Color color = Color::Default, bool bold if (color != Color::Default) wattroff(w, COLOR_PAIR(color)); } -std::string format_seconds(int s) { - int total_minutes = s / 60; - int seconds = s % 60; - int hours = total_minutes / 60; - int minutes = total_minutes % 60; - return util::string_format("%02d:%02d:%02d", hours, minutes, seconds); -} - } // namespace ConsoleUI::ConsoleUI(Replay *replay, QObject *parent) : replay(replay), sm({"carState", "liveParameters"}), QObject(parent) { @@ -156,13 +148,13 @@ void ConsoleUI::timerEvent(QTimerEvent *ev) { } void ConsoleUI::updateStatus() { - auto write_item = [this](int y, int x, const char *key, const std::string &value, const char *unit, + auto write_item = [this](int y, int x, const char *key, const std::string &value, const std::string &unit, bool bold = false, Color color = Color::BrightWhite) { auto win = w[Win::CarState]; wmove(win, y, x); add_str(win, key); add_str(win, value.c_str(), color, bold); - add_str(win, unit); + add_str(win, unit.c_str()); }; static const std::pair status_text[] = { {"loading...", Color::Red}, @@ -173,13 +165,15 @@ void ConsoleUI::updateStatus() { sm.update(0); if (status != Status::Paused) { - status = (sm.updated("carState") || sm.updated("liveParameters")) ? Status::Playing : Status::Waiting; + auto events = replay->events(); + uint64_t current_mono_time = replay->routeStartTime() + replay->currentSeconds() * 1e9; + bool playing = !events->empty() && events->back()->mono_time > current_mono_time; + status = playing ? Status::Playing : Status::Waiting; } auto [status_str, status_color] = status_text[status]; write_item(0, 0, "STATUS: ", status_str, " ", false, status_color); - std::string suffix = util::string_format(" / %s [%d/%d] ", format_seconds(replay->totalSeconds()).c_str(), - replay->currentSeconds() / 60, replay->route()->segments().size()); - write_item(0, 25, "TIME: ", format_seconds(replay->currentSeconds()), suffix.c_str(), true); + std::string current_segment = " - " + std::to_string((int)(replay->currentSeconds() / 60)); + write_item(0, 25, "TIME: ", replay->currentDateTime().toString("ddd MMMM dd hh:mm:ss").toStdString(), current_segment, true); auto p = sm["liveParameters"].getLiveParameters(); write_item(1, 0, "STIFFNESS: ", util::string_format("%.2f %%", p.getStiffnessFactor() * 100), " "); diff --git a/tools/replay/main.cc b/tools/replay/main.cc index 40dace0c91..6b624aa1fa 100644 --- a/tools/replay/main.cc +++ b/tools/replay/main.cc @@ -1,6 +1,7 @@ #include #include +#include "common/prefix.h" #include "tools/replay/consoleui.h" #include "tools/replay/replay.h" @@ -23,9 +24,11 @@ int main(int argc, char *argv[]) { parser.addPositionalArgument("route", "the drive to replay. find your drives at connect.comma.ai"); parser.addOption({{"a", "allow"}, "whitelist of services to send", "allow"}); parser.addOption({{"b", "block"}, "blacklist of services to send", "block"}); + parser.addOption({{"c", "cache"}, "cache segments in memory. default is 5", "n"}); parser.addOption({{"s", "start"}, "start from ", "seconds"}); parser.addOption({"demo", "use a demo route instead of providing your own"}); parser.addOption({"data_dir", "local directory with routes", "data_dir"}); + parser.addOption({"prefix", "set OPENPILOT_PREFIX", "prefix"}); for (auto &[name, _, desc] : flags) { parser.addOption({name, desc}); } @@ -46,7 +49,17 @@ int main(int argc, char *argv[]) { replay_flags |= flag; } } + + std::unique_ptr op_prefix; + auto prefix = parser.value("prefix"); + if (!prefix.isEmpty()) { + op_prefix.reset(new OpenpilotPrefix(prefix.toStdString())); + } + Replay *replay = new Replay(route, allow, block, nullptr, replay_flags, parser.value("data_dir"), &app); + if (!parser.value("c").isEmpty()) { + replay->setSegmentCacheLimit(parser.value("c").toInt()); + } if (!replay->load()) { return 0; } diff --git a/tools/replay/replay.cc b/tools/replay/replay.cc index 1337a4ef2c..b68330f688 100644 --- a/tools/replay/replay.cc +++ b/tools/replay/replay.cc @@ -25,6 +25,13 @@ Replay::Replay(QString route, QStringList allow, QStringList block, SubMaster *s s.push_back(it.name); } } + + if (!allow_list.empty()) { + // the following events are needed for replay to work properly. + allow_list.insert(cereal::Event::Which::INIT_DATA); + allow_list.insert(cereal::Event::Which::CAR_PARAMS); + } + qDebug() << "services " << s; qDebug() << "loading route " << route; @@ -51,9 +58,9 @@ void Replay::stop() { stream_thread_->wait(); stream_thread_ = nullptr; } - segments_.clear(); camera_server_.reset(nullptr); timeline_future.waitForFinished(); + segments_.clear(); rInfo("shutdown: done"); } @@ -107,6 +114,7 @@ void Replay::seekTo(double seconds, bool relative) { rInfo("seeking to %d s, segment %d", (int)seconds, seg); current_segment_ = seg; cur_mono_time_ = route_start_ts_ + seconds * 1e9; + emit seekedTo(seconds); return isSegmentMerged(seg); }); queueSegment(); @@ -123,9 +131,9 @@ void Replay::buildTimeline() { uint64_t alert_begin = 0; TimelineType alert_type = TimelineType::None; - for (int i = 0; i < segments_.size() && !exit_; ++i) { + for (auto it = segments_.cbegin(); it != segments_.cend() && !exit_; ++it) { LogReader log; - if (!log.load(route_->at(i).qlog.toStdString(), &exit_, + if (!log.load(route_->at(it->first).qlog.toStdString(), &exit_, {cereal::Event::Which::CONTROLS_STATE, cereal::Event::Which::USER_FLAG}, !hasFlag(REPLAY_FLAG_NO_FILE_CACHE), 0, 3)) continue; @@ -185,7 +193,7 @@ std::optional Replay::find(FindFlag flag) { void Replay::pause(bool pause) { updateEvents([=]() { - rWarning("%s at %d s", pause ? "paused..." : "resuming", currentSeconds()); + rWarning("%s at %.2f s", pause ? "paused..." : "resuming", currentSeconds()); paused_ = pause; return true; }); @@ -209,11 +217,17 @@ void Replay::segmentLoadFinished(bool success) { void Replay::queueSegment() { if (segments_.empty()) return; - SegmentMap::iterator cur, end; - cur = end = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); - for (int i = 0; end != segments_.end() && i <= segment_cache_limit + FORWARD_FETCH_SEGS; ++i) { + SegmentMap::iterator begin, cur; + begin = cur = segments_.lower_bound(std::min(current_segment_.load(), segments_.rbegin()->first)); + int distance = std::max(std::ceil(segment_cache_limit / 2.0) - 1, segment_cache_limit - std::distance(cur, segments_.end())); + for (int i = 0; begin != segments_.begin() && i < distance; ++i) { + --begin; + } + auto end = begin; + for (int i = 0; end != segments_.end() && i < segment_cache_limit; ++i) { ++end; } + // load one segment at a time for (auto it = cur; it != end; ++it) { auto &[n, seg] = *it; @@ -227,12 +241,6 @@ void Replay::queueSegment() { } } - const auto &cur_segment = cur->second; - // merge the previous adjacent segment if it's loaded - auto begin = segments_.find(cur_segment->seg_num - 1); - if (begin == segments_.end() || !(begin->second && begin->second->isLoaded())) { - begin = cur; - } mergeSegments(begin, end); // free segments out of current semgnt window. @@ -240,6 +248,7 @@ void Replay::queueSegment() { std::for_each(end, segments_.end(), [](auto &e) { e.second.reset(nullptr); }); // start stream thread + const auto &cur_segment = cur->second; if (stream_thread_ == nullptr && cur_segment->isLoaded()) { startStream(cur_segment.get()); emit streamStarted(); @@ -247,12 +256,13 @@ 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 && it->second->isLoaded() && segments_need_merge.size() < segment_cache_limit; ++it) { - segments_need_merge.push_back(it->first); - new_events_size += it->second->log->events.size(); + for (auto it = begin; it != end; ++it) { + if (it->second && it->second->isLoaded()) { + segments_need_merge.push_back(it->first); + new_events_size += it->second->log->events.size(); + } } if (segments_need_merge != segments_merged_) { @@ -266,8 +276,12 @@ void Replay::mergeSegments(const SegmentMap::iterator &begin, const SegmentMap:: new_events_->reserve(new_events_size); for (int n : segments_need_merge) { 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()); + if (e.size() > 0) { + auto insert_from = e.begin(); + if (new_events_->size() > 0 && (*insert_from)->which == cereal::Event::Which::INIT_DATA) ++insert_from; + auto middle = new_events_->insert(new_events_->end(), insert_from, e.end()); + std::inplace_merge(new_events_->begin(), middle, new_events_->end(), Event::lessThan()); + } } updateEvents([&]() { @@ -358,6 +372,7 @@ void Replay::publishFrame(const Event *e) { void Replay::stream() { cereal::Event::Which cur_which = cereal::Event::Which::INIT_DATA; + double prev_replay_speed = 1.0; std::unique_lock lk(stream_lock_); while (true) { @@ -397,10 +412,11 @@ void Replay::stream() { long rtime = nanos_since_boot() - loop_start_ts; long behind_ns = etime - rtime; // if behind_ns is greater than 1 second, it means that an invalid segemnt is skipped by seeking/replaying - if (behind_ns >= 1 * 1e9) { - // reset start times + if (behind_ns >= 1 * 1e9 || speed_ != prev_replay_speed) { + // reset event start times evt_start_ts = cur_mono_time_; loop_start_ts = nanos_since_boot(); + prev_replay_speed = speed_; } else if (behind_ns > 0 && !hasFlag(REPLAY_FLAG_FULL_SPEED)) { precise_nano_sleep(behind_ns); } diff --git a/tools/replay/replay.h b/tools/replay/replay.h index 88c285125a..2bb426361b 100644 --- a/tools/replay/replay.h +++ b/tools/replay/replay.h @@ -10,7 +10,7 @@ const QString DEMO_ROUTE = "4cf7a6ad03080c90|2021-09-29--13-46-36"; // one segment uses about 100M of memory -constexpr int FORWARD_FETCH_SEGS = 3; +constexpr int MIN_SEGMENTS_CACHE = 5; enum REPLAY_FLAGS { REPLAY_FLAG_NONE = 0x0000, @@ -58,18 +58,20 @@ public: event_filter = filter; } inline int segmentCacheLimit() const { return segment_cache_limit; } - inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(3, n); } + inline void setSegmentCacheLimit(int n) { segment_cache_limit = std::max(MIN_SEGMENTS_CACHE, n); } 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; } inline const Route* route() const { return route_.get(); } inline double currentSeconds() const { return double(cur_mono_time_ - route_start_ts_) / 1e9; } + inline QDateTime currentDateTime() const { return route_->datetime().addSecs(currentSeconds()); } inline uint64_t routeStartTime() const { return route_start_ts_; } inline int toSeconds(uint64_t mono_time) const { return (mono_time - route_start_ts_) / 1e9; } inline int totalSeconds() const { return segments_.size() * 60; } inline void setSpeed(float speed) { speed_ = speed; } inline float getSpeed() const { return speed_; } inline const std::vector *events() const { return events_.get(); } + inline const std::map> &segments() const { return segments_; }; inline const std::string &carFingerprint() const { return car_fingerprint_; } inline const std::vector> getTimeline() { std::lock_guard lk(timeline_lock); @@ -79,6 +81,7 @@ public: signals: void streamStarted(); void segmentsMerged(); + void seekedTo(double sec); protected slots: void segmentLoadFinished(bool success); @@ -133,5 +136,5 @@ protected: float speed_ = 1.0; replayEventFilter event_filter = nullptr; void *filter_opaque = nullptr; - int segment_cache_limit = 3; + int segment_cache_limit = MIN_SEGMENTS_CACHE; }; diff --git a/tools/replay/route.cc b/tools/replay/route.cc index f0d6ec5a12..619aeb3f5f 100644 --- a/tools/replay/route.cc +++ b/tools/replay/route.cc @@ -19,18 +19,19 @@ Route::Route(const QString &route, const QString &data_dir) : data_dir_(data_dir } RouteIdentifier Route::parseRoute(const QString &str) { - QRegExp rx(R"(^([a-z0-9]{16})([|_/])(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})(?:(--|/)(\d*))?$)"); + QRegExp rx(R"(^(?:([a-z0-9]{16})([|_/]))?(\d{4}-\d{2}-\d{2}--\d{2}-\d{2}-\d{2})(?:(--|/)(\d*))?$)"); if (rx.indexIn(str) == -1) return {}; const QStringList list = rx.capturedTexts(); - return {list[1], list[3], list[5].toInt(), list[1] + "|" + list[3]}; + return {.dongle_id = list[1], .timestamp = list[3], .segment_id = list[5].toInt(), .str = list[1] + "|" + list[3]}; } bool Route::load() { - if (route_.str.isEmpty()) { + if (route_.str.isEmpty() || (data_dir_.isEmpty() && route_.dongle_id.isEmpty())) { rInfo("invalid route format"); return false; } + date_time_ = QDateTime::fromString(route_.timestamp, "yyyy-MM-dd--HH-mm-ss"); return data_dir_.isEmpty() ? loadFromServer() : loadFromLocal(); } diff --git a/tools/replay/route.h b/tools/replay/route.h index 6b78ebad87..86adf6a14d 100644 --- a/tools/replay/route.h +++ b/tools/replay/route.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "tools/replay/framereader.h" @@ -27,6 +28,7 @@ public: Route(const QString &route, const QString &data_dir = {}); bool load(); inline const QString &name() const { return route_.str; } + inline const QDateTime datetime() const { return date_time_; } inline const QString &dir() const { return data_dir_; } inline const RouteIdentifier &identifier() const { return route_; } inline const std::map &segments() const { return segments_; } @@ -41,6 +43,7 @@ protected: RouteIdentifier route_ = {}; QString data_dir_; std::map segments_; + QDateTime date_time_; }; class Segment : public QObject { diff --git a/tools/replay/tests/test_replay.cc b/tools/replay/tests/test_replay.cc index 5b61b6b6f2..30e3c811ee 100644 --- a/tools/replay/tests/test_replay.cc +++ b/tools/replay/tests/test_replay.cc @@ -197,8 +197,8 @@ void TestReplay::test_seek() { stream_thread_ = new QThread(this); QEventLoop loop; std::thread thread = std::thread([&]() { - for (int i = 0; i < 50; ++i) { - testSeekTo(random_int(0, 3 * 60)); + for (int i = 0; i < 10; ++i) { + testSeekTo(random_int(0, 1 * 60)); } loop.quit(); }); @@ -207,8 +207,7 @@ void TestReplay::test_seek() { } TEST_CASE("Replay") { - auto flag = GENERATE(REPLAY_FLAG_NO_FILE_CACHE, REPLAY_FLAG_NONE); - TestReplay replay(DEMO_ROUTE, flag); + TestReplay replay(DEMO_ROUTE); REQUIRE(replay.load()); replay.test_seek(); } diff --git a/tools/replay/ui.py b/tools/replay/ui.py index bbcd49061e..43cbef5bd6 100755 --- a/tools/replay/ui.py +++ b/tools/replay/ui.py @@ -128,8 +128,8 @@ def ui_thread(addr): sm.update(0) w = sm['controlsState'].lateralControlState.which() - if w == 'lqrState': - angle_steers_k = sm['controlsState'].lateralControlState.lqrState.steeringAngleDeg + if w == 'lqrStateDEPRECATED': + angle_steers_k = sm['controlsState'].lateralControlState.lqrStateDEPRECATED.steeringAngleDeg elif w == 'indiState': angle_steers_k = sm['controlsState'].lateralControlState.indiState.steeringAngleDeg else: diff --git a/tools/sim/Dockerfile.sim b/tools/sim/Dockerfile.sim index 3692d48e0f..be16f8c863 100644 --- a/tools/sim/Dockerfile.sim +++ b/tools/sim/Dockerfile.sim @@ -15,7 +15,6 @@ RUN mkdir -p $HOME/openpilot COPY SConstruct $HOME/openpilot/ COPY ./third_party $HOME/openpilot/third_party -COPY ./pyextra $HOME/openpilot/pyextra COPY ./site_scons $HOME/openpilot/site_scons COPY ./rednose $HOME/openpilot/rednose COPY ./laika $HOME/openpilot/laika diff --git a/tools/sim/README.md b/tools/sim/README.md index 40603f3f71..69a89aefab 100644 --- a/tools/sim/README.md +++ b/tools/sim/README.md @@ -6,7 +6,7 @@ openpilot implements a [bridge](bridge.py) that allows it to run in the [CARLA s ## System Requirements openpilot doesn't have any extreme hardware requirements, however CARLA requires an NVIDIA graphics card and is very resource-intensive and may not run smoothly on your system. -For this case, we have a the simulator in low quality by default. +For this case, we have the simulator in low quality by default. You can also check out the [CARLA python documentation](https://carla.readthedocs.io/en/latest/python_api/) to find more parameters to tune that might increase performance on your system. diff --git a/tools/sim/bridge.py b/tools/sim/bridge.py index a105ed751e..7bb12badb2 100755 --- a/tools/sim/bridge.py +++ b/tools/sim/bridge.py @@ -82,11 +82,10 @@ class Camerad: self.queue = cl.CommandQueue(self.ctx) cl_arg = f" -DHEIGHT={H} -DWIDTH={W} -DRGB_STRIDE={W * 3} -DUV_WIDTH={W // 2} -DUV_HEIGHT={H // 2} -DRGB_SIZE={W * H} -DCL_DEBUG " - # TODO: move rgb_to_yuv.cl to local dir once the frame stream camera is removed - kernel_fn = os.path.join(BASEDIR, "system", "camerad", "transforms", "rgb_to_yuv.cl") + kernel_fn = os.path.join(BASEDIR, "tools/sim/rgb_to_nv12.cl") with open(kernel_fn) as f: prg = cl.Program(self.ctx, f.read()).build(cl_arg) - self.krnl = prg.rgb_to_yuv + self.krnl = prg.rgb_to_nv12 self.Wdiv4 = W // 4 if (W % 4 == 0) else (W + (4 - W % 4)) // 4 self.Hdiv4 = H // 4 if (H % 4 == 0) else (H + (4 - H % 4)) // 4 @@ -129,7 +128,7 @@ def imu_callback(imu, vehicle_state): vehicle_state.bearing_deg = math.degrees(imu.compass) dat = messaging.new_message('accelerometer') dat.accelerometer.sensor = 4 - dat.accelerometer.type = 0x1 + dat.accelerometer.type = 0x10 dat.accelerometer.timestamp = dat.logMonoTime # TODO: use the IMU timestamp dat.accelerometer.init('acceleration') dat.accelerometer.acceleration.v = [imu.accelerometer.x, imu.accelerometer.y, imu.accelerometer.z] @@ -212,7 +211,10 @@ def fake_driver_monitoring(exit_event: threading.Event): while not exit_event.is_set(): # dmonitoringmodeld output dat = messaging.new_message('driverStateV2') + dat.driverStateV2.leftDriverData.faceOrientation = [0., 0., 0.] dat.driverStateV2.leftDriverData.faceProb = 1.0 + dat.driverStateV2.rightDriverData.faceOrientation = [0., 0., 0.] + dat.driverStateV2.rightDriverData.faceProb = 1.0 pm.send('driverStateV2', dat) # dmonitoringd output diff --git a/tools/sim/launch_openpilot.sh b/tools/sim/launch_openpilot.sh index adabc40c2e..2100f05b67 100755 --- a/tools/sim/launch_openpilot.sh +++ b/tools/sim/launch_openpilot.sh @@ -6,7 +6,7 @@ export SIMULATION="1" export SKIP_FW_QUERY="1" export FINGERPRINT="HONDA CIVIC 2016" -export BLOCK="camerad,loggerd,encoderd" +export BLOCK="camerad,loggerd,encoderd,micd" if [[ "$CI" ]]; then # TODO: offscreen UI should work export BLOCK="${BLOCK},ui" diff --git a/system/camerad/transforms/rgb_to_yuv.cl b/tools/sim/rgb_to_nv12.cl similarity index 75% rename from system/camerad/transforms/rgb_to_yuv.cl rename to tools/sim/rgb_to_nv12.cl index 60dbdb4d5e..54816d5d7d 100644 --- a/system/camerad/transforms/rgb_to_yuv.cl +++ b/tools/sim/rgb_to_nv12.cl @@ -29,23 +29,21 @@ inline void convert_4_ys(__global uchar * out_yuv, int yi, const uchar8 rgbs1, c vstore4(yy, 0, out_yuv + yi); } -inline void convert_uv(__global uchar * out_yuv, int ui, int vi, +inline void convert_uv(__global uchar * out_yuv, int uvi, const uchar8 rgbs1, const uchar8 rgbs2) { // U & V: average of 2x2 pixels square const short ab = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); const short ag = AVERAGE(rgbs1.s1, rgbs1.s4, rgbs2.s1, rgbs2.s4); const short ar = AVERAGE(rgbs1.s2, rgbs1.s5, rgbs2.s2, rgbs2.s5); #ifdef CL_DEBUG - if(ui >= RGB_SIZE + RGB_SIZE / 4) - printf("U overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4); - if(vi >= RGB_SIZE + RGB_SIZE / 2) - printf("V overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2); + if(uvi >= RGB_SIZE + RGB_SIZE / 2) + printf("UV overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2); #endif - out_yuv[ui] = RGB_TO_U(ar, ag, ab); - out_yuv[vi] = RGB_TO_V(ar, ag, ab); + out_yuv[uvi] = RGB_TO_U(ar, ag, ab); + out_yuv[uvi+1] = RGB_TO_V(ar, ag, ab); } -inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi, +inline void convert_2_uvs(__global uchar * out_yuv, int uvi, const uchar8 rgbs1, const uchar8 rgbs2, const uchar8 rgbs3, const uchar8 rgbs4) { // U & V: average of 2x2 pixels square const short ab1 = AVERAGE(rgbs1.s0, rgbs1.s3, rgbs2.s0, rgbs2.s3); @@ -54,25 +52,20 @@ inline void convert_2_uvs(__global uchar * out_yuv, int ui, int vi, const short ab2 = AVERAGE(rgbs1.s6, rgbs3.s1, rgbs2.s6, rgbs4.s1); const short ag2 = AVERAGE(rgbs1.s7, rgbs3.s2, rgbs2.s7, rgbs4.s2); const short ar2 = AVERAGE(rgbs3.s0, rgbs3.s3, rgbs4.s0, rgbs4.s3); - uchar2 u2 = (uchar2)( + uchar4 uv = (uchar4)( RGB_TO_U(ar1, ag1, ab1), - RGB_TO_U(ar2, ag2, ab2) - ); - uchar2 v2 = (uchar2)( RGB_TO_V(ar1, ag1, ab1), + RGB_TO_U(ar2, ag2, ab2), RGB_TO_V(ar2, ag2, ab2) ); #ifdef CL_DEBUG1 - if(ui > RGB_SIZE + RGB_SIZE / 4 - 2) - printf("U 2 overflow, %d >= %d\n", ui, RGB_SIZE + RGB_SIZE / 4 - 2); - if(vi > RGB_SIZE + RGB_SIZE / 2 - 2) - printf("V 2 overflow, %d >= %d\n", vi, RGB_SIZE + RGB_SIZE / 2 - 2); + if(uvi > RGB_SIZE + RGB_SIZE / 2 - 4) + printf("UV2 overflow, %d >= %d\n", uvi, RGB_SIZE + RGB_SIZE / 2 - 2); #endif - vstore2(u2, 0, out_yuv + ui); - vstore2(v2, 0, out_yuv + vi); + vstore4(uv, 0, out_yuv + uvi); } -__kernel void rgb_to_yuv(__global uchar const * const rgb, +__kernel void rgb_to_nv12(__global uchar const * const rgb, __global uchar * out_yuv) { const int dx = get_global_id(0); @@ -81,8 +74,7 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, const int row = mul24(dy, 4); // Current row in rgb image const int bgri_start = mad24(row, RGB_STRIDE, mul24(col, 3)); // Start offset of rgb data being converted const int yi_start = mad24(row, WIDTH, col); // Start offset in the target yuv buffer - int ui = mad24(row / 2, UV_WIDTH, RGB_SIZE + col / 2); - int vi = mad24(row / 2 , UV_WIDTH, RGB_SIZE + UV_WIDTH * UV_HEIGHT + col / 2); + int uvi = mad24(row / 2, WIDTH, RGB_SIZE + col); int num_col = min(WIDTH - col, 4); int num_row = min(HEIGHT - row, 4); if(num_row == 4) { @@ -99,15 +91,15 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); convert_4_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0, rgbs2_1); convert_4_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0, rgbs3_1); - convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); - convert_2_uvs(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); + convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); + convert_2_uvs(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0, rgbs2_1, rgbs3_1); } else if(num_col == 2) { convert_2_ys(out_yuv, yi_start, rgbs0_0); convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); convert_2_ys(out_yuv, yi_start + WIDTH * 2, rgbs2_0); convert_2_ys(out_yuv, yi_start + WIDTH * 3, rgbs3_0); - convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0); - convert_uv(out_yuv, ui + UV_WIDTH, vi + UV_WIDTH, rgbs2_0, rgbs3_0); + convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); + convert_uv(out_yuv, uvi + WIDTH, rgbs2_0, rgbs3_0); } } else { const uchar8 rgbs0_0 = vload8(0, rgb + bgri_start); @@ -117,11 +109,11 @@ __kernel void rgb_to_yuv(__global uchar const * const rgb, if(num_col == 4) { convert_4_ys(out_yuv, yi_start, rgbs0_0, rgbs0_1); convert_4_ys(out_yuv, yi_start + WIDTH, rgbs1_0, rgbs1_1); - convert_2_uvs(out_yuv, ui, vi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); + convert_2_uvs(out_yuv, uvi, rgbs0_0, rgbs1_0, rgbs0_1, rgbs1_1); } else if(num_col == 2) { convert_2_ys(out_yuv, yi_start, rgbs0_0); convert_2_ys(out_yuv, yi_start + WIDTH, rgbs1_0); - convert_uv(out_yuv, ui, vi, rgbs0_0, rgbs1_0); + convert_uv(out_yuv, uvi, rgbs0_0, rgbs1_0); } } } diff --git a/tools/sim/start_openpilot_docker.sh b/tools/sim/start_openpilot_docker.sh index e48e63574d..dba89b8b70 100755 --- a/tools/sim/start_openpilot_docker.sh +++ b/tools/sim/start_openpilot_docker.sh @@ -20,6 +20,7 @@ else EXTRA_ARGS="${EXTRA_ARGS} -it" fi +docker kill openpilot_client || true docker run --net=host\ --name openpilot_client \ --rm \ diff --git a/tools/tuning/measure_steering_accuracy.py b/tools/tuning/measure_steering_accuracy.py index 7abfb6358a..4819be770f 100755 --- a/tools/tuning/measure_steering_accuracy.py +++ b/tools/tuning/measure_steering_accuracy.py @@ -8,24 +8,13 @@ import signal from collections import defaultdict import cereal.messaging as messaging +from tools.lib.logreader import logreader_from_route_or_segment def sigint_handler(signal, frame): - print("handler!") exit(0) signal.signal(signal.SIGINT, sigint_handler) -if __name__ == "__main__": - - parser = argparse.ArgumentParser(description='Sniff a communication socket') - parser.add_argument('control_type', help="[pid|indi|lqr|angle]") - parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") - parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") - args = parser.parse_args() - - if args.addr != "127.0.0.1": - os.environ["ZMQ"] = "1" - messaging.context = messaging.Context() - +class SteeringAccuracyTool: all_groups = {"germany": (45, "45 - up m/s // 162 - up km/h // 101 - up mph"), "veryfast": (35, "35 - 45 m/s // 126 - 162 km/h // 78 - 101 mph"), "fast": (25, "25 - 35 m/s // 90 - 126 km/h // 56 - 78 mph"), @@ -33,39 +22,28 @@ if __name__ == "__main__": "slow": (5, " 5 - 15 m/s // 18 - 54 km/h // 11 - 34 mph"), "crawl": (0, " 0 - 5 m/s // 0 - 18 km/h // 0 - 11 mph")} - if args.group == "all": - display_groups = all_groups.keys() - elif args.group in all_groups.keys(): - display_groups = [args.group] - else: - raise ValueError("invalid speed group, see help") + def __init__(self, args): + self.msg_cnt = 0 + self.cnt = 0 + self.total_error = 0 - speed_group_stats = {} - for group in all_groups: - speed_group_stats[group] = defaultdict(lambda: {'err': 0, "cnt": 0, "=": 0, "+": 0, "-": 0, "steer": 0, "limited": 0, "saturated": 0, "dpp": 0}) - - carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) - sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'lateralPlan'], addr=args.addr) - time.sleep(1) # Make sure all submaster data is available before going further - - msg_cnt = 0 - cnt = 0 - total_error = 0 - - while messaging.recv_one(carControl): - sm.update() - msg_cnt += 1 - - if args.control_type == "pid": - control_state = sm['controlsState'].lateralControlState.pidState - elif args.control_type == "indi": - control_state = sm['controlsState'].lateralControlState.indiState - elif args.control_type == "lqr": - control_state = sm['controlsState'].lateralControlState.lqrState - elif args.control_type == "angle": - control_state = sm['controlsState'].lateralControlState.angleState + if args.group == "all": + self.display_groups = self.all_groups.keys() + elif args.group in self.all_groups.keys(): + self.display_groups = [args.group] else: - raise ValueError("invalid lateral control type, see help") + raise ValueError("invalid speed group, see help") + + self.speed_group_stats = {} + for group in self.all_groups: + self.speed_group_stats[group] = defaultdict(lambda: {'err': 0, "cnt": 0, "=": 0, "+": 0, "-": 0, "steer": 0, "limited": 0, "saturated": 0, "dpp": 0}) + + def update(self, sm): + self.msg_cnt += 1 + + lateralControlState = sm['controlsState'].lateralControlState + control_type = list(lateralControlState.to_dict().keys())[0] + control_state = lateralControlState.__getattr__(control_type) v_ego = sm['carState'].vEgo active = sm['controlsState'].active @@ -77,10 +55,10 @@ if __name__ == "__main__": d_path_points = sm['lateralPlan'].dPathPoints # must be engaged, not at standstill, not overriding steering, and not changing lanes if active and not standstill and not overriding and not changing_lanes: - cnt += 1 + self.cnt += 1 # wait 5 seconds after engage / standstill / override / lane change - if cnt >= 500: + if self.cnt >= 500: actual_angle = control_state.steeringAngleDeg desired_angle = control_state.steeringAngleDesiredDeg @@ -91,41 +69,88 @@ if __name__ == "__main__": angle_error = round(angle_error, 2) angle_abs = int(abs(round(desired_angle, 0))) - for group, group_props in all_groups.items(): + for group, group_props in self.all_groups.items(): if v_ego > group_props[0]: # collect stats - speed_group_stats[group][angle_abs]["cnt"] += 1 - speed_group_stats[group][angle_abs]["err"] += angle_error - speed_group_stats[group][angle_abs]["steer"] += abs(steer) + self.speed_group_stats[group][angle_abs]["cnt"] += 1 + self.speed_group_stats[group][angle_abs]["err"] += angle_error + self.speed_group_stats[group][angle_abs]["steer"] += abs(steer) if len(d_path_points): - speed_group_stats[group][angle_abs]["dpp"] += abs(d_path_points[0]) + self.speed_group_stats[group][angle_abs]["dpp"] += abs(d_path_points[0]) if steer_limited: - speed_group_stats[group][angle_abs]["limited"] += 1 + self.speed_group_stats[group][angle_abs]["limited"] += 1 if control_state.saturated: - speed_group_stats[group][angle_abs]["saturated"] += 1 + self.speed_group_stats[group][angle_abs]["saturated"] += 1 if actual_angle == desired_angle: - speed_group_stats[group][angle_abs]["="] += 1 + self.speed_group_stats[group][angle_abs]["="] += 1 else: if desired_angle == 0.: overshoot = True else: overshoot = desired_angle < actual_angle if desired_angle > 0. else desired_angle > actual_angle - speed_group_stats[group][angle_abs]["+" if overshoot else "-"] += 1 + self.speed_group_stats[group][angle_abs]["+" if overshoot else "-"] += 1 break else: - cnt = 0 + self.cnt = 0 - if msg_cnt % 100 == 0: + if self.msg_cnt % 100 == 0: print(chr(27) + "[2J") - if cnt != 0: + if self.cnt != 0: print("COLLECTING ...\n") else: print("DISABLED (not active, standstill, steering override, or lane change)\n") - for group in display_groups: - if len(speed_group_stats[group]) > 0: - print(f"speed group: {group:10s} {all_groups[group][1]:>96s}") + for group in self.display_groups: + if len(self.speed_group_stats[group]) > 0: + print(f"speed group: {group:10s} {self.all_groups[group][1]:>96s}") print(f" {'-'*118}") - for k in sorted(speed_group_stats[group].keys()): - v = speed_group_stats[group][k] + for k in sorted(self.speed_group_stats[group].keys()): + v = self.speed_group_stats[group][k] print(f' {k:#2}° | actuator:{int(v["steer"] / v["cnt"] * 100):#3}% | error: {round(v["err"] / v["cnt"], 2):2.2f}° | -:{int(v["-"] / v["cnt"] * 100):#3}% | =:{int(v["="] / v["cnt"] * 100):#3}% | +:{int(v["+"] / v["cnt"] * 100):#3}% | lim:{v["limited"]:#5} | sat:{v["saturated"]:#5} | path dev: {round(v["dpp"] / v["cnt"], 2):2.2f}m | total: {v["cnt"]:#5}') print("") + + +if __name__ == "__main__": + + parser = argparse.ArgumentParser(description='Steering accuracy measurement tool') + parser.add_argument('--route', help="route name") + parser.add_argument('--addr', default='127.0.0.1', help="IP address for optional ZMQ listener, default to msgq") + parser.add_argument('--group', default='all', help="speed group to display, [crawl|slow|medium|fast|veryfast|germany|all], default to all") + parser.add_argument('--cache', default=False, action='store_true', help="use cached data, default to False") + args = parser.parse_args() + + if args.cache: + os.environ['FILEREADER_CACHE'] = '1' + + tool = SteeringAccuracyTool(args) + + if args.route is not None: + print(f"loading {args.route}...") + lr = logreader_from_route_or_segment(args.route, sort_by_time=True) + + sm = {} + for msg in lr: + if msg.which() == 'carState': + sm['carState'] = msg.carState + elif msg.which() == 'carControl': + sm['carControl'] = msg.carControl + elif msg.which() == 'controlsState': + sm['controlsState'] = msg.controlsState + elif msg.which() == 'lateralPlan': + sm['lateralPlan'] = msg.lateralPlan + + if msg.which() == 'carControl' and 'carState' in sm and 'controlsState' in sm and 'lateralPlan' in sm: + tool.update(sm) + + else: + if args.addr != "127.0.0.1": + os.environ["ZMQ"] = "1" + messaging.context = messaging.Context() + + carControl = messaging.sub_sock('carControl', addr=args.addr, conflate=True) + sm = messaging.SubMaster(['carState', 'carControl', 'controlsState', 'lateralPlan'], addr=args.addr) + time.sleep(1) # Make sure all submaster data is available before going further + + print("waiting for messages...") + while messaging.recv_one(carControl): + sm.update() + tool.update(sm) diff --git a/tools/ubuntu_setup.sh b/tools/ubuntu_setup.sh index 7e021bcc23..71bad2e8a2 100755 --- a/tools/ubuntu_setup.sh +++ b/tools/ubuntu_setup.sh @@ -4,13 +4,23 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" ROOT="$(cd $DIR/../ && pwd)" +SUDO="" # NOTE: this is used in a docker build, so do not run any scripts here. +# Use sudo if not root +if [[ ! $(id -u) -eq 0 ]]; then + if [[ -z $(which sudo) ]]; then + echo "Please install sudo or run as root" + exit 1 + fi + SUDO="sudo" +fi + # Install packages present in all supported versions of Ubuntu function install_ubuntu_common_requirements() { - sudo apt-get update - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get update + $SUDO apt-get install -y --no-install-recommends \ autoconf \ build-essential \ ca-certificates \ @@ -46,6 +56,7 @@ function install_ubuntu_common_requirements() { libomp-dev \ libopencv-dev \ libpng16-16 \ + libportaudio2 \ libssl-dev \ libsqlite3-dev \ libusb-1.0-0-dev \ @@ -71,10 +82,11 @@ function install_ubuntu_common_requirements() { } # Install Ubuntu 22.04 LTS packages -function install_ubuntu_jammy_requirements() { +function install_ubuntu_lts_latest_requirements() { install_ubuntu_common_requirements - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get install -y --no-install-recommends \ + g++-12 \ qtbase5-dev \ qtchooser \ qt5-qmake \ @@ -86,7 +98,7 @@ function install_ubuntu_jammy_requirements() { function install_ubuntu_focal_requirements() { install_ubuntu_common_requirements - sudo apt-get install -y --no-install-recommends \ + $SUDO apt-get install -y --no-install-recommends \ libavresample-dev \ qt5-default \ python-dev @@ -97,7 +109,10 @@ if [ -f "/etc/os-release" ]; then source /etc/os-release case "$VERSION_CODENAME" in "jammy") - install_ubuntu_jammy_requirements + install_ubuntu_lts_latest_requirements + ;; + "kinetic") + install_ubuntu_lts_latest_requirements ;; "focal") install_ubuntu_focal_requirements @@ -109,8 +124,8 @@ if [ -f "/etc/os-release" ]; then if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1 fi - if [ "$UBUNTU_CODENAME" = "jammy" ]; then - install_ubuntu_jammy_requirements + if [ "$UBUNTU_CODENAME" = "jammy" ] || [ "$UBUNTU_CODENAME" = "kinetic" ]; then + install_ubuntu_lts_latest_requirements else install_ubuntu_focal_requirements fi diff --git a/tools/webcam/README.md b/tools/webcam/README.md index f896e19b86..237e07cdb6 100644 --- a/tools/webcam/README.md +++ b/tools/webcam/README.md @@ -18,7 +18,7 @@ git clone https://github.com/commaai/openpilot.git - Follow [this readme](https://github.com/commaai/openpilot/tree/master/tools) to install the requirements - Add line "export PYTHONPATH=$HOME/openpilot" to your ~/.bashrc - Install tensorflow 2.2 and nvidia drivers: nvidia-xxx/cuda10.0/cudnn7.6.5 -- Install [OpenCL Driver](http://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz) +- Install [OpenCL Driver](https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532/l_opencl_p_18.1.0.015.tgz) - Install [OpenCV4](https://www.pyimagesearch.com/2018/08/15/how-to-install-opencv-4-on-ubuntu/) (ignore the Python part) ## Build openpilot for webcam diff --git a/update_requirements.sh b/update_requirements.sh index 060dca76ef..b430df59e5 100755 --- a/update_requirements.sh +++ b/update_requirements.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" @@ -22,8 +22,13 @@ if [ -z "\$PYENV_ROOT" ]; then eval "\$(pyenv virtualenv-init -)" fi EOF + + # setup now without restarting shell + export PATH=$HOME/.pyenv/bin:$HOME/.pyenv/shims:$PATH + export PYENV_ROOT="$HOME/.pyenv" + eval "$(pyenv init -)" + eval "$(pyenv virtualenv-init -)" fi -source $RC_FILE export MAKEFLAGS="-j$(nproc)" @@ -40,32 +45,37 @@ fi eval "$(pyenv init --path)" echo "update pip" -pip install pip==21.3.1 -pip install pipenv==2021.11.23 +pip install pip==22.3.1 +pip install poetry==1.2.2 -if [ -d "./xx" ]; then - echo "WARNING: using xx Pipfile ******" - export PIPENV_SYSTEM=1 - export PIPENV_PIPFILE=./xx/Pipfile -fi +poetry config virtualenvs.prefer-active-python true --local -if [ -z "$PIPENV_SYSTEM" ]; then - echo "PYTHONPATH=${PWD}" > .env - RUN="pipenv run" -else - RUN="" +POETRY_INSTALL_ARGS="" +if [ -d "./xx" ] || [ -n "$XX" ]; then + echo "WARNING: using xx dependency group, installing globally" + poetry config virtualenvs.create false --local + POETRY_INSTALL_ARGS="--with xx --sync" fi echo "pip packages install..." -pipenv sync --dev -pipenv --clear +poetry install --no-cache --no-root $POETRY_INSTALL_ARGS pyenv rehash -echo "pre-commit hooks install..." -shopt -s nullglob -for f in .pre-commit-config.yaml */.pre-commit-config.yaml; do - cd $DIR/$(dirname $f) - if [ -e ".git" ]; then - $RUN pre-commit install - fi -done +if [ -d "./xx" ] || [ -n "$POETRY_VIRTUALENVS_CREATE" ]; then + RUN="" +else + echo "PYTHONPATH=${PWD}" > .env + poetry self add poetry-dotenv-plugin@^0.1.0 + RUN="poetry run" +fi + +if [ "$(uname)" != "Darwin" ]; then + echo "pre-commit hooks install..." + shopt -s nullglob + for f in .pre-commit-config.yaml */.pre-commit-config.yaml; do + cd $DIR/$(dirname $f) + if [ -e ".git" ]; then + $RUN pre-commit install + fi + done +fi