diff --git a/.github/workflows/auto_pr_review.yaml b/.github/workflows/auto_pr_review.yaml index 820801a2c7..abb6c38d6b 100644 --- a/.github/workflows/auto_pr_review.yaml +++ b/.github/workflows/auto_pr_review.yaml @@ -89,6 +89,13 @@ jobs: return subset.every((item) => superset.includes(item)); }; + // Utility function to check if a list of checkboxes is a subset of another list of checkboxes + isCheckboxSubset = (templateCheckBoxTexts, prTextCheckBoxTexts) => { + // Check if each template checkbox text is a substring of at least one PR checkbox text + // (user should be allowed to add additional text) + return templateCheckBoxTexts.every((item) => prTextCheckBoxTexts.some((element) => element.includes(item))) + } + // Get filenames of all currently checked-in PR templates const template_contents = await github.rest.repos.getContent({ owner: context.repo.owner, @@ -146,7 +153,7 @@ jobs: template.checkboxes + "]" ); if ( - isSubset(template.checkboxes, pr_checkboxes) && + isCheckboxSubset(template.checkboxes, pr_checkboxes) && isSubset(template.headings, pr_headings) ) { console.debug("Found matching template!"); diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 38dbf07a47..3e3fe46e7a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -47,7 +47,7 @@ jobs: export PYTHONPATH=$TARGET_DIR cd $TARGET_DIR scons -j$(nproc) - selfdrive/car/tests/test_car_interfaces.py + pytest -n logical selfdrive/car/tests/test_car_interfaces.py - name: Push master-ci run: | unset TARGET_DIR diff --git a/.github/workflows/repo-maintenance.yaml b/.github/workflows/repo-maintenance.yaml index bd882210fa..445a1cf11c 100644 --- a/.github/workflows/repo-maintenance.yaml +++ b/.github/workflows/repo-maintenance.yaml @@ -2,7 +2,7 @@ name: repo maintenance on: schedule: - - cron: "0 12 * * 1" # every Monday at 12am UTC (4am PST) + - cron: "0 14 * * 1" # every Monday at 2am UTC (6am PST) workflow_dispatch: jobs: @@ -11,6 +11,7 @@ jobs: runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest + if: github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 with: @@ -36,6 +37,7 @@ jobs: runs-on: ubuntu-20.04 container: image: ghcr.io/commaai/openpilot-base:latest + if: github.repository == 'commaai/openpilot' steps: - uses: actions/checkout@v4 - name: poetry lock diff --git a/.github/workflows/selfdrive_tests.yaml b/.github/workflows/selfdrive_tests.yaml index 964c36776e..d1dff147f2 100644 --- a/.github/workflows/selfdrive_tests.yaml +++ b/.github/workflows/selfdrive_tests.yaml @@ -14,7 +14,6 @@ concurrency: env: PYTHONWARNINGS: error BASE_IMAGE: openpilot-base - CL_BASE_IMAGE: openpilot-base-cl AZURE_TOKEN: ${{ secrets.AZURE_COMMADATACI_OPENPILOTCI_TOKEN }} DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} @@ -22,11 +21,7 @@ env: RUN: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PRE_COMMIT_HOME=/tmp/pre-commit -e PYTHONWARNINGS=error -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/pre-commit:/tmp/pre-commit -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c - BUILD_CL: selfdrive/test/docker_build.sh cl - - RUN_CL: docker run --shm-size 1G -v $PWD:/tmp/openpilot -w /tmp/openpilot -e CI=1 -e PYTHONWARNINGS=error -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c - - PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 + PYTEST: pytest --continue-on-collection-errors --cov --cov-report=xml --cov-append --durations=0 --durations-min=5 --hypothesis-seed 0 -n logical jobs: build_release: @@ -106,11 +101,6 @@ jobs: - uses: ./.github/workflows/setup-with-retry with: docker_hub_pat: ${{ secrets.DOCKER_HUB_PAT }} - - name: Build and push CL Docker image - if: matrix.arch == 'x86_64' - run: | - unset TARGET_ARCHITECTURE - eval "$BUILD_CL" docker_push_multiarch: name: docker push multiarch tag @@ -127,7 +117,7 @@ jobs: - name: Merge x64 and arm64 tags run: | export PUSH_IMAGE=true - selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64 + scripts/retry.sh selfdrive/test/docker_tag_multiarch.sh base x86_64 aarch64 static_analysis: name: static analysis @@ -182,7 +172,7 @@ jobs: run: | ${{ env.RUN }} "source selfdrive/test/setup_xvfb.sh && \ export MAPBOX_TOKEN='pk.eyJ1Ijoiam5ld2IiLCJhIjoiY2xxNW8zZXprMGw1ZzJwbzZneHd2NHljbSJ9.gV7VPRfbXFetD-1OVF0XZg' && \ - $PYTEST --timeout 60 -m 'not slow' -n $(nproc) && \ + $PYTEST --timeout 60 -m 'not slow' && \ ./selfdrive/ui/tests/create_test_translations.sh && \ QT_QPA_PLATFORM=offscreen ./selfdrive/ui/tests/test_translations && \ ./selfdrive/ui/tests/test_translations.py" @@ -258,15 +248,13 @@ jobs: key: regen-${{ hashFiles('.github/workflows/selfdrive_tests.yaml', 'selfdrive/test/process_replay/test_regen.py') }} - name: Build base Docker image run: eval "$BUILD" - - name: Build Docker image - run: eval "$BUILD_CL" - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" - name: Run regen timeout-minutes: 30 run: | - ${{ env.RUN_CL }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ + ${{ env.RUN }} "ONNXCPU=1 $PYTEST selfdrive/test/process_replay/test_regen.py && \ chmod -R 777 /tmp/comma_download_cache" test_modeld: @@ -279,9 +267,6 @@ jobs: - uses: ./.github/workflows/setup-with-retry - name: Build base Docker image run: eval "$BUILD" - - name: Build Docker image - # Sim docker is needed to get the OpenCL drivers - run: eval "$BUILD_CL" - name: Build openpilot run: | ${{ env.RUN }} "scons -j$(nproc)" @@ -289,14 +274,14 @@ jobs: - name: Run model replay with ONNX timeout-minutes: 4 run: | - ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ + ${{ env.RUN }} "unset PYTHONWARNINGS && \ ONNXCPU=1 NO_NAV=1 coverage run selfdrive/test/process_replay/model_replay.py && \ coverage combine && \ coverage xml" - name: Run unit tests timeout-minutes: 4 run: | - ${{ env.RUN_CL }} "unset PYTHONWARNINGS && \ + ${{ env.RUN }} "unset PYTHONWARNINGS && \ $PYTEST selfdrive/modeld" - name: "Upload coverage to Codecov" uses: codecov/codecov-action@v3 @@ -328,7 +313,7 @@ jobs: - name: Build openpilot run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test car models - timeout-minutes: 25 + timeout-minutes: 10 run: | ${{ env.RUN }} "$PYTEST selfdrive/car/tests/test_models.py && \ chmod -R 777 /tmp/comma_download_cache" diff --git a/.github/workflows/tools_tests.yaml b/.github/workflows/tools_tests.yaml index c185fd1d3c..d15ab8cd60 100644 --- a/.github/workflows/tools_tests.yaml +++ b/.github/workflows/tools_tests.yaml @@ -12,17 +12,12 @@ concurrency: env: BASE_IMAGE: openpilot-base - CL_BASE_IMAGE: openpilot-base-cl DOCKER_LOGIN: docker login ghcr.io -u ${{ github.actor }} -p ${{ secrets.GITHUB_TOKEN }} BUILD: selfdrive/test/docker_build.sh base RUN: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e FILEREADER_CACHE=1 -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $BASE_IMAGE /bin/bash -c - BUILD_CL: selfdrive/test/docker_build.sh cl - - RUN_CL: docker run --shm-size 1G -v $GITHUB_WORKSPACE:/tmp/openpilot -w /tmp/openpilot -e PYTHONPATH=/tmp/openpilot -e NUM_JOBS -e JOB_ID -e GITHUB_ACTION -e GITHUB_REF -e GITHUB_HEAD_REF -e GITHUB_SHA -e GITHUB_REPOSITORY -e GITHUB_RUN_ID -v $GITHUB_WORKSPACE/.ci_cache/scons_cache:/tmp/scons_cache -v $GITHUB_WORKSPACE/.ci_cache/comma_download_cache:/tmp/comma_download_cache -v $GITHUB_WORKSPACE/.ci_cache/openpilot_cache:/tmp/openpilot_cache $CL_BASE_IMAGE /bin/bash -c - jobs: plotjuggler: @@ -52,8 +47,6 @@ jobs: with: submodules: true - uses: ./.github/workflows/setup-with-retry - - name: Build base cl image - run: eval "$BUILD_CL" - name: Setup to push to repo if: github.ref == 'refs/heads/master' && github.repository == 'commaai/openpilot' run: | @@ -104,6 +97,6 @@ jobs: timeout-minutes: ${{ ((steps.restore-scons-cache.outputs.cache-hit == 'true') && 10 || 30) }} # allow more time when we missed the scons cache run: ${{ env.RUN }} "scons -j$(nproc)" - name: Test notebooks - timeout-minutes: 2 + timeout-minutes: 3 run: | ${{ env.RUN }} "pip install nbmake && pytest --nbmake tools/car_porting/examples/" \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3e91531d08..3da75aaea4 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,6 @@ venv/ .overlay_init .overlay_consistent .sconsign.dblite -.vscode* model2.png a.out .hypothesis diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index fa05b5b8a2..6db68e55bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -10,7 +10,7 @@ repos: - id: check-ast exclude: '^(third_party)/' - id: check-json - exclude: '.devcontainer/devcontainer.json' # this supports JSON with comments + exclude: '.devcontainer/devcontainer.json|.vscode/' # these support JSON with comments - id: check-toml - id: check-xml - id: check-yaml @@ -32,6 +32,11 @@ repos: # if you've got a short variable name that's getting flagged, add it here - -L bu,ro,te,ue,alo,hda,ois,nam,nams,ned,som,parm,setts,inout,warmup,bumb,nd,sie,preints - --builtins clear,rare,informal,usage,code,names,en-GB_to_en-US +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.2.2 + hooks: + - id: ruff + exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' - repo: local hooks: - id: mypy @@ -43,11 +48,6 @@ repos: - --local-partial-types - --explicit-package-bases exclude: '^(third_party/)|(cereal/)|(opendbc/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)|(xx/)' -- repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.14 - hooks: - - id: ruff - exclude: '^(third_party/)|(cereal/)|(panda/)|(rednose/)|(rednose_repo/)|(tinygrad/)|(tinygrad_repo/)|(teleoprtc/)|(teleoprtc_repo/)' - repo: local hooks: - id: cppcheck @@ -74,6 +74,14 @@ repos: # https://google.github.io/styleguide/cppguide.html # relevant rules are whitelisted, see all options with: cpplint --filter= - --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces +- repo: https://github.com/MarcoGorelli/cython-lint + rev: v0.16.0 + hooks: + - id: cython-lint + exclude: '^(third_party/)|(cereal/)|(body/)|(rednose/)|(rednose_repo/)|(opendbc/)|(panda/)|(generated/)' + args: + - --max-line-length=240 + - --ignore=E111, E302, E305 - repo: local hooks: - id: test_translations @@ -83,13 +91,13 @@ repos: pass_filenames: false files: 'selfdrive/ui/translations/*' - repo: https://github.com/python-poetry/poetry - rev: '1.7.0' + rev: '1.8.0' hooks: - id: poetry-check name: validate poetry lock args: - --lock - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.27.3 + rev: 0.28.0 hooks: - id: check-github-workflows diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..458312fc88 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "ms-python.python", + "ms-vscode.cpptools", + "elagil.pre-commit-helper", + "charliermarsh.ruff", + ] +} diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000000..3b3953c3f4 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,47 @@ +{ + "version": "0.2.0", + "inputs": [ + { + "id": "python_process", + "type": "pickString", + "description": "Select the process to debug", + "options": [ + "selfdrive/controls/controlsd.py", + "selfdrive/navd/navd.py", + "system/timed/timed.py", + "tools/sim/run_bridge.py" + ] + }, + { + "id": "cpp_process", + "type": "pickString", + "description": "Select the process to debug", + "options": [ + "selfdrive/ui/ui" + ] + }, + { + "id": "args", + "description": "Arguments to pass to the process", + "type": "promptString" + } + ], + "configurations": [ + { + "name": "Python: openpilot Process", + "type": "debugpy", + "request": "launch", + "program": "${input:python_process}", + "console": "integratedTerminal", + "justMyCode": true, + "args": "${input:args}" + }, + { + "name": "C++: openpilot Process", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/${input:cpp_process}", + "cwd": "${workspaceFolder}", + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..daf74ca777 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,16 @@ +{ + "editor.tabSize": 2, + "editor.insertSpaces": true, + "editor.renderWhitespace": "trailing", + "files.trimTrailingWhitespace": true, + "search.exclude": { + "**/.git": true, + "**/.venv": true, + "**/__pycache__": true + }, + "files.exclude": { + "**/.git": true, + "**/.venv": true, + "**/__pycache__": true + } +} \ No newline at end of file diff --git a/Dockerfile.openpilot_base b/Dockerfile.openpilot_base index d280d2c9ec..a6c4c71790 100644 --- a/Dockerfile.openpilot_base +++ b/Dockerfile.openpilot_base @@ -22,6 +22,44 @@ RUN cd /tmp && \ rm -rf arm/ && \ rm -rf thumb/nofp thumb/v6* thumb/v8* thumb/v7+fp thumb/v7-r+fp.sp +# Add OpenCL +RUN apt-get update && apt-get install -y --no-install-recommends \ + apt-utils \ + alien \ + unzip \ + tar \ + curl \ + xz-utils \ + dbus \ + gcc-arm-none-eabi \ + tmux \ + vim \ + lsb-core \ + libx11-6 \ + && rm -rf /var/lib/apt/lists/* + +ARG INTEL_DRIVER=l_opencl_p_18.1.0.015.tgz +ARG INTEL_DRIVER_URL=https://registrationcenter-download.intel.com/akdlm/irc_nas/vcp/15532 +RUN mkdir -p /tmp/opencl-driver-intel + +RUN cd /tmp/opencl-driver-intel && \ + echo INTEL_DRIVER is $INTEL_DRIVER && \ + curl -O $INTEL_DRIVER_URL/$INTEL_DRIVER && \ + tar -xzf $INTEL_DRIVER && \ + for i in $(basename $INTEL_DRIVER .tgz)/rpm/*.rpm; do alien --to-deb $i; done && \ + dpkg -i *.deb && \ + rm -rf $INTEL_DRIVER $(basename $INTEL_DRIVER .tgz) *.deb && \ + mkdir -p /etc/OpenCL/vendors && \ + echo /opt/intel/opencl_compilers_and_libraries_18.1.0.015/linux/compiler/lib/intel64_lin/libintelocl.so > /etc/OpenCL/vendors/intel.icd && \ + cd / && \ + rm -rf /tmp/opencl-driver-intel + +ENV NVIDIA_VISIBLE_DEVICES all +ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute +ENV QTWEBENGINE_DISABLE_SANDBOX 1 + +RUN dbus-uuidgen > /etc/machine-id + ARG USER=batman ARG USER_UID=1000 RUN useradd -m -s /bin/bash -u $USER_UID $USER diff --git a/Dockerfile.openpilot_base_cl b/Dockerfile.openpilot_base_cl deleted file mode 100644 index 4c8ecfc78d..0000000000 --- a/Dockerfile.openpilot_base_cl +++ /dev/null @@ -1,37 +0,0 @@ -FROM ghcr.io/commaai/openpilot-base:latest - -RUN apt-get update && apt-get install -y --no-install-recommends\ - apt-utils \ - alien \ - unzip \ - tar \ - curl \ - xz-utils \ - dbus \ - gcc-arm-none-eabi \ - tmux \ - vim \ - lsb-core \ - libx11-6 \ - && rm -rf /var/lib/apt/lists/* - -# Intel OpenCL driver -ARG INTEL_DRIVER=l_opencl_p_18.1.0.015.tgz -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 && \ - curl -O $INTEL_DRIVER_URL/$INTEL_DRIVER && \ - tar -xzf $INTEL_DRIVER && \ - for i in $(basename $INTEL_DRIVER .tgz)/rpm/*.rpm; do alien --to-deb $i; done && \ - dpkg -i *.deb && \ - rm -rf $INTEL_DRIVER $(basename $INTEL_DRIVER .tgz) *.deb && \ - mkdir -p /etc/OpenCL/vendors && \ - echo /opt/intel/opencl_compilers_and_libraries_18.1.0.015/linux/compiler/lib/intel64_lin/libintelocl.so > /etc/OpenCL/vendors/intel.icd && \ - rm -rf /tmp/opencl-driver-intel -ENV NVIDIA_VISIBLE_DEVICES all -ENV NVIDIA_DRIVER_CAPABILITIES graphics,utility,compute -ENV QTWEBENGINE_DISABLE_SANDBOX 1 - -RUN dbus-uuidgen > /etc/machine-id - diff --git a/Jenkinsfile b/Jenkinsfile index e45bcd16b2..d716510bfe 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -12,10 +12,12 @@ def retryWithDelay(int maxRetries, int delay, Closure body) { def device(String ip, String step_label, String cmd) { withCredentials([file(credentialsId: 'id_rsa', variable: 'key_file')]) { def ssh_cmd = """ -ssh -tt -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' +ssh -tt -o ConnectTimeout=5 -o ServerAliveInterval=5 -o ServerAliveCountMax=2 -o BatchMode=yes -o StrictHostKeyChecking=no -i ${key_file} 'comma@${ip}' /usr/bin/bash <<'END' set -e +shopt -s huponexit # kill all child processes when the shell exits + export CI=1 export PYTHONWARNINGS=error export LOGPRINT=debug @@ -78,17 +80,13 @@ def deviceStage(String stageName, String deviceType, List extra_env, def steps) def extra = extra_env.collect { "export ${it}" }.join('\n'); def branch = env.BRANCH_NAME ?: 'master'; - docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') { - lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1) { + lock(resource: "", label: deviceType, inversePrecedence: true, variable: 'device_ip', quantity: 1, resourceSelectStrategy: 'random') { + docker.image('ghcr.io/commaai/alpine-ssh').inside('--user=root') { timeout(time: 20, unit: 'MINUTES') { retry (3) { device(device_ip, "git checkout", extra + "\n" + readFile("selfdrive/test/setup_device_ci.sh")) } steps.each { item -> - if (branch != "master" && item.size() == 3 && !hasDirectoryChanged(item[2])) { - println "Skipped '${item[0]}', no relevant changes were detected." - return; - } device(device_ip, item[0], item[1]) } } @@ -106,7 +104,7 @@ def pcStage(String stageName, Closure body) { checkout scm - def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE}"; + def dockerArgs = "--user=batman -v /tmp/comma_download_cache:/tmp/comma_download_cache -v /tmp/scons_cache:/tmp/scons_cache -e PYTHONPATH=${env.WORKSPACE} --cpus=8 --memory 16g -e PYTEST_ADDOPTS='-n8'"; def openpilot_base = retryWithDelay (3, 15) { return docker.build("openpilot-base:build-${env.GIT_COMMIT}", "-f Dockerfile.openpilot_base .") @@ -143,20 +141,6 @@ def setupCredentials() { } } -def hasDirectoryChanged(List paths) { - for (change in currentBuild.changeSets) { - for (item in change.items) { - for (affectedPath in item.affectedPaths) { - for (path in paths) { - if (affectedPath.startsWith(path)) { - return true - } - } - } - } - } - return false -} node { env.CI = "1" @@ -205,17 +189,17 @@ node { ]) }, 'HW + Unit Tests': { - deviceStage("tici", "tici-common", ["UNSAFE=1"], [ + deviceStage("tici-hardware", "tici-common", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py", ["panda/", "selfdrive/boardd/"]], - ["test power draw", "./system/hardware/tici/tests/test_power_draw.py"], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], + ["test power draw", "pytest -s system/hardware/tici/tests/test_power_draw.py"], ["test encoder", "LD_LIBRARY_PATH=/usr/local/lib pytest system/loggerd/tests/test_encoder.py"], ["test pigeond", "pytest system/sensord/tests/test_pigeond.py"], ["test manager", "pytest selfdrive/manager/test/test_manager.py"], ]) }, 'loopback': { - deviceStage("tici", "tici-loopback", ["UNSAFE=1"], [ + deviceStage("loopback", "tici-loopback", ["UNSAFE=1"], [ ["build openpilot", "cd selfdrive/manager && ./build.py"], ["test boardd loopback", "pytest selfdrive/boardd/tests/test_boardd_loopback.py"], ]) @@ -243,7 +227,7 @@ node { ]) }, 'replay': { - deviceStage("tici", "tici-replay", ["UNSAFE=1"], [ + deviceStage("model-replay", "tici-replay", ["UNSAFE=1"], [ ["build", "cd selfdrive/manager && ./build.py"], ["model replay", "selfdrive/test/process_replay/model_replay.py"], ]) @@ -252,31 +236,13 @@ node { deviceStage("tizi", "tizi", ["UNSAFE=1"], [ ["build openpilot", "cd selfdrive/manager && ./build.py"], ["test boardd loopback", "SINGLE_PANDA=1 pytest selfdrive/boardd/tests/test_boardd_loopback.py"], - ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py", ["panda/", "selfdrive/boardd/"]], + ["test pandad", "pytest selfdrive/boardd/tests/test_pandad.py"], ["test amp", "pytest system/hardware/tici/tests/test_amplifier.py"], ["test hw", "pytest system/hardware/tici/tests/test_hardware.py"], - ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py", ["system/qcomgpsd/"]], + ["test qcomgpsd", "pytest system/qcomgpsd/tests/test_qcomgpsd.py"], ]) }, - // *** PC tests *** - 'PC tests': { - pcStage("PC tests") { - // tests that our build system's dependencies are configured properly, - // needs a machine with lots of cores - sh label: "test multi-threaded build", - script: '''#!/bin/bash - scons --no-cache --random -j$(nproc)''' - } - }, - 'car tests': { - pcStage("car tests") { - sh label: "build", script: "selfdrive/manager/build.py" - sh label: "run car tests", script: "cd selfdrive/car/tests && MAX_EXAMPLES=300 INTERNAL_SEG_CNT=300 FILEREADER_CACHE=1 \ - INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt pytest test_models.py test_car_interfaces.py" - } - }, - ) } } catch (Exception e) { diff --git a/RELEASES.md b/RELEASES.md index aa3a80d56d..d0fd530db1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,4 +1,9 @@ -Version 0.9.6 (2024-02-XX) +Version 0.9.7 (2024-XX-XX) +======================== +* New driving model +* Support for many hybrid Ford models + +Version 0.9.6 (2024-02-27) ======================== * New driving model * Vision model trained on more data @@ -9,8 +14,12 @@ Version 0.9.6 (2024-02-XX) * AGNOS 9 * comma body streaming and controls over WebRTC * Improved fuzzy fingerprinting for many makes and models +* Alpha longitudinal support for new Toyota models +* Chevrolet Equinox 2019-22 support thanks to JasonJShuler and nworb-cire! +* Dodge Durango 2020-21 support * Hyundai Staria 2023 support thanks to sunnyhaibin! * Kia Niro Plug-in Hybrid 2022 support thanks to sunnyhaibin! +* Lexus LC 2024 support thanks to nelsonjchen! * Toyota RAV4 2023-24 support * Toyota RAV4 Hybrid 2023-24 support diff --git a/SConstruct b/SConstruct index 3faa978087..b04e3903ed 100644 --- a/SConstruct +++ b/SConstruct @@ -145,7 +145,6 @@ else: libpath = [ f"#third_party/acados/{arch}/lib", f"#third_party/libyuv/{arch}/lib", - f"#third_party/mapbox-gl-native-qt/{arch}", "/usr/lib", "/usr/local/lib", ] @@ -208,11 +207,12 @@ env = Environment( "#third_party/json11", "#third_party/linux/include", "#third_party/snpe/include", - "#third_party/mapbox-gl-native-qt/include", "#third_party/qrcode", "#third_party", "#cereal", "#opendbc/can", + "#third_party/maplibre-native-qt/include", + f"#third_party/maplibre-native-qt/{arch}/include" ], CC='clang', @@ -318,7 +318,7 @@ try: except SCons.Errors.UserError: qt_env.Tool('qt') -qt_env['CPPPATH'] += qt_dirs + ["#selfdrive/ui/qt/"] +qt_env['CPPPATH'] += qt_dirs# + ["#selfdrive/ui/qt/"] qt_flags = [ "-D_REENTRANT", "-DQT_NO_DEBUG", @@ -331,7 +331,8 @@ qt_flags = [ "-DQT_MESSAGELOGCONTEXT", ] qt_env['CXXFLAGS'] += qt_flags -qt_env['LIBPATH'] += ['#selfdrive/ui'] +qt_env['LIBPATH'] += ['#selfdrive/ui', f"#third_party/maplibre-native-qt/{arch}/lib"] +qt_env['RPATH'] += [Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath] qt_env['LIBS'] = qt_libs if GetOption("clazy"): diff --git a/cereal b/cereal index a6ade85c9d..a4255106b7 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit a6ade85c9dd6652fde547b9e089a297f67606dcf +Subproject commit a4255106b7255e00ae04162f7aa14aa3cae339c3 diff --git a/common/file_helpers.py b/common/file_helpers.py index dea298a529..29ad219c07 100644 --- a/common/file_helpers.py +++ b/common/file_helpers.py @@ -1,7 +1,6 @@ import os import tempfile import contextlib -from typing import Optional class CallbackReader: @@ -24,7 +23,7 @@ class CallbackReader: @contextlib.contextmanager -def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: Optional[str] = None, newline: Optional[str] = None, +def atomic_write_in_dir(path: str, mode: str = 'w', buffering: int = -1, encoding: str = None, newline: str = None, overwrite: bool = False): """Write to a file atomically using a temporary file in the same directory as the destination file.""" dir_name = os.path.dirname(path) diff --git a/common/gpio.py b/common/gpio.py index 88a9479a60..68932cb87a 100644 --- a/common/gpio.py +++ b/common/gpio.py @@ -1,6 +1,5 @@ import os from functools import lru_cache -from typing import Optional, List def gpio_init(pin: int, output: bool) -> None: try: @@ -16,7 +15,7 @@ def gpio_set(pin: int, high: bool) -> None: except Exception as e: print(f"Failed to set gpio {pin} value: {e}") -def gpio_read(pin: int) -> Optional[bool]: +def gpio_read(pin: int) -> bool | None: val = None try: with open(f"/sys/class/gpio/gpio{pin}/value", 'rb') as f: @@ -37,7 +36,7 @@ def gpio_export(pin: int) -> None: print(f"Failed to export gpio {pin}") @lru_cache(maxsize=None) -def get_irq_action(irq: int) -> List[str]: +def get_irq_action(irq: int) -> list[str]: try: with open(f"/sys/kernel/irq/{irq}/actions") as f: actions = f.read().strip().split(',') @@ -45,7 +44,7 @@ def get_irq_action(irq: int) -> List[str]: except FileNotFoundError: return [] -def get_irqs_for_action(action: str) -> List[str]: +def get_irqs_for_action(action: str) -> list[str]: ret = [] with open("/proc/interrupts") as f: for l in f.readlines(): diff --git a/common/mock/__init__.py b/common/mock/__init__.py new file mode 100644 index 0000000000..8c86bbd394 --- /dev/null +++ b/common/mock/__init__.py @@ -0,0 +1,50 @@ +""" +Utilities for generating mock messages for testing. +example in common/tests/test_mock.py +""" + + +import functools +import threading +from cereal.messaging import PubMaster +from cereal.services import SERVICE_LIST +from openpilot.common.mock.generators import generate_liveLocationKalman +from openpilot.common.realtime import Ratekeeper + + +MOCK_GENERATOR = { + "liveLocationKalman": generate_liveLocationKalman +} + + +def generate_messages_loop(services: list[str], done: threading.Event): + pm = PubMaster(services) + rk = Ratekeeper(100) + i = 0 + while not done.is_set(): + for s in services: + should_send = i % (100/SERVICE_LIST[s].frequency) == 0 + if should_send: + message = MOCK_GENERATOR[s]() + pm.send(s, message) + i += 1 + rk.keep_time() + + +def mock_messages(services: list[str] | str): + if isinstance(services, str): + services = [services] + + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + done = threading.Event() + t = threading.Thread(target=generate_messages_loop, args=(services, done)) + t.start() + try: + return func(*args, **kwargs) + finally: + done.set() + t.join() + return wrapper + return decorator diff --git a/common/mock/generators.py b/common/mock/generators.py new file mode 100644 index 0000000000..40951faf85 --- /dev/null +++ b/common/mock/generators.py @@ -0,0 +1,20 @@ +from cereal import messaging + + +LOCATION1 = (32.7174, -117.16277) +LOCATION2 = (32.7558, -117.2037) + +LLK_DECIMATION = 10 +RENDER_FRAMES = 15 +DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION + + +def generate_liveLocationKalman(location=LOCATION1): + msg = messaging.new_message('liveLocationKalman') + msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} + msg.liveLocationKalman.status = 'valid' + msg.liveLocationKalman.gpsOK = True + return msg diff --git a/common/params.cc b/common/params.cc index 3ce2505243..eb75705ca3 100644 --- a/common/params.cc +++ b/common/params.cc @@ -125,6 +125,7 @@ std::unordered_map keys = { {"ForcePowerDown", PERSISTENT}, {"GitBranch", PERSISTENT}, {"GitCommit", PERSISTENT}, + {"GitCommitDate", PERSISTENT}, {"GitDiff", PERSISTENT}, {"GithubSshKeys", PERSISTENT}, {"GithubUsername", PERSISTENT}, diff --git a/common/params_pyx.pyx b/common/params_pyx.pyx index 47d2075df2..535514e521 100644 --- a/common/params_pyx.pyx +++ b/common/params_pyx.pyx @@ -29,7 +29,7 @@ cdef extern from "common/params.h": def ensure_bytes(v): - return v.encode() if isinstance(v, str) else v; + return v.encode() if isinstance(v, str) else v class UnknownKeyName(Exception): pass diff --git a/common/prefix.py b/common/prefix.py index d027e3e5a1..4059ac09e2 100644 --- a/common/prefix.py +++ b/common/prefix.py @@ -2,14 +2,13 @@ import os import shutil import uuid -from typing import Optional from openpilot.common.params import Params from openpilot.system.hardware.hw import Paths from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT class OpenpilotPrefix: - def __init__(self, prefix: Optional[str] = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): + def __init__(self, prefix: str = None, clean_dirs_on_exit: bool = True, shared_download_cache: bool = False): self.prefix = prefix if prefix else str(uuid.uuid4().hex[0:15]) self.msgq_path = os.path.join('/dev/shm', self.prefix) self.clean_dirs_on_exit = clean_dirs_on_exit diff --git a/common/realtime.py b/common/realtime.py index a398146166..6b8587ff06 100644 --- a/common/realtime.py +++ b/common/realtime.py @@ -3,7 +3,6 @@ import gc import os import time from collections import deque -from typing import Optional, List, Union from setproctitle import getproctitle @@ -33,12 +32,12 @@ def set_realtime_priority(level: int) -> None: os.sched_setscheduler(0, os.SCHED_FIFO, os.sched_param(level)) -def set_core_affinity(cores: List[int]) -> None: +def set_core_affinity(cores: list[int]) -> None: if not PC: os.sched_setaffinity(0, cores) -def config_realtime_process(cores: Union[int, List[int]], priority: int) -> None: +def config_realtime_process(cores: int | list[int], priority: int) -> None: gc.disable() set_realtime_priority(priority) c = cores if isinstance(cores, list) else [cores, ] @@ -46,7 +45,7 @@ def config_realtime_process(cores: Union[int, List[int]], priority: int) -> None class Ratekeeper: - def __init__(self, rate: float, print_delay_threshold: Optional[float] = 0.0) -> None: + def __init__(self, rate: float, print_delay_threshold: float | None = 0.0) -> None: """Rate in Hz for ratekeeping. print_delay_threshold must be nonnegative.""" self._interval = 1. / rate self._next_frame_time = time.monotonic() + self._interval diff --git a/common/swaglog.cc b/common/swaglog.cc index 873836b725..7864a6355a 100644 --- a/common/swaglog.cc +++ b/common/swaglog.cc @@ -41,6 +41,15 @@ public: if (char* dongle_id = getenv("DONGLE_ID")) { ctx_j["dongle_id"] = dongle_id; } + if (char* git_origin = getenv("GIT_ORIGIN")) { + ctx_j["origin"] = git_origin; + } + if (char* git_branch = getenv("GIT_BRANCH")) { + ctx_j["branch"] = git_branch; + } + if (char* git_commit = getenv("GIT_COMMIT")) { + ctx_j["commit"] = git_commit; + } if (char* daemon_name = getenv("MANAGER_DAEMON")) { ctx_j["daemon"] = daemon_name; } diff --git a/common/transformations/orientation.py b/common/transformations/orientation.py index ce4378738d..86e6a6c347 100644 --- a/common/transformations/orientation.py +++ b/common/transformations/orientation.py @@ -1,5 +1,5 @@ import numpy as np -from typing import Callable +from collections.abc import Callable from openpilot.common.transformations.transformations import (ecef_euler_from_ned_single, euler2quat_single, diff --git a/common/transformations/transformations.pxd b/common/transformations/transformations.pxd index 7af0098701..964adf06ec 100644 --- a/common/transformations/transformations.pxd +++ b/common/transformations/transformations.pxd @@ -1,4 +1,4 @@ -#cython: language_level=3 +# cython: language_level=3 from libcpp cimport bool cdef extern from "orientation.cc": diff --git a/common/transformations/transformations.pyx b/common/transformations/transformations.pyx index c5cb9e0056..ae045c369d 100644 --- a/common/transformations/transformations.pyx +++ b/common/transformations/transformations.pyx @@ -17,7 +17,6 @@ from openpilot.common.transformations.transformations cimport ecef2geodetic as e from openpilot.common.transformations.transformations cimport LocalCoord_c -import cython import numpy as np cimport numpy as np @@ -34,14 +33,14 @@ cdef Matrix3 numpy2matrix(np.ndarray[double, ndim=2, mode="fortran"] m): return Matrix3(m.data) cdef ECEF list2ecef(ecef): - cdef ECEF e; + cdef ECEF e e.x = ecef[0] e.y = ecef[1] e.z = ecef[2] return e cdef NED list2ned(ned): - cdef NED n; + cdef NED n n.n = ned[0] n.e = ned[1] n.d = ned[2] @@ -61,7 +60,7 @@ def euler2quat_single(euler): def quat2euler_single(quat): cdef Quaternion q = Quaternion(quat[0], quat[1], quat[2], quat[3]) - cdef Vector3 e = quat2euler_c(q); + cdef Vector3 e = quat2euler_c(q) return [e(0), e(1), e(2)] def quat2rot_single(quat): diff --git a/common/version.h b/common/version.h index 787bc897d7..177882e31d 100644 --- a/common/version.h +++ b/common/version.h @@ -1 +1 @@ -#define COMMA_VERSION "0.9.6" +#define COMMA_VERSION "0.9.7" diff --git a/conftest.py b/conftest.py index c4eb259a35..0d2a4a8fc4 100644 --- a/conftest.py +++ b/conftest.py @@ -1,10 +1,12 @@ +import contextlib +import gc import os import pytest import random from openpilot.common.prefix import OpenpilotPrefix from openpilot.selfdrive.manager import manager -from openpilot.system.hardware import TICI +from openpilot.system.hardware import TICI, HARDWARE def pytest_sessionstart(session): @@ -24,45 +26,61 @@ def pytest_runtest_call(item): yield -@pytest.fixture(scope="function", autouse=True) -def openpilot_function_fixture(request): +@contextlib.contextmanager +def clean_env(): starting_env = dict(os.environ) + yield + os.environ.clear() + os.environ.update(starting_env) + +@pytest.fixture(scope="function", autouse=True) +def openpilot_function_fixture(request): random.seed(0) - # setup a clean environment for each test - with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: - prefix = os.environ["OPENPILOT_PREFIX"] + with clean_env(): + # setup a clean environment for each test + with OpenpilotPrefix(shared_download_cache=request.node.get_closest_marker("shared_download_cache") is not None) as prefix: + prefix = os.environ["OPENPILOT_PREFIX"] - yield + yield - # ensure the test doesn't change the prefix - assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"] + # ensure the test doesn't change the prefix + assert "OPENPILOT_PREFIX" in os.environ and prefix == os.environ["OPENPILOT_PREFIX"] - os.environ.clear() - os.environ.update(starting_env) + # cleanup any started processes + manager.manager_cleanup() - # cleanup any started processes - manager.manager_cleanup() + # some processes disable gc for performance, re-enable here + if not gc.isenabled(): + gc.enable() + gc.collect() # If you use setUpClass, the environment variables won't be cleared properly, # so we need to hook both the function and class pytest fixtures @pytest.fixture(scope="class", autouse=True) def openpilot_class_fixture(): - starting_env = dict(os.environ) + with clean_env(): + yield - yield - os.environ.clear() - os.environ.update(starting_env) +@pytest.fixture(scope="function") +def tici_setup_fixture(openpilot_function_fixture): + """Ensure a consistent state for tests on-device. Needs the openpilot function fixture to run first.""" + HARDWARE.initialize_hardware() + HARDWARE.set_power_save(False) + os.system("pkill -9 -f athena") @pytest.hookimpl(tryfirst=True) def pytest_collection_modifyitems(config, items): skipper = pytest.mark.skip(reason="Skipping tici test on PC") for item in items: - if not TICI and "tici" in item.keywords: - item.add_marker(skipper) + if "tici" in item.keywords: + if not TICI: + item.add_marker(skipper) + else: + item.fixturenames.append('tici_setup_fixture') if "xdist_group_class_property" in item.keywords: class_property_name = item.get_closest_marker('xdist_group_class_property').args[0] diff --git a/docs/CARS.md b/docs/CARS.md index 5620bc703c..7ad12189cb 100644 --- a/docs/CARS.md +++ b/docs/CARS.md @@ -4,7 +4,7 @@ A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified. -# 275 Supported Cars +# 288 Supported Cars |Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|Hardware Needed
 |Video| |---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:| @@ -23,6 +23,7 @@ A supported vehicle is one that just works when you install a comma device. All |Cadillac|Escalade ESV 2019[4](#footnotes)|Adaptive Cruise Control (ACC) & LKAS|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Chevrolet|Equinox 2019-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Silverado 1500 2020-21|Safety Package II|openpilot available[1](#footnotes)|0 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Trailblazer 2021-22|Adaptive Cruise Control (ACC)|openpilot available[1](#footnotes)|3 mph|6 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 GM connector
- 1 comma 3X
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chevrolet|Volt 2017-18[4](#footnotes)|Adaptive Cruise Control (ACC)|openpilot|0 mph|7 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 OBD-II connector
- 1 comma 3X
- 2 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -33,13 +34,22 @@ A supported vehicle is one that just works when you install a comma device. All |Chrysler|Pacifica Hybrid 2018|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Chrysler|Pacifica Hybrid 2019-23|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |comma|body|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|None|| +|Dodge|Durango 2020-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 FCA connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Bronco Sport 2021-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Escape 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Escape Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Escape Plug-in Hybrid 2020-22|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Explorer 2020-23|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Explorer Hybrid 2020-23|Co-Pilot360 Assist+|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Focus 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Focus Hybrid 2018[3](#footnotes)|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Kuga 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Kuga Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Kuga Plug-in Hybrid 2020-22|Adaptive Cruise Control with Lane Centering|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2022|LARIAT Luxury|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Ford|Maverick 2023|Co-Pilot360 Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Maverick Hybrid 2022|LARIAT Luxury|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Ford|Maverick Hybrid 2023|Co-Pilot360 Assist|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G70 2018-19|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G70 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Genesis|G80 2017|All|Stock|19 mph|37 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai J connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -72,7 +82,7 @@ A supported vehicle is one that just works when you install a comma device. All |Honda|Odyssey 2018-20|Honda Sensing|openpilot|25 mph|0 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Passport 2019-23|All|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Honda|Pilot 2016-22|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Honda|Ridgeline 2017-23|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Honda|Ridgeline 2017-24|Honda Sensing|openpilot|25 mph|12 mph|[![star](assets/icon-star-empty.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 Honda Nidec connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2019|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Hyundai|Azera Hybrid 2020|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai K connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -138,6 +148,7 @@ A supported vehicle is one that just works when you install a comma device. All |Kia|Niro Hybrid 2023[6](#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)](##)|
Parts- 1 Hyundai A connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2018-19|All|Stock|10 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai C connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro Plug-in Hybrid 2020|All|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Kia|Niro Plug-in Hybrid 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai D connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Niro 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)](##)|
Parts- 1 Hyundai F connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Hyundai B connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 Hyundai G connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -161,6 +172,7 @@ A supported vehicle is one that just works when you install a comma device. All |Lexus|GS F 2016|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2017-19|All|Stock|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|IS 2022-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lexus|LC 2024|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|NX 2020-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |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)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -173,7 +185,8 @@ A supported vehicle is one that just works when you install a comma device. All |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)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|RX Hybrid 2020-22|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Lexus|UX Hybrid 2019-23|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Lincoln|Aviator 2020-21|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lincoln|Aviator 2020-23|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Lincoln|Aviator Plug-in Hybrid 2020-23|Co-Pilot360 Plus|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Ford Q3 connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |MAN|eTGE 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |MAN|TGE 2017-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Mazda|CX-5 2022-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 Mazda connector
- 1 RJ45 cable (7 ft)
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -212,9 +225,9 @@ A supported vehicle is one that just works when you install a comma device. All |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)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Avalon Hybrid 2022|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR 2021|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|C-HR 2021|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|C-HR Hybrid 2017-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|C-HR Hybrid 2021-22|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|C-HR Hybrid 2021-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry 2018-20|All|Stock|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry 2021-24|All|openpilot|0 mph[9](#footnotes)|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Camry Hybrid 2018-20|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -240,13 +253,13 @@ A supported vehicle is one that just works when you install a comma device. All |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)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2017-18|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 2023-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2016|Toyota Safety Sense P|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2017-18|All|openpilot available[2](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|RAV4 Hybrid 2019-21|All|openpilot|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2022|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Toyota|RAV4 Hybrid 2023-24|All|Stock|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-empty.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2022|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Toyota|RAV4 Hybrid 2023-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Toyota|Sienna 2018-20|All|openpilot available[2](#footnotes)|19 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 RJ45 cable (7 ft)
- 1 Toyota A connector
- 1 comma 3X
- 1 comma power v2
- 1 harness box
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon 2018-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Arteon eHybrid 2020-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| @@ -267,8 +280,8 @@ A supported vehicle is one that just works when you install a comma device. All |Volkswagen|Golf R 2015-19|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Golf SportsVan 2015-20|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Grand California 2019-23|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|31 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 angled mount (8 degrees)
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta 2018-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| -|Volkswagen|Jetta GLI 2021-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta 2018-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| +|Volkswagen|Jetta GLI 2021-24|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Passat 2015-22[11](#footnotes)|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Passat Alltrack 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| |Volkswagen|Passat GTE 2015-22|Adaptive Cruise Control (ACC) & Lane Assist|openpilot available[1,13](#footnotes)|0 mph|0 mph|[![star](assets/icon-star-full.svg)](##)|[![star](assets/icon-star-full.svg)](##)|
Parts- 1 J533 connector
- 1 USB-C coupler
- 1 comma 3X
- 1 harness box
- 1 long OBD-C cable
- 1 mount
- 1 right angle OBD-C cable (1.5 ft)
Buy Here
|| diff --git a/launch_chffrplus.sh b/launch_chffrplus.sh index 9271c15436..a7f61411c8 100755 --- a/launch_chffrplus.sh +++ b/launch_chffrplus.sh @@ -15,6 +15,11 @@ function agnos_init { # set success flag for current boot slot sudo abctl --set_success + # TODO: do this without udev in AGNOS + # udev does this, but sometimes we startup faster + sudo chgrp gpu /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 + sudo chmod 660 /dev/adsprpc-smd /dev/ion /dev/kgsl-3d0 + # Check if AGNOS update is required if [ $(< /VERSION) != "$AGNOS_VERSION" ]; then AGNOS_PY="$DIR/system/hardware/tici/agnos.py" diff --git a/launch_env.sh b/launch_env.sh index d86ec63593..6859afb0d4 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="9.3" + export AGNOS_VERSION="9.7" fi export STAGING_ROOT="/data/safe_staging" diff --git a/opendbc b/opendbc index 3cfd0bf4eb..0ac21652f2 160000 --- a/opendbc +++ b/opendbc @@ -1 +1 @@ -Subproject commit 3cfd0bf4eb73953f3d179dddc1ba2c92e317188c +Subproject commit 0ac21652f2e643e29aa471ad6b238bf74b22e356 diff --git a/openpilot/__init__.py b/openpilot/__init__.py index b28b04f643..e69de29bb2 100644 --- a/openpilot/__init__.py +++ b/openpilot/__init__.py @@ -1,3 +0,0 @@ - - - diff --git a/panda b/panda index ec17f75efc..0c7d5f11d7 160000 --- a/panda +++ b/panda @@ -1 +1 @@ -Subproject commit ec17f75efca05c04313049e1d6dd376ef54d42ec +Subproject commit 0c7d5f11d7187904022ea49b6a76b54d7b280345 diff --git a/poetry.lock b/poetry.lock index 4cd6f5062e..588186d138 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2,87 +2,87 @@ [[package]] name = "aiohttp" -version = "3.9.2" +version = "3.9.3" description = "Async http client/server framework (asyncio)" optional = false python-versions = ">=3.8" files = [ - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:772fbe371788e61c58d6d3d904268e48a594ba866804d08c995ad71b144f94cb"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:edd4f1af2253f227ae311ab3d403d0c506c9b4410c7fc8d9573dec6d9740369f"}, - {file = "aiohttp-3.9.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cfee9287778399fdef6f8a11c9e425e1cb13cc9920fd3a3df8f122500978292b"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc158466f6a980a6095ee55174d1de5730ad7dec251be655d9a6a9dd7ea1ff9"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54ec82f45d57c9a65a1ead3953b51c704f9587440e6682f689da97f3e8defa35"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:abeb813a18eb387f0d835ef51f88568540ad0325807a77a6e501fed4610f864e"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc91d07280d7d169f3a0f9179d8babd0ee05c79d4d891447629ff0d7d8089ec2"}, - {file = "aiohttp-3.9.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b65e861f4bebfb660f7f0f40fa3eb9f2ab9af10647d05dac824390e7af8f75b7"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:04fd8ffd2be73d42bcf55fd78cde7958eeee6d4d8f73c3846b7cba491ecdb570"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:3d8d962b439a859b3ded9a1e111a4615357b01620a546bc601f25b0211f2da81"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:8ceb658afd12b27552597cf9a65d9807d58aef45adbb58616cdd5ad4c258c39e"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:0e4ee4df741670560b1bc393672035418bf9063718fee05e1796bf867e995fad"}, - {file = "aiohttp-3.9.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2dec87a556f300d3211decf018bfd263424f0690fcca00de94a837949fbcea02"}, - {file = "aiohttp-3.9.2-cp310-cp310-win32.whl", hash = "sha256:3e1a800f988ce7c4917f34096f81585a73dbf65b5c39618b37926b1238cf9bc4"}, - {file = "aiohttp-3.9.2-cp310-cp310-win_amd64.whl", hash = "sha256:ea510718a41b95c236c992b89fdfc3d04cc7ca60281f93aaada497c2b4e05c46"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6aaa6f99256dd1b5756a50891a20f0d252bd7bdb0854c5d440edab4495c9f973"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a27d8c70ad87bcfce2e97488652075a9bdd5b70093f50b10ae051dfe5e6baf37"}, - {file = "aiohttp-3.9.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:54287bcb74d21715ac8382e9de146d9442b5f133d9babb7e5d9e453faadd005e"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bb3d05569aa83011fcb346b5266e00b04180105fcacc63743fc2e4a1862a891"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c8534e7d69bb8e8d134fe2be9890d1b863518582f30c9874ed7ed12e48abe3c4"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4bd9d5b989d57b41e4ff56ab250c5ddf259f32db17159cce630fd543376bd96b"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa6904088e6642609981f919ba775838ebf7df7fe64998b1a954fb411ffb4663"}, - {file = "aiohttp-3.9.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bda42eb410be91b349fb4ee3a23a30ee301c391e503996a638d05659d76ea4c2"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:193cc1ccd69d819562cc7f345c815a6fc51d223b2ef22f23c1a0f67a88de9a72"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b9f1cb839b621f84a5b006848e336cf1496688059d2408e617af33e3470ba204"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:d22a0931848b8c7a023c695fa2057c6aaac19085f257d48baa24455e67df97ec"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4112d8ba61fbd0abd5d43a9cb312214565b446d926e282a6d7da3f5a5aa71d36"}, - {file = "aiohttp-3.9.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c4ad4241b52bb2eb7a4d2bde060d31c2b255b8c6597dd8deac2f039168d14fd7"}, - {file = "aiohttp-3.9.2-cp311-cp311-win32.whl", hash = "sha256:ee2661a3f5b529f4fc8a8ffee9f736ae054adfb353a0d2f78218be90617194b3"}, - {file = "aiohttp-3.9.2-cp311-cp311-win_amd64.whl", hash = "sha256:4deae2c165a5db1ed97df2868ef31ca3cc999988812e82386d22937d9d6fed52"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:6f4cdba12539215aaecf3c310ce9d067b0081a0795dd8a8805fdb67a65c0572a"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:84e843b33d5460a5c501c05539809ff3aee07436296ff9fbc4d327e32aa3a326"}, - {file = "aiohttp-3.9.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:8008d0f451d66140a5aa1c17e3eedc9d56e14207568cd42072c9d6b92bf19b52"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:61c47ab8ef629793c086378b1df93d18438612d3ed60dca76c3422f4fbafa792"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc71f748e12284312f140eaa6599a520389273174b42c345d13c7e07792f4f57"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a1c3a4d0ab2f75f22ec80bca62385db2e8810ee12efa8c9e92efea45c1849133"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a87aa0b13bbee025faa59fa58861303c2b064b9855d4c0e45ec70182bbeba1b"}, - {file = "aiohttp-3.9.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e2cc0d04688b9f4a7854c56c18aa7af9e5b0a87a28f934e2e596ba7e14783192"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1956e3ac376b1711c1533266dec4efd485f821d84c13ce1217d53e42c9e65f08"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:114da29f39eccd71b93a0fcacff178749a5c3559009b4a4498c2c173a6d74dff"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:3f17999ae3927d8a9a823a1283b201344a0627272f92d4f3e3a4efe276972fe8"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:f31df6a32217a34ae2f813b152a6f348154f948c83213b690e59d9e84020925c"}, - {file = "aiohttp-3.9.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:7a75307ffe31329928a8d47eae0692192327c599113d41b278d4c12b54e1bd11"}, - {file = "aiohttp-3.9.2-cp312-cp312-win32.whl", hash = "sha256:972b63d589ff8f305463593050a31b5ce91638918da38139b9d8deaba9e0fed7"}, - {file = "aiohttp-3.9.2-cp312-cp312-win_amd64.whl", hash = "sha256:200dc0246f0cb5405c80d18ac905c8350179c063ea1587580e3335bfc243ba6a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:158564d0d1020e0d3fe919a81d97aadad35171e13e7b425b244ad4337fc6793a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:da1346cd0ccb395f0ed16b113ebb626fa43b7b07fd7344fce33e7a4f04a8897a"}, - {file = "aiohttp-3.9.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:eaa9256de26ea0334ffa25f1913ae15a51e35c529a1ed9af8e6286dd44312554"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1543e7fb00214fb4ccead42e6a7d86f3bb7c34751ec7c605cca7388e525fd0b4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:186e94570433a004e05f31f632726ae0f2c9dee4762a9ce915769ce9c0a23d89"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d52d20832ac1560f4510d68e7ba8befbc801a2b77df12bd0cd2bcf3b049e52a4"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1c45e4e815ac6af3b72ca2bde9b608d2571737bb1e2d42299fc1ffdf60f6f9a1"}, - {file = "aiohttp-3.9.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa906b9bdfd4a7972dd0628dbbd6413d2062df5b431194486a78f0d2ae87bd55"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:68bbee9e17d66f17bb0010aa15a22c6eb28583edcc8b3212e2b8e3f77f3ebe2a"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4c189b64bd6d9a403a1a3f86a3ab3acbc3dc41a68f73a268a4f683f89a4dec1f"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8a7876f794523123bca6d44bfecd89c9fec9ec897a25f3dd202ee7fc5c6525b7"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d23fba734e3dd7b1d679b9473129cd52e4ec0e65a4512b488981a56420e708db"}, - {file = "aiohttp-3.9.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:b141753be581fab842a25cb319f79536d19c2a51995d7d8b29ee290169868eab"}, - {file = "aiohttp-3.9.2-cp38-cp38-win32.whl", hash = "sha256:103daf41ff3b53ba6fa09ad410793e2e76c9d0269151812e5aba4b9dd674a7e8"}, - {file = "aiohttp-3.9.2-cp38-cp38-win_amd64.whl", hash = "sha256:328918a6c2835861ff7afa8c6d2c70c35fdaf996205d5932351bdd952f33fa2f"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5264d7327c9464786f74e4ec9342afbbb6ee70dfbb2ec9e3dfce7a54c8043aa3"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:07205ae0015e05c78b3288c1517afa000823a678a41594b3fdc870878d645305"}, - {file = "aiohttp-3.9.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ae0a1e638cffc3ec4d4784b8b4fd1cf28968febc4bd2718ffa25b99b96a741bd"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d43302a30ba1166325974858e6ef31727a23bdd12db40e725bec0f759abce505"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16a967685907003765855999af11a79b24e70b34dc710f77a38d21cd9fc4f5fe"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6fa3ee92cd441d5c2d07ca88d7a9cef50f7ec975f0117cd0c62018022a184308"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0b500c5ad9c07639d48615a770f49618130e61be36608fc9bc2d9bae31732b8f"}, - {file = "aiohttp-3.9.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c07327b368745b1ce2393ae9e1aafed7073d9199e1dcba14e035cc646c7941bf"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:cc7d6502c23a0ec109687bf31909b3fb7b196faf198f8cff68c81b49eb316ea9"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:07be2be7071723c3509ab5c08108d3a74f2181d4964e869f2504aaab68f8d3e8"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:122468f6fee5fcbe67cb07014a08c195b3d4c41ff71e7b5160a7bcc41d585a5f"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:00a9abcea793c81e7f8778ca195a1714a64f6d7436c4c0bb168ad2a212627000"}, - {file = "aiohttp-3.9.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:7a9825fdd64ecac5c670234d80bb52bdcaa4139d1f839165f548208b3779c6c6"}, - {file = "aiohttp-3.9.2-cp39-cp39-win32.whl", hash = "sha256:5422cd9a4a00f24c7244e1b15aa9b87935c85fb6a00c8ac9b2527b38627a9211"}, - {file = "aiohttp-3.9.2-cp39-cp39-win_amd64.whl", hash = "sha256:7d579dcd5d82a86a46f725458418458fa43686f6a7b252f2966d359033ffc8ab"}, - {file = "aiohttp-3.9.2.tar.gz", hash = "sha256:b0ad0a5e86ce73f5368a164c10ada10504bf91869c05ab75d982c6048217fbf7"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:939677b61f9d72a4fa2a042a5eee2a99a24001a67c13da113b2e30396567db54"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1f5cd333fcf7590a18334c90f8c9147c837a6ec8a178e88d90a9b96ea03194cc"}, + {file = "aiohttp-3.9.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:82e6aa28dd46374f72093eda8bcd142f7771ee1eb9d1e223ff0fa7177a96b4a5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f56455b0c2c7cc3b0c584815264461d07b177f903a04481dfc33e08a89f0c26b"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bca77a198bb6e69795ef2f09a5f4c12758487f83f33d63acde5f0d4919815768"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e083c285857b78ee21a96ba1eb1b5339733c3563f72980728ca2b08b53826ca5"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab40e6251c3873d86ea9b30a1ac6d7478c09277b32e14745d0d3c6e76e3c7e29"}, + {file = "aiohttp-3.9.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df822ee7feaaeffb99c1a9e5e608800bd8eda6e5f18f5cfb0dc7eeb2eaa6bbec"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:acef0899fea7492145d2bbaaaec7b345c87753168589cc7faf0afec9afe9b747"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:cd73265a9e5ea618014802ab01babf1940cecb90c9762d8b9e7d2cc1e1969ec6"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:a78ed8a53a1221393d9637c01870248a6f4ea5b214a59a92a36f18151739452c"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:6b0e029353361f1746bac2e4cc19b32f972ec03f0f943b390c4ab3371840aabf"}, + {file = "aiohttp-3.9.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7cf5c9458e1e90e3c390c2639f1017a0379a99a94fdfad3a1fd966a2874bba52"}, + {file = "aiohttp-3.9.3-cp310-cp310-win32.whl", hash = "sha256:3e59c23c52765951b69ec45ddbbc9403a8761ee6f57253250c6e1536cacc758b"}, + {file = "aiohttp-3.9.3-cp310-cp310-win_amd64.whl", hash = "sha256:055ce4f74b82551678291473f66dc9fb9048a50d8324278751926ff0ae7715e5"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:6b88f9386ff1ad91ace19d2a1c0225896e28815ee09fc6a8932fded8cda97c3d"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c46956ed82961e31557b6857a5ca153c67e5476972e5f7190015018760938da2"}, + {file = "aiohttp-3.9.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:07b837ef0d2f252f96009e9b8435ec1fef68ef8b1461933253d318748ec1acdc"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad46e6f620574b3b4801c68255492e0159d1712271cc99d8bdf35f2043ec266"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ed3e046ea7b14938112ccd53d91c1539af3e6679b222f9469981e3dac7ba1ce"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:039df344b45ae0b34ac885ab5b53940b174530d4dd8a14ed8b0e2155b9dddccb"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7943c414d3a8d9235f5f15c22ace69787c140c80b718dcd57caaade95f7cd93b"}, + {file = "aiohttp-3.9.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:84871a243359bb42c12728f04d181a389718710129b36b6aad0fc4655a7647d4"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5eafe2c065df5401ba06821b9a054d9cb2848867f3c59801b5d07a0be3a380ae"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:9d3c9b50f19704552f23b4eaea1fc082fdd82c63429a6506446cbd8737823da3"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:f033d80bc6283092613882dfe40419c6a6a1527e04fc69350e87a9df02bbc283"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:2c895a656dd7e061b2fd6bb77d971cc38f2afc277229ce7dd3552de8313a483e"}, + {file = "aiohttp-3.9.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1f5a71d25cd8106eab05f8704cd9167b6e5187bcdf8f090a66c6d88b634802b4"}, + {file = "aiohttp-3.9.3-cp311-cp311-win32.whl", hash = "sha256:50fca156d718f8ced687a373f9e140c1bb765ca16e3d6f4fe116e3df7c05b2c5"}, + {file = "aiohttp-3.9.3-cp311-cp311-win_amd64.whl", hash = "sha256:5fe9ce6c09668063b8447f85d43b8d1c4e5d3d7e92c63173e6180b2ac5d46dd8"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:38a19bc3b686ad55804ae931012f78f7a534cce165d089a2059f658f6c91fa60"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:770d015888c2a598b377bd2f663adfd947d78c0124cfe7b959e1ef39f5b13869"}, + {file = "aiohttp-3.9.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ee43080e75fc92bf36219926c8e6de497f9b247301bbf88c5c7593d931426679"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52df73f14ed99cee84865b95a3d9e044f226320a87af208f068ecc33e0c35b96"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc9b311743a78043b26ffaeeb9715dc360335e5517832f5a8e339f8a43581e4d"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b955ed993491f1a5da7f92e98d5dad3c1e14dc175f74517c4e610b1f2456fb11"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:504b6981675ace64c28bf4a05a508af5cde526e36492c98916127f5a02354d53"}, + {file = "aiohttp-3.9.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a6fe5571784af92b6bc2fda8d1925cccdf24642d49546d3144948a6a1ed58ca5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ba39e9c8627edc56544c8628cc180d88605df3892beeb2b94c9bc857774848ca"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:e5e46b578c0e9db71d04c4b506a2121c0cb371dd89af17a0586ff6769d4c58c1"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:938a9653e1e0c592053f815f7028e41a3062e902095e5a7dc84617c87267ebd5"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:c3452ea726c76e92f3b9fae4b34a151981a9ec0a4847a627c43d71a15ac32aa6"}, + {file = "aiohttp-3.9.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ff30218887e62209942f91ac1be902cc80cddb86bf00fbc6783b7a43b2bea26f"}, + {file = "aiohttp-3.9.3-cp312-cp312-win32.whl", hash = "sha256:38f307b41e0bea3294a9a2a87833191e4bcf89bb0365e83a8be3a58b31fb7f38"}, + {file = "aiohttp-3.9.3-cp312-cp312-win_amd64.whl", hash = "sha256:b791a3143681a520c0a17e26ae7465f1b6f99461a28019d1a2f425236e6eedb5"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ed621426d961df79aa3b963ac7af0d40392956ffa9be022024cd16297b30c8c"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:7f46acd6a194287b7e41e87957bfe2ad1ad88318d447caf5b090012f2c5bb528"}, + {file = "aiohttp-3.9.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:feeb18a801aacb098220e2c3eea59a512362eb408d4afd0c242044c33ad6d542"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f734e38fd8666f53da904c52a23ce517f1b07722118d750405af7e4123933511"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b40670ec7e2156d8e57f70aec34a7216407848dfe6c693ef131ddf6e76feb672"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fdd215b7b7fd4a53994f238d0f46b7ba4ac4c0adb12452beee724ddd0743ae5d"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:017a21b0df49039c8f46ca0971b3a7fdc1f56741ab1240cb90ca408049766168"}, + {file = "aiohttp-3.9.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e99abf0bba688259a496f966211c49a514e65afa9b3073a1fcee08856e04425b"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:648056db9a9fa565d3fa851880f99f45e3f9a771dd3ff3bb0c048ea83fb28194"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:8aacb477dc26797ee089721536a292a664846489c49d3ef9725f992449eda5a8"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:522a11c934ea660ff8953eda090dcd2154d367dec1ae3c540aff9f8a5c109ab4"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:5bce0dc147ca85caa5d33debc4f4d65e8e8b5c97c7f9f660f215fa74fc49a321"}, + {file = "aiohttp-3.9.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b4af9f25b49a7be47c0972139e59ec0e8285c371049df1a63b6ca81fdd216a2"}, + {file = "aiohttp-3.9.3-cp38-cp38-win32.whl", hash = "sha256:298abd678033b8571995650ccee753d9458dfa0377be4dba91e4491da3f2be63"}, + {file = "aiohttp-3.9.3-cp38-cp38-win_amd64.whl", hash = "sha256:69361bfdca5468c0488d7017b9b1e5ce769d40b46a9f4a2eed26b78619e9396c"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:0fa43c32d1643f518491d9d3a730f85f5bbaedcbd7fbcae27435bb8b7a061b29"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:835a55b7ca49468aaaac0b217092dfdff370e6c215c9224c52f30daaa735c1c1"}, + {file = "aiohttp-3.9.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:06a9b2c8837d9a94fae16c6223acc14b4dfdff216ab9b7202e07a9a09541168f"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abf151955990d23f84205286938796c55ff11bbfb4ccfada8c9c83ae6b3c89a3"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:59c26c95975f26e662ca78fdf543d4eeaef70e533a672b4113dd888bd2423caa"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f95511dd5d0e05fd9728bac4096319f80615aaef4acbecb35a990afebe953b0e"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:595f105710293e76b9dc09f52e0dd896bd064a79346234b521f6b968ffdd8e58"}, + {file = "aiohttp-3.9.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7c8b816c2b5af5c8a436df44ca08258fc1a13b449393a91484225fcb7545533"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f1088fa100bf46e7b398ffd9904f4808a0612e1d966b4aa43baa535d1b6341eb"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f59dfe57bb1ec82ac0698ebfcdb7bcd0e99c255bd637ff613760d5f33e7c81b3"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:361a1026c9dd4aba0109e4040e2aecf9884f5cfe1b1b1bd3d09419c205e2e53d"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:363afe77cfcbe3a36353d8ea133e904b108feea505aa4792dad6585a8192c55a"}, + {file = "aiohttp-3.9.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8e2c45c208c62e955e8256949eb225bd8b66a4c9b6865729a786f2aa79b72e9d"}, + {file = "aiohttp-3.9.3-cp39-cp39-win32.whl", hash = "sha256:f7217af2e14da0856e082e96ff637f14ae45c10a5714b63c77f26d8884cf1051"}, + {file = "aiohttp-3.9.3-cp39-cp39-win_amd64.whl", hash = "sha256:27468897f628c627230dba07ec65dc8d0db566923c48f29e084ce382119802bc"}, + {file = "aiohttp-3.9.3.tar.gz", hash = "sha256:90842933e5d1ff760fae6caca4b2b3edba53ba8f4b71e95dacf2818a2aca06f7"}, ] [package.dependencies] @@ -255,13 +255,13 @@ files = [ [[package]] name = "azure-core" -version = "1.29.7" +version = "1.30.0" description = "Microsoft Azure Core Library for Python" optional = false python-versions = ">=3.7" files = [ - {file = "azure-core-1.29.7.tar.gz", hash = "sha256:2944faf1a7ff1558b1f457cabf60f279869cabaeef86b353bed8eb032c7d8c5e"}, - {file = "azure_core-1.29.7-py3-none-any.whl", hash = "sha256:95a7b41b4af102e5fcdfac9500fcc82ff86e936c7145a099b7848b9ac0501250"}, + {file = "azure-core-1.30.0.tar.gz", hash = "sha256:6f3a7883ef184722f6bd997262eddaf80cfe7e5b3e0caaaf8db1695695893d35"}, + {file = "azure_core-1.30.0-py3-none-any.whl", hash = "sha256:3dae7962aad109610e68c9a7abb31d79720e1d982ddf61363038d175a5025e89"}, ] [package.dependencies] @@ -395,13 +395,13 @@ numpy = "*" [[package]] name = "certifi" -version = "2023.11.17" +version = "2024.2.2" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2023.11.17-py3-none-any.whl", hash = "sha256:e036ab49d5b79556f99cfc2d9320b34cfbe5be05c5871b51de9329f0603b0474"}, - {file = "certifi-2023.11.17.tar.gz", hash = "sha256:9b469f3a900bf28dc19b8cfbf8019bf47f7fdd1a65a1d4ffb98fc14166beb4d1"}, + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, ] [[package]] @@ -751,63 +751,63 @@ test = ["pytest", "pytest-timeout"] [[package]] name = "coverage" -version = "7.4.1" +version = "7.4.3" description = "Code coverage measurement for Python" optional = false python-versions = ">=3.8" files = [ - {file = "coverage-7.4.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:077d366e724f24fc02dbfe9d946534357fda71af9764ff99d73c3c596001bbd7"}, - {file = "coverage-7.4.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0193657651f5399d433c92f8ae264aff31fc1d066deee4b831549526433f3f61"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d17bbc946f52ca67adf72a5ee783cd7cd3477f8f8796f59b4974a9b59cacc9ee"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3277f5fa7483c927fe3a7b017b39351610265308f5267ac6d4c2b64cc1d8d25"}, - {file = "coverage-7.4.1-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6dceb61d40cbfcf45f51e59933c784a50846dc03211054bd76b421a713dcdf19"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6008adeca04a445ea6ef31b2cbaf1d01d02986047606f7da266629afee982630"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:c61f66d93d712f6e03369b6a7769233bfda880b12f417eefdd4f16d1deb2fc4c"}, - {file = "coverage-7.4.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b9bb62fac84d5f2ff523304e59e5c439955fb3b7f44e3d7b2085184db74d733b"}, - {file = "coverage-7.4.1-cp310-cp310-win32.whl", hash = "sha256:f86f368e1c7ce897bf2457b9eb61169a44e2ef797099fb5728482b8d69f3f016"}, - {file = "coverage-7.4.1-cp310-cp310-win_amd64.whl", hash = "sha256:869b5046d41abfea3e381dd143407b0d29b8282a904a19cb908fa24d090cc018"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b8ffb498a83d7e0305968289441914154fb0ef5d8b3157df02a90c6695978295"}, - {file = "coverage-7.4.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3cacfaefe6089d477264001f90f55b7881ba615953414999c46cc9713ff93c8c"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d6850e6e36e332d5511a48a251790ddc545e16e8beaf046c03985c69ccb2676"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18e961aa13b6d47f758cc5879383d27b5b3f3dcd9ce8cdbfdc2571fe86feb4dd"}, - {file = "coverage-7.4.1-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dfd1e1b9f0898817babf840b77ce9fe655ecbe8b1b327983df485b30df8cc011"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6b00e21f86598b6330f0019b40fb397e705135040dbedc2ca9a93c7441178e74"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:536d609c6963c50055bab766d9951b6c394759190d03311f3e9fcf194ca909e1"}, - {file = "coverage-7.4.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7ac8f8eb153724f84885a1374999b7e45734bf93a87d8df1e7ce2146860edef6"}, - {file = "coverage-7.4.1-cp311-cp311-win32.whl", hash = "sha256:f3771b23bb3675a06f5d885c3630b1d01ea6cac9e84a01aaf5508706dba546c5"}, - {file = "coverage-7.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d2f9d4cc2a53b38cabc2d6d80f7f9b7e3da26b2f53d48f05876fef7956b6968"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f68ef3660677e6624c8cace943e4765545f8191313a07288a53d3da188bd8581"}, - {file = "coverage-7.4.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23b27b8a698e749b61809fb637eb98ebf0e505710ec46a8aa6f1be7dc0dc43a6"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e3424c554391dc9ef4a92ad28665756566a28fecf47308f91841f6c49288e66"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e0860a348bf7004c812c8368d1fc7f77fe8e4c095d661a579196a9533778e156"}, - {file = "coverage-7.4.1-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe558371c1bdf3b8fa03e097c523fb9645b8730399c14fe7721ee9c9e2a545d3"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3468cc8720402af37b6c6e7e2a9cdb9f6c16c728638a2ebc768ba1ef6f26c3a1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:02f2edb575d62172aa28fe00efe821ae31f25dc3d589055b3fb64d51e52e4ab1"}, - {file = "coverage-7.4.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ca6e61dc52f601d1d224526360cdeab0d0712ec104a2ce6cc5ccef6ed9a233bc"}, - {file = "coverage-7.4.1-cp312-cp312-win32.whl", hash = "sha256:ca7b26a5e456a843b9b6683eada193fc1f65c761b3a473941efe5a291f604c74"}, - {file = "coverage-7.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:85ccc5fa54c2ed64bd91ed3b4a627b9cce04646a659512a051fa82a92c04a448"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:8bdb0285a0202888d19ec6b6d23d5990410decb932b709f2b0dfe216d031d218"}, - {file = "coverage-7.4.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:918440dea04521f499721c039863ef95433314b1db00ff826a02580c1f503e45"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:379d4c7abad5afbe9d88cc31ea8ca262296480a86af945b08214eb1a556a3e4d"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b094116f0b6155e36a304ff912f89bbb5067157aff5f94060ff20bbabdc8da06"}, - {file = "coverage-7.4.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f2f5968608b1fe2a1d00d01ad1017ee27efd99b3437e08b83ded9b7af3f6f766"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:10e88e7f41e6197ea0429ae18f21ff521d4f4490aa33048f6c6f94c6045a6a75"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a4a3907011d39dbc3e37bdc5df0a8c93853c369039b59efa33a7b6669de04c60"}, - {file = "coverage-7.4.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6d224f0c4c9c98290a6990259073f496fcec1b5cc613eecbd22786d398ded3ad"}, - {file = "coverage-7.4.1-cp38-cp38-win32.whl", hash = "sha256:23f5881362dcb0e1a92b84b3c2809bdc90db892332daab81ad8f642d8ed55042"}, - {file = "coverage-7.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:a07f61fc452c43cd5328b392e52555f7d1952400a1ad09086c4a8addccbd138d"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8e738a492b6221f8dcf281b67129510835461132b03024830ac0e554311a5c54"}, - {file = "coverage-7.4.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:46342fed0fff72efcda77040b14728049200cbba1279e0bf1188f1f2078c1d70"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9641e21670c68c7e57d2053ddf6c443e4f0a6e18e547e86af3fad0795414a628"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aeb2c2688ed93b027eb0d26aa188ada34acb22dceea256d76390eea135083950"}, - {file = "coverage-7.4.1-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d12c923757de24e4e2110cf8832d83a886a4cf215c6e61ed506006872b43a6d1"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0491275c3b9971cdbd28a4595c2cb5838f08036bca31765bad5e17edf900b2c7"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:8dfc5e195bbef80aabd81596ef52a1277ee7143fe419efc3c4d8ba2754671756"}, - {file = "coverage-7.4.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1a78b656a4d12b0490ca72651fe4d9f5e07e3c6461063a9b6265ee45eb2bdd35"}, - {file = "coverage-7.4.1-cp39-cp39-win32.whl", hash = "sha256:f90515974b39f4dea2f27c0959688621b46d96d5a626cf9c53dbc653a895c05c"}, - {file = "coverage-7.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:64e723ca82a84053dd7bfcc986bdb34af8d9da83c521c19d6b472bc6880e191a"}, - {file = "coverage-7.4.1-pp38.pp39.pp310-none-any.whl", hash = "sha256:32a8d985462e37cfdab611a6f95b09d7c091d07668fdc26e47a725ee575fe166"}, - {file = "coverage-7.4.1.tar.gz", hash = "sha256:1ed4b95480952b1a26d863e546fa5094564aa0065e1e5f0d4d0041f293251d04"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8580b827d4746d47294c0e0b92854c85a92c2227927433998f0d3320ae8a71b6"}, + {file = "coverage-7.4.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:718187eeb9849fc6cc23e0d9b092bc2348821c5e1a901c9f8975df0bc785bfd4"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:767b35c3a246bcb55b8044fd3a43b8cd553dd1f9f2c1eeb87a302b1f8daa0524"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae7f19afe0cce50039e2c782bff379c7e347cba335429678450b8fe81c4ef96d"}, + {file = "coverage-7.4.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba3a8aaed13770e970b3df46980cb068d1c24af1a1968b7818b69af8c4347efb"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ee866acc0861caebb4f2ab79f0b94dbfbdbfadc19f82e6e9c93930f74e11d7a0"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:506edb1dd49e13a2d4cac6a5173317b82a23c9d6e8df63efb4f0380de0fbccbc"}, + {file = "coverage-7.4.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd6545d97c98a192c5ac995d21c894b581f1fd14cf389be90724d21808b657e2"}, + {file = "coverage-7.4.3-cp310-cp310-win32.whl", hash = "sha256:f6a09b360d67e589236a44f0c39218a8efba2593b6abdccc300a8862cffc2f94"}, + {file = "coverage-7.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:18d90523ce7553dd0b7e23cbb28865db23cddfd683a38fb224115f7826de78d0"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cbbe5e739d45a52f3200a771c6d2c7acf89eb2524890a4a3aa1a7fa0695d2a47"}, + {file = "coverage-7.4.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:489763b2d037b164846ebac0cbd368b8a4ca56385c4090807ff9fad817de4113"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:451f433ad901b3bb00184d83fd83d135fb682d780b38af7944c9faeecb1e0bfe"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fcc66e222cf4c719fe7722a403888b1f5e1682d1679bd780e2b26c18bb648cdc"}, + {file = "coverage-7.4.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3ec74cfef2d985e145baae90d9b1b32f85e1741b04cd967aaf9cfa84c1334f3"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:abbbd8093c5229c72d4c2926afaee0e6e3140de69d5dcd918b2921f2f0c8baba"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:35eb581efdacf7b7422af677b92170da4ef34500467381e805944a3201df2079"}, + {file = "coverage-7.4.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:8249b1c7334be8f8c3abcaaa996e1e4927b0e5a23b65f5bf6cfe3180d8ca7840"}, + {file = "coverage-7.4.3-cp311-cp311-win32.whl", hash = "sha256:cf30900aa1ba595312ae41978b95e256e419d8a823af79ce670835409fc02ad3"}, + {file = "coverage-7.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:18c7320695c949de11a351742ee001849912fd57e62a706d83dfc1581897fa2e"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b51bfc348925e92a9bd9b2e48dad13431b57011fd1038f08316e6bf1df107d10"}, + {file = "coverage-7.4.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d6cdecaedea1ea9e033d8adf6a0ab11107b49571bbb9737175444cea6eb72328"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b2eccb883368f9e972e216c7b4c7c06cabda925b5f06dde0650281cb7666a30"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6c00cdc8fa4e50e1cc1f941a7f2e3e0f26cb2a1233c9696f26963ff58445bac7"}, + {file = "coverage-7.4.3-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9a4a8dd3dcf4cbd3165737358e4d7dfbd9d59902ad11e3b15eebb6393b0446e"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:062b0a75d9261e2f9c6d071753f7eef0fc9caf3a2c82d36d76667ba7b6470003"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ebe7c9e67a2d15fa97b77ea6571ce5e1e1f6b0db71d1d5e96f8d2bf134303c1d"}, + {file = "coverage-7.4.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c0a120238dd71c68484f02562f6d446d736adcc6ca0993712289b102705a9a3a"}, + {file = "coverage-7.4.3-cp312-cp312-win32.whl", hash = "sha256:37389611ba54fd6d278fde86eb2c013c8e50232e38f5c68235d09d0a3f8aa352"}, + {file = "coverage-7.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:d25b937a5d9ffa857d41be042b4238dd61db888533b53bc76dc082cb5a15e914"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:28ca2098939eabab044ad68850aac8f8db6bf0b29bc7f2887d05889b17346454"}, + {file = "coverage-7.4.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:280459f0a03cecbe8800786cdc23067a8fc64c0bd51dc614008d9c36e1659d7e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c0cdedd3500e0511eac1517bf560149764b7d8e65cb800d8bf1c63ebf39edd2"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a9babb9466fe1da12417a4aed923e90124a534736de6201794a3aea9d98484e"}, + {file = "coverage-7.4.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dec9de46a33cf2dd87a5254af095a409ea3bf952d85ad339751e7de6d962cde6"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:16bae383a9cc5abab9bb05c10a3e5a52e0a788325dc9ba8499e821885928968c"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:2c854ce44e1ee31bda4e318af1dbcfc929026d12c5ed030095ad98197eeeaed0"}, + {file = "coverage-7.4.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:ce8c50520f57ec57aa21a63ea4f325c7b657386b3f02ccaedeccf9ebe27686e1"}, + {file = "coverage-7.4.3-cp38-cp38-win32.whl", hash = "sha256:708a3369dcf055c00ddeeaa2b20f0dd1ce664eeabde6623e516c5228b753654f"}, + {file = "coverage-7.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:1bf25fbca0c8d121a3e92a2a0555c7e5bc981aee5c3fdaf4bb7809f410f696b9"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b253094dbe1b431d3a4ac2f053b6d7ede2664ac559705a704f621742e034f1f"}, + {file = "coverage-7.4.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:77fbfc5720cceac9c200054b9fab50cb2a7d79660609200ab83f5db96162d20c"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6679060424faa9c11808598504c3ab472de4531c571ab2befa32f4971835788e"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4af154d617c875b52651dd8dd17a31270c495082f3d55f6128e7629658d63765"}, + {file = "coverage-7.4.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8640f1fde5e1b8e3439fe482cdc2b0bb6c329f4bb161927c28d2e8879c6029ee"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:69b9f6f66c0af29642e73a520b6fed25ff9fd69a25975ebe6acb297234eda501"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0842571634f39016a6c03e9d4aba502be652a6e4455fadb73cd3a3a49173e38f"}, + {file = "coverage-7.4.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a78ed23b08e8ab524551f52953a8a05d61c3a760781762aac49f8de6eede8c45"}, + {file = "coverage-7.4.3-cp39-cp39-win32.whl", hash = "sha256:c0524de3ff096e15fcbfe8f056fdb4ea0bf497d584454f344d59fce069d3e6e9"}, + {file = "coverage-7.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:0209a6369ccce576b43bb227dc8322d8ef9e323d089c6f3f26a597b09cb4d2aa"}, + {file = "coverage-7.4.3-pp38.pp39.pp310-none-any.whl", hash = "sha256:7cbde573904625509a3f37b6fecea974e363460b556a627c60dc2f47e2fffa51"}, + {file = "coverage-7.4.3.tar.gz", hash = "sha256:276f6077a5c61447a48d133ed13e759c09e62aff0dc84274a68dc18660104d52"}, ] [package.extras] @@ -825,43 +825,43 @@ files = [ [[package]] name = "cryptography" -version = "42.0.1" +version = "42.0.5" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:265bdc693570b895eb641410b8fc9e8ddbce723a669236162b9d9cfb70bd8d77"}, - {file = "cryptography-42.0.1-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:160fa08dfa6dca9cb8ad9bd84e080c0db6414ba5ad9a7470bc60fb154f60111e"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:727387886c9c8de927c360a396c5edcb9340d9e960cda145fca75bdafdabd24c"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4d84673c012aa698555d4710dcfe5f8a0ad76ea9dde8ef803128cc669640a2e0"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e6edc3a568667daf7d349d7e820783426ee4f1c0feab86c29bd1d6fe2755e009"}, - {file = "cryptography-42.0.1-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:d50718dd574a49d3ef3f7ef7ece66ef281b527951eb2267ce570425459f6a404"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:9544492e8024f29919eac2117edd8c950165e74eb551a22c53f6fdf6ba5f4cb8"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:ab6b302d51fbb1dd339abc6f139a480de14d49d50f65fdc7dff782aa8631d035"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:2fe16624637d6e3e765530bc55caa786ff2cbca67371d306e5d0a72e7c3d0407"}, - {file = "cryptography-42.0.1-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ed1b2130f5456a09a134cc505a17fc2830a1a48ed53efd37dcc904a23d7b82fa"}, - {file = "cryptography-42.0.1-cp37-abi3-win32.whl", hash = "sha256:e5edf189431b4d51f5c6fb4a95084a75cef6b4646c934eb6e32304fc720e1453"}, - {file = "cryptography-42.0.1-cp37-abi3-win_amd64.whl", hash = "sha256:6bfd823b336fdcd8e06285ae8883d3d2624d3bdef312a0e2ef905f332f8e9302"}, - {file = "cryptography-42.0.1-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:351db02c1938c8e6b1fee8a78d6b15c5ccceca7a36b5ce48390479143da3b411"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:430100abed6d3652208ae1dd410c8396213baee2e01a003a4449357db7dc9e14"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2dff7a32880a51321f5de7869ac9dde6b1fca00fc1fef89d60e93f215468e824"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b512f33c6ab195852595187af5440d01bb5f8dd57cb7a91e1e009a17f1b7ebca"}, - {file = "cryptography-42.0.1-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:95d900d19a370ae36087cc728e6e7be9c964ffd8cbcb517fd1efb9c9284a6abc"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:6ac8924085ed8287545cba89dc472fc224c10cc634cdf2c3e2866fe868108e77"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:cb2861a9364fa27d24832c718150fdbf9ce6781d7dc246a516435f57cfa31fe7"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:25ec6e9e81de5d39f111a4114193dbd39167cc4bbd31c30471cebedc2a92c323"}, - {file = "cryptography-42.0.1-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:9d61fcdf37647765086030d81872488e4cb3fafe1d2dda1d487875c3709c0a49"}, - {file = "cryptography-42.0.1-cp39-abi3-win32.whl", hash = "sha256:16b9260d04a0bfc8952b00335ff54f471309d3eb9d7e8dbfe9b0bd9e26e67881"}, - {file = "cryptography-42.0.1-cp39-abi3-win_amd64.whl", hash = "sha256:7911586fc69d06cd0ab3f874a169433db1bc2f0e40988661408ac06c4527a986"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:d3594947d2507d4ef7a180a7f49a6db41f75fb874c2fd0e94f36b89bfd678bf2"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:8d7efb6bf427d2add2f40b6e1e8e476c17508fa8907234775214b153e69c2e11"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:126e0ba3cc754b200a2fb88f67d66de0d9b9e94070c5bc548318c8dab6383cb6"}, - {file = "cryptography-42.0.1-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:802d6f83233cf9696b59b09eb067e6b4d5ae40942feeb8e13b213c8fad47f1aa"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:0b7cacc142260ada944de070ce810c3e2a438963ee3deb45aa26fd2cee94c9a4"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:32ea63ceeae870f1a62e87f9727359174089f7b4b01e4999750827bf10e15d60"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:d3902c779a92151f134f68e555dd0b17c658e13429f270d8a847399b99235a3f"}, - {file = "cryptography-42.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:50aecd93676bcca78379604ed664c45da82bc1241ffb6f97f6b7392ed5bc6f04"}, - {file = "cryptography-42.0.1.tar.gz", hash = "sha256:fd33f53809bb363cf126bebe7a99d97735988d9b0131a2be59fbf83e1259a5b7"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, ] [package.dependencies] @@ -989,22 +989,22 @@ files = [ [[package]] name = "dnspython" -version = "2.5.0" +version = "2.6.1" description = "DNS toolkit" optional = false python-versions = ">=3.8" files = [ - {file = "dnspython-2.5.0-py3-none-any.whl", hash = "sha256:6facdf76b73c742ccf2d07add296f178e629da60be23ce4b0a9c927b1e02c3a6"}, - {file = "dnspython-2.5.0.tar.gz", hash = "sha256:a0034815a59ba9ae888946be7ccca8f7c157b286f8455b379c692efb51022a15"}, + {file = "dnspython-2.6.1-py3-none-any.whl", hash = "sha256:5ef3b9680161f6fa89daf8ad451b5f1a33b18ae8a1c6778cdf4b43f08c0a6e50"}, + {file = "dnspython-2.6.1.tar.gz", hash = "sha256:e8f0f9c23a7b7cb99ded64e6c3a6f3e701d78f50c55e002b839dea7225cff7cc"}, ] [package.extras] -dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=5.0.3)", "mypy (>=1.0.1)", "pylint (>=2.7)", "pytest (>=6.2.5)", "pytest-cov (>=3.0.0)", "sphinx (>=7.0.0)", "twine (>=4.0.0)", "wheel (>=0.41.0)"] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "sphinx (>=7.2.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] dnssec = ["cryptography (>=41)"] -doh = ["h2 (>=4.1.0)", "httpcore (>=0.17.3)", "httpx (>=0.25.1)"] -doq = ["aioquic (>=0.9.20)"] -idna = ["idna (>=2.1)"] -trio = ["trio (>=0.14)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=0.9.25)"] +idna = ["idna (>=3.6)"] +trio = ["trio (>=0.23)"] wmi = ["wmi (>=1.5.1)"] [[package]] @@ -1131,60 +1131,60 @@ files = [ [[package]] name = "fonttools" -version = "4.47.2" +version = "4.49.0" description = "Tools to manipulate font files" optional = false python-versions = ">=3.8" files = [ - {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3b629108351d25512d4ea1a8393a2dba325b7b7d7308116b605ea3f8e1be88df"}, - {file = "fonttools-4.47.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c19044256c44fe299d9a73456aabee4b4d06c6b930287be93b533b4737d70aa1"}, - {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8be28c036b9f186e8c7eaf8a11b42373e7e4949f9e9f370202b9da4c4c3f56c"}, - {file = "fonttools-4.47.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f83a4daef6d2a202acb9bf572958f91cfde5b10c8ee7fb1d09a4c81e5d851fd8"}, - {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4a5a5318ba5365d992666ac4fe35365f93004109d18858a3e18ae46f67907670"}, - {file = "fonttools-4.47.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8f57ecd742545362a0f7186774b2d1c53423ed9ece67689c93a1055b236f638c"}, - {file = "fonttools-4.47.2-cp310-cp310-win32.whl", hash = "sha256:a1c154bb85dc9a4cf145250c88d112d88eb414bad81d4cb524d06258dea1bdc0"}, - {file = "fonttools-4.47.2-cp310-cp310-win_amd64.whl", hash = "sha256:3e2b95dce2ead58fb12524d0ca7d63a63459dd489e7e5838c3cd53557f8933e1"}, - {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:29495d6d109cdbabe73cfb6f419ce67080c3ef9ea1e08d5750240fd4b0c4763b"}, - {file = "fonttools-4.47.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a1d313a415eaaba2b35d6cd33536560deeebd2ed758b9bfb89ab5d97dc5deac"}, - {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:90f898cdd67f52f18049250a6474185ef6544c91f27a7bee70d87d77a8daf89c"}, - {file = "fonttools-4.47.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3480eeb52770ff75140fe7d9a2ec33fb67b07efea0ab5129c7e0c6a639c40c70"}, - {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0255dbc128fee75fb9be364806b940ed450dd6838672a150d501ee86523ac61e"}, - {file = "fonttools-4.47.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f791446ff297fd5f1e2247c188de53c1bfb9dd7f0549eba55b73a3c2087a2703"}, - {file = "fonttools-4.47.2-cp311-cp311-win32.whl", hash = "sha256:740947906590a878a4bde7dd748e85fefa4d470a268b964748403b3ab2aeed6c"}, - {file = "fonttools-4.47.2-cp311-cp311-win_amd64.whl", hash = "sha256:63fbed184979f09a65aa9c88b395ca539c94287ba3a364517698462e13e457c9"}, - {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4ec558c543609e71b2275c4894e93493f65d2f41c15fe1d089080c1d0bb4d635"}, - {file = "fonttools-4.47.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:e040f905d542362e07e72e03612a6270c33d38281fd573160e1003e43718d68d"}, - {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6dd58cc03016b281bd2c74c84cdaa6bd3ce54c5a7f47478b7657b930ac3ed8eb"}, - {file = "fonttools-4.47.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32ab2e9702dff0dd4510c7bb958f265a8d3dd5c0e2547e7b5f7a3df4979abb07"}, - {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3a808f3c1d1df1f5bf39be869b6e0c263570cdafb5bdb2df66087733f566ea71"}, - {file = "fonttools-4.47.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ac71e2e201df041a2891067dc36256755b1229ae167edbdc419b16da78732c2f"}, - {file = "fonttools-4.47.2-cp312-cp312-win32.whl", hash = "sha256:69731e8bea0578b3c28fdb43dbf95b9386e2d49a399e9a4ad736b8e479b08085"}, - {file = "fonttools-4.47.2-cp312-cp312-win_amd64.whl", hash = "sha256:b3e1304e5f19ca861d86a72218ecce68f391646d85c851742d265787f55457a4"}, - {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:254d9a6f7be00212bf0c3159e0a420eb19c63793b2c05e049eb337f3023c5ecc"}, - {file = "fonttools-4.47.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:eabae77a07c41ae0b35184894202305c3ad211a93b2eb53837c2a1143c8bc952"}, - {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a86a5ab2873ed2575d0fcdf1828143cfc6b977ac448e3dc616bb1e3d20efbafa"}, - {file = "fonttools-4.47.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13819db8445a0cec8c3ff5f243af6418ab19175072a9a92f6cc8ca7d1452754b"}, - {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4e743935139aa485fe3253fc33fe467eab6ea42583fa681223ea3f1a93dd01e6"}, - {file = "fonttools-4.47.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d49ce3ea7b7173faebc5664872243b40cf88814ca3eb135c4a3cdff66af71946"}, - {file = "fonttools-4.47.2-cp38-cp38-win32.whl", hash = "sha256:94208ea750e3f96e267f394d5588579bb64cc628e321dbb1d4243ffbc291b18b"}, - {file = "fonttools-4.47.2-cp38-cp38-win_amd64.whl", hash = "sha256:0f750037e02beb8b3569fbff701a572e62a685d2a0e840d75816592280e5feae"}, - {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:3d71606c9321f6701642bd4746f99b6089e53d7e9817fc6b964e90d9c5f0ecc6"}, - {file = "fonttools-4.47.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:86e0427864c6c91cf77f16d1fb9bf1bbf7453e824589e8fb8461b6ee1144f506"}, - {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0a00bd0e68e88987dcc047ea31c26d40a3c61185153b03457956a87e39d43c37"}, - {file = "fonttools-4.47.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d77479fb885ef38a16a253a2f4096bc3d14e63a56d6246bfdb56365a12b20c"}, - {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5465df494f20a7d01712b072ae3ee9ad2887004701b95cb2cc6dcb9c2c97a899"}, - {file = "fonttools-4.47.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:4c811d3c73b6abac275babb8aa439206288f56fdb2c6f8835e3d7b70de8937a7"}, - {file = "fonttools-4.47.2-cp39-cp39-win32.whl", hash = "sha256:5b60e3afa9635e3dfd3ace2757039593e3bd3cf128be0ddb7a1ff4ac45fa5a50"}, - {file = "fonttools-4.47.2-cp39-cp39-win_amd64.whl", hash = "sha256:7ee48bd9d6b7e8f66866c9090807e3a4a56cf43ffad48962725a190e0dd774c8"}, - {file = "fonttools-4.47.2-py3-none-any.whl", hash = "sha256:7eb7ad665258fba68fd22228a09f347469d95a97fb88198e133595947a20a184"}, - {file = "fonttools-4.47.2.tar.gz", hash = "sha256:7df26dd3650e98ca45f1e29883c96a0b9f5bb6af8d632a6a108bc744fa0bd9b3"}, + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d970ecca0aac90d399e458f0b7a8a597e08f95de021f17785fb68e2dc0b99717"}, + {file = "fonttools-4.49.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ac9a745b7609f489faa65e1dc842168c18530874a5f5b742ac3dd79e26bca8bc"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ba0e00620ca28d4ca11fc700806fd69144b463aa3275e1b36e56c7c09915559"}, + {file = "fonttools-4.49.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdee3ab220283057e7840d5fb768ad4c2ebe65bdba6f75d5d7bf47f4e0ed7d29"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ce7033cb61f2bb65d8849658d3786188afd80f53dad8366a7232654804529532"}, + {file = "fonttools-4.49.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:07bc5ea02bb7bc3aa40a1eb0481ce20e8d9b9642a9536cde0218290dd6085828"}, + {file = "fonttools-4.49.0-cp310-cp310-win32.whl", hash = "sha256:86eef6aab7fd7c6c8545f3ebd00fd1d6729ca1f63b0cb4d621bccb7d1d1c852b"}, + {file = "fonttools-4.49.0-cp310-cp310-win_amd64.whl", hash = "sha256:1fac1b7eebfce75ea663e860e7c5b4a8831b858c17acd68263bc156125201abf"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:edc0cce355984bb3c1d1e89d6a661934d39586bb32191ebff98c600f8957c63e"}, + {file = "fonttools-4.49.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:83a0d9336de2cba86d886507dd6e0153df333ac787377325a39a2797ec529814"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36c8865bdb5cfeec88f5028e7e592370a0657b676c6f1d84a2108e0564f90e22"}, + {file = "fonttools-4.49.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:33037d9e56e2562c710c8954d0f20d25b8386b397250d65581e544edc9d6b942"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:8fb022d799b96df3eaa27263e9eea306bd3d437cc9aa981820850281a02b6c9a"}, + {file = "fonttools-4.49.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:33c584c0ef7dc54f5dd4f84082eabd8d09d1871a3d8ca2986b0c0c98165f8e86"}, + {file = "fonttools-4.49.0-cp311-cp311-win32.whl", hash = "sha256:cbe61b158deb09cffdd8540dc4a948d6e8f4d5b4f3bf5cd7db09bd6a61fee64e"}, + {file = "fonttools-4.49.0-cp311-cp311-win_amd64.whl", hash = "sha256:fc11e5114f3f978d0cea7e9853627935b30d451742eeb4239a81a677bdee6bf6"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:d647a0e697e5daa98c87993726da8281c7233d9d4ffe410812a4896c7c57c075"}, + {file = "fonttools-4.49.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f3bbe672df03563d1f3a691ae531f2e31f84061724c319652039e5a70927167e"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bebd91041dda0d511b0d303180ed36e31f4f54b106b1259b69fade68413aa7ff"}, + {file = "fonttools-4.49.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4145f91531fd43c50f9eb893faa08399816bb0b13c425667c48475c9f3a2b9b5"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ea329dafb9670ffbdf4dbc3b0e5c264104abcd8441d56de77f06967f032943cb"}, + {file = "fonttools-4.49.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:c076a9e548521ecc13d944b1d261ff3d7825048c338722a4bd126d22316087b7"}, + {file = "fonttools-4.49.0-cp312-cp312-win32.whl", hash = "sha256:b607ea1e96768d13be26d2b400d10d3ebd1456343eb5eaddd2f47d1c4bd00880"}, + {file = "fonttools-4.49.0-cp312-cp312-win_amd64.whl", hash = "sha256:a974c49a981e187381b9cc2c07c6b902d0079b88ff01aed34695ec5360767034"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:b85ec0bdd7bdaa5c1946398cbb541e90a6dfc51df76dfa88e0aaa41b335940cb"}, + {file = "fonttools-4.49.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:af20acbe198a8a790618ee42db192eb128afcdcc4e96d99993aca0b60d1faeb4"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d418b1fee41a1d14931f7ab4b92dc0bc323b490e41d7a333eec82c9f1780c75"}, + {file = "fonttools-4.49.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b44a52b8e6244b6548851b03b2b377a9702b88ddc21dcaf56a15a0393d425cb9"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:7c7125068e04a70739dad11857a4d47626f2b0bd54de39e8622e89701836eabd"}, + {file = "fonttools-4.49.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29e89d0e1a7f18bc30f197cfadcbef5a13d99806447c7e245f5667579a808036"}, + {file = "fonttools-4.49.0-cp38-cp38-win32.whl", hash = "sha256:9d95fa0d22bf4f12d2fb7b07a46070cdfc19ef5a7b1c98bc172bfab5bf0d6844"}, + {file = "fonttools-4.49.0-cp38-cp38-win_amd64.whl", hash = "sha256:768947008b4dc552d02772e5ebd49e71430a466e2373008ce905f953afea755a"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:08877e355d3dde1c11973bb58d4acad1981e6d1140711230a4bfb40b2b937ccc"}, + {file = "fonttools-4.49.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:fdb54b076f25d6b0f0298dc706acee5052de20c83530fa165b60d1f2e9cbe3cb"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0af65c720520710cc01c293f9c70bd69684365c6015cc3671db2b7d807fe51f2"}, + {file = "fonttools-4.49.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f255ce8ed7556658f6d23f6afd22a6d9bbc3edb9b96c96682124dc487e1bf42"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d00af0884c0e65f60dfaf9340e26658836b935052fdd0439952ae42e44fdd2be"}, + {file = "fonttools-4.49.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:263832fae27481d48dfafcc43174644b6706639661e242902ceb30553557e16c"}, + {file = "fonttools-4.49.0-cp39-cp39-win32.whl", hash = "sha256:0404faea044577a01bb82d47a8fa4bc7a54067fa7e324785dd65d200d6dd1133"}, + {file = "fonttools-4.49.0-cp39-cp39-win_amd64.whl", hash = "sha256:b050d362df50fc6e38ae3954d8c29bf2da52be384649ee8245fdb5186b620836"}, + {file = "fonttools-4.49.0-py3-none-any.whl", hash = "sha256:af281525e5dd7fa0b39fb1667b8d5ca0e2a9079967e14c4bfe90fd1cd13e0f18"}, + {file = "fonttools-4.49.0.tar.gz", hash = "sha256:ebf46e7f01b7af7861310417d7c49591a85d99146fc23a5ba82fdb28af156321"}, ] [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", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] +all = ["brotli (>=1.0.1)", "brotlicffi (>=0.8.0)", "fs (>=2.2.0,<3)", "lxml (>=4.0)", "lz4 (>=1.7.4.2)", "matplotlib", "munkres", "pycairo", "scipy", "skia-pathops (>=0.5.0)", "sympy", "uharfbuzz (>=0.23.0)", "unicodedata2 (>=15.1.0)", "xattr", "zopfli (>=0.1.4)"] graphite = ["lz4 (>=1.7.4.2)"] interpolatable = ["munkres", "pycairo", "scipy"] -lxml = ["lxml (>=4.0,<5)"] +lxml = ["lxml (>=4.0)"] pathops = ["skia-pathops (>=0.5.0)"] plot = ["matplotlib"] repacker = ["uharfbuzz (>=0.23.0)"] @@ -1340,13 +1340,13 @@ rewrite = ["tokenize-rt (>=3)"] [[package]] name = "geopandas" -version = "0.14.2" +version = "0.14.3" description = "Geographic pandas extensions" optional = false python-versions = ">=3.9" files = [ - {file = "geopandas-0.14.2-py3-none-any.whl", hash = "sha256:0efa61235a68862c1c6be89fc3707cdeba67667d5676bb19e24f3c57a8c2f723"}, - {file = "geopandas-0.14.2.tar.gz", hash = "sha256:6e71d57b8376f9fdc9f1c3aa3170e7e420e91778de854f51013ae66fd371ccdb"}, + {file = "geopandas-0.14.3-py3-none-any.whl", hash = "sha256:41b31ad39e21bc9e8c4254f78f8dc4ce3d33d144e22e630a00bb336c83160204"}, + {file = "geopandas-0.14.3.tar.gz", hash = "sha256:748af035d4a068a4ae00cab384acb61d387685c833b0022e0729aa45216b23ac"}, ] [package.dependencies] @@ -1563,13 +1563,13 @@ zoneinfo = ["backports.zoneinfo (>=0.2.1)", "tzdata (>=2022.1)"] [[package]] name = "identify" -version = "2.5.33" +version = "2.5.35" description = "File identification library for Python" optional = false python-versions = ">=3.8" files = [ - {file = "identify-2.5.33-py2.py3-none-any.whl", hash = "sha256:d40ce5fcd762817627670da8a7d8d8e65f24342d14539c59488dc603bf662e34"}, - {file = "identify-2.5.33.tar.gz", hash = "sha256:161558f9fe4559e1557e1bff323e8631f6a0e4837f7497767c1782832f16b62d"}, + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, ] [package.extras] @@ -2047,108 +2047,108 @@ testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] [[package]] name = "markupsafe" -version = "2.1.4" +version = "2.1.5" description = "Safely add untrusted strings to HTML/XML markup." optional = false python-versions = ">=3.7" files = [ - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:de8153a7aae3835484ac168a9a9bdaa0c5eee4e0bc595503c95d53b942879c84"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e888ff76ceb39601c59e219f281466c6d7e66bd375b4ec1ce83bcdc68306796b"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0b838c37ba596fcbfca71651a104a611543077156cb0a26fe0c475e1f152ee8"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac1ebf6983148b45b5fa48593950f90ed6d1d26300604f321c74a9ca1609f8e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0fbad3d346df8f9d72622ac71b69565e621ada2ce6572f37c2eae8dacd60385d"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d5291d98cd3ad9a562883468c690a2a238c4a6388ab3bd155b0c75dd55ece858"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:a7cc49ef48a3c7a0005a949f3c04f8baa5409d3f663a1b36f0eba9bfe2a0396e"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b83041cda633871572f0d3c41dddd5582ad7d22f65a72eacd8d3d6d00291df26"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win32.whl", hash = "sha256:0c26f67b3fe27302d3a412b85ef696792c4a2386293c53ba683a89562f9399b0"}, - {file = "MarkupSafe-2.1.4-cp310-cp310-win_amd64.whl", hash = "sha256:a76055d5cb1c23485d7ddae533229039b850db711c554a12ea64a0fd8a0129e2"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9e9e3c4020aa2dc62d5dd6743a69e399ce3de58320522948af6140ac959ab863"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0042d6a9880b38e1dd9ff83146cc3c9c18a059b9360ceae207805567aacccc69"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:55d03fea4c4e9fd0ad75dc2e7e2b6757b80c152c032ea1d1de487461d8140efc"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ab3a886a237f6e9c9f4f7d272067e712cdb4efa774bef494dccad08f39d8ae6"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abf5ebbec056817057bfafc0445916bb688a255a5146f900445d081db08cbabb"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e1a0d1924a5013d4f294087e00024ad25668234569289650929ab871231668e7"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:e7902211afd0af05fbadcc9a312e4cf10f27b779cf1323e78d52377ae4b72bea"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c669391319973e49a7c6230c218a1e3044710bc1ce4c8e6eb71f7e6d43a2c131"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win32.whl", hash = "sha256:31f57d64c336b8ccb1966d156932f3daa4fee74176b0fdc48ef580be774aae74"}, - {file = "MarkupSafe-2.1.4-cp311-cp311-win_amd64.whl", hash = "sha256:54a7e1380dfece8847c71bf7e33da5d084e9b889c75eca19100ef98027bd9f56"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:a76cd37d229fc385738bd1ce4cba2a121cf26b53864c1772694ad0ad348e509e"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:987d13fe1d23e12a66ca2073b8d2e2a75cec2ecb8eab43ff5624ba0ad42764bc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5244324676254697fe5c181fc762284e2c5fceeb1c4e3e7f6aca2b6f107e60dc"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78bc995e004681246e85e28e068111a4c3f35f34e6c62da1471e844ee1446250"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4d176cfdfde84f732c4a53109b293d05883e952bbba68b857ae446fa3119b4f"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:f9917691f410a2e0897d1ef99619fd3f7dd503647c8ff2475bf90c3cf222ad74"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:f06e5a9e99b7df44640767842f414ed5d7bedaaa78cd817ce04bbd6fd86e2dd6"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396549cea79e8ca4ba65525470d534e8a41070e6b3500ce2414921099cb73e8d"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win32.whl", hash = "sha256:f6be2d708a9d0e9b0054856f07ac7070fbe1754be40ca8525d5adccdbda8f475"}, - {file = "MarkupSafe-2.1.4-cp312-cp312-win_amd64.whl", hash = "sha256:5045e892cfdaecc5b4c01822f353cf2c8feb88a6ec1c0adef2a2e705eef0f656"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7a07f40ef8f0fbc5ef1000d0c78771f4d5ca03b4953fc162749772916b298fc4"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d18b66fe626ac412d96c2ab536306c736c66cf2a31c243a45025156cc190dc8a"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:698e84142f3f884114ea8cf83e7a67ca8f4ace8454e78fe960646c6c91c63bfa"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:49a3b78a5af63ec10d8604180380c13dcd870aba7928c1fe04e881d5c792dc4e"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:15866d7f2dc60cfdde12ebb4e75e41be862348b4728300c36cdf405e258415ec"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6aa5e2e7fc9bc042ae82d8b79d795b9a62bd8f15ba1e7594e3db243f158b5565"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:54635102ba3cf5da26eb6f96c4b8c53af8a9c0d97b64bdcb592596a6255d8518"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win32.whl", hash = "sha256:3583a3a3ab7958e354dc1d25be74aee6228938312ee875a22330c4dc2e41beb0"}, - {file = "MarkupSafe-2.1.4-cp37-cp37m-win_amd64.whl", hash = "sha256:d6e427c7378c7f1b2bef6a344c925b8b63623d3321c09a237b7cc0e77dd98ceb"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:bf1196dcc239e608605b716e7b166eb5faf4bc192f8a44b81e85251e62584bd2"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4df98d4a9cd6a88d6a585852f56f2155c9cdb6aec78361a19f938810aa020954"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b835aba863195269ea358cecc21b400276747cc977492319fd7682b8cd2c253d"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23984d1bdae01bee794267424af55eef4dfc038dc5d1272860669b2aa025c9e3"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c98c33ffe20e9a489145d97070a435ea0679fddaabcafe19982fe9c971987d5"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9896fca4a8eb246defc8b2a7ac77ef7553b638e04fbf170bff78a40fa8a91474"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b0fe73bac2fed83839dbdbe6da84ae2a31c11cfc1c777a40dbd8ac8a6ed1560f"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c7556bafeaa0a50e2fe7dc86e0382dea349ebcad8f010d5a7dc6ba568eaaa789"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win32.whl", hash = "sha256:fc1a75aa8f11b87910ffd98de62b29d6520b6d6e8a3de69a70ca34dea85d2a8a"}, - {file = "MarkupSafe-2.1.4-cp38-cp38-win_amd64.whl", hash = "sha256:3a66c36a3864df95e4f62f9167c734b3b1192cb0851b43d7cc08040c074c6279"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:765f036a3d00395a326df2835d8f86b637dbaf9832f90f5d196c3b8a7a5080cb"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:21e7af8091007bf4bebf4521184f4880a6acab8df0df52ef9e513d8e5db23411"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5c31fe855c77cad679b302aabc42d724ed87c043b1432d457f4976add1c2c3e"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7653fa39578957bc42e5ebc15cf4361d9e0ee4b702d7d5ec96cdac860953c5b4"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:47bb5f0142b8b64ed1399b6b60f700a580335c8e1c57f2f15587bd072012decc"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:fe8512ed897d5daf089e5bd010c3dc03bb1bdae00b35588c49b98268d4a01e00"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:36d7626a8cca4d34216875aee5a1d3d654bb3dac201c1c003d182283e3205949"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b6f14a9cd50c3cb100eb94b3273131c80d102e19bb20253ac7bd7336118a673a"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win32.whl", hash = "sha256:c8f253a84dbd2c63c19590fa86a032ef3d8cc18923b8049d91bcdeeb2581fbf6"}, - {file = "MarkupSafe-2.1.4-cp39-cp39-win_amd64.whl", hash = "sha256:8b570a1537367b52396e53325769608f2a687ec9a4363647af1cded8928af959"}, - {file = "MarkupSafe-2.1.4.tar.gz", hash = "sha256:3aae9af4cac263007fd6309c64c6ab4506dd2b79382d9d19a1994f9240b8db4f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, ] [[package]] name = "matplotlib" -version = "3.8.2" +version = "3.8.3" description = "Python plotting package" optional = false python-versions = ">=3.9" files = [ - {file = "matplotlib-3.8.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:09796f89fb71a0c0e1e2f4bdaf63fb2cefc84446bb963ecdeb40dfee7dfa98c7"}, - {file = "matplotlib-3.8.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:6f9c6976748a25e8b9be51ea028df49b8e561eed7809146da7a47dbecebab367"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b78e4f2cedf303869b782071b55fdde5987fda3038e9d09e58c91cc261b5ad18"}, - {file = "matplotlib-3.8.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e208f46cf6576a7624195aa047cb344a7f802e113bb1a06cfd4bee431de5e31"}, - {file = "matplotlib-3.8.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a569130ff53798ea5f50afce7406e91fdc471ca1e0e26ba976a8c734c9427a"}, - {file = "matplotlib-3.8.2-cp310-cp310-win_amd64.whl", hash = "sha256:830f00640c965c5b7f6bc32f0d4ce0c36dfe0379f7dd65b07a00c801713ec40a"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:d86593ccf546223eb75a39b44c32788e6f6440d13cfc4750c1c15d0fcb850b63"}, - {file = "matplotlib-3.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9a5430836811b7652991939012f43d2808a2db9b64ee240387e8c43e2e5578c8"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b9576723858a78751d5aacd2497b8aef29ffea6d1c95981505877f7ac28215c6"}, - {file = "matplotlib-3.8.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5ba9cbd8ac6cf422f3102622b20f8552d601bf8837e49a3afed188d560152788"}, - {file = "matplotlib-3.8.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:03f9d160a29e0b65c0790bb07f4f45d6a181b1ac33eb1bb0dd225986450148f0"}, - {file = "matplotlib-3.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:3773002da767f0a9323ba1a9b9b5d00d6257dbd2a93107233167cfb581f64717"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:4c318c1e95e2f5926fba326f68177dee364aa791d6df022ceb91b8221bd0a627"}, - {file = "matplotlib-3.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:091275d18d942cf1ee9609c830a1bc36610607d8223b1b981c37d5c9fc3e46a4"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b0f3b8ea0e99e233a4bcc44590f01604840d833c280ebb8fe5554fd3e6cfe8d"}, - {file = "matplotlib-3.8.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7b1704a530395aaf73912be741c04d181f82ca78084fbd80bc737be04848331"}, - {file = "matplotlib-3.8.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:533b0e3b0c6768eef8cbe4b583731ce25a91ab54a22f830db2b031e83cca9213"}, - {file = "matplotlib-3.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:0f4fc5d72b75e2c18e55eb32292659cf731d9d5b312a6eb036506304f4675630"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:deaed9ad4da0b1aea77fe0aa0cebb9ef611c70b3177be936a95e5d01fa05094f"}, - {file = "matplotlib-3.8.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:172f4d0fbac3383d39164c6caafd3255ce6fa58f08fc392513a0b1d3b89c4f89"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7d36c2209d9136cd8e02fab1c0ddc185ce79bc914c45054a9f514e44c787917"}, - {file = "matplotlib-3.8.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5864bdd7da445e4e5e011b199bb67168cdad10b501750367c496420f2ad00843"}, - {file = "matplotlib-3.8.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ef8345b48e95cee45ff25192ed1f4857273117917a4dcd48e3905619bcd9c9b8"}, - {file = "matplotlib-3.8.2-cp39-cp39-win_amd64.whl", hash = "sha256:7c48d9e221b637c017232e3760ed30b4e8d5dfd081daf327e829bf2a72c731b4"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:aa11b3c6928a1e496c1a79917d51d4cd5d04f8a2e75f21df4949eeefdf697f4b"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d1095fecf99eeb7384dabad4bf44b965f929a5f6079654b681193edf7169ec20"}, - {file = "matplotlib-3.8.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:bddfb1db89bfaa855912261c805bd0e10218923cc262b9159a49c29a7a1c1afa"}, - {file = "matplotlib-3.8.2.tar.gz", hash = "sha256:01a978b871b881ee76017152f1f1a0cbf6bd5f7b8ff8c96df0df1bd57d8755a1"}, + {file = "matplotlib-3.8.3-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:cf60138ccc8004f117ab2a2bad513cc4d122e55864b4fe7adf4db20ca68a078f"}, + {file = "matplotlib-3.8.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f557156f7116be3340cdeef7f128fa99b0d5d287d5f41a16e169819dcf22357"}, + {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f386cf162b059809ecfac3bcc491a9ea17da69fa35c8ded8ad154cd4b933d5ec"}, + {file = "matplotlib-3.8.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3c5f96f57b0369c288bf6f9b5274ba45787f7e0589a34d24bdbaf6d3344632f"}, + {file = "matplotlib-3.8.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:83e0f72e2c116ca7e571c57aa29b0fe697d4c6425c4e87c6e994159e0c008635"}, + {file = "matplotlib-3.8.3-cp310-cp310-win_amd64.whl", hash = "sha256:1c5c8290074ba31a41db1dc332dc2b62def469ff33766cbe325d32a3ee291aea"}, + {file = "matplotlib-3.8.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:5184e07c7e1d6d1481862ee361905b7059f7fe065fc837f7c3dc11eeb3f2f900"}, + {file = "matplotlib-3.8.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d7e7e0993d0758933b1a241a432b42c2db22dfa37d4108342ab4afb9557cbe3e"}, + {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:04b36ad07eac9740fc76c2aa16edf94e50b297d6eb4c081e3add863de4bb19a7"}, + {file = "matplotlib-3.8.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7c42dae72a62f14982f1474f7e5c9959fc4bc70c9de11cc5244c6e766200ba65"}, + {file = "matplotlib-3.8.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:bf5932eee0d428192c40b7eac1399d608f5d995f975cdb9d1e6b48539a5ad8d0"}, + {file = "matplotlib-3.8.3-cp311-cp311-win_amd64.whl", hash = "sha256:40321634e3a05ed02abf7c7b47a50be50b53ef3eaa3a573847431a545585b407"}, + {file = "matplotlib-3.8.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:09074f8057917d17ab52c242fdf4916f30e99959c1908958b1fc6032e2d0f6d4"}, + {file = "matplotlib-3.8.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5745f6d0fb5acfabbb2790318db03809a253096e98c91b9a31969df28ee604aa"}, + {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b97653d869a71721b639714b42d87cda4cfee0ee74b47c569e4874c7590c55c5"}, + {file = "matplotlib-3.8.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:242489efdb75b690c9c2e70bb5c6550727058c8a614e4c7716f363c27e10bba1"}, + {file = "matplotlib-3.8.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:83c0653c64b73926730bd9ea14aa0f50f202ba187c307a881673bad4985967b7"}, + {file = "matplotlib-3.8.3-cp312-cp312-win_amd64.whl", hash = "sha256:ef6c1025a570354297d6c15f7d0f296d95f88bd3850066b7f1e7b4f2f4c13a39"}, + {file = "matplotlib-3.8.3-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:c4af3f7317f8a1009bbb2d0bf23dfaba859eb7dd4ccbd604eba146dccaaaf0a4"}, + {file = "matplotlib-3.8.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4c6e00a65d017d26009bac6808f637b75ceade3e1ff91a138576f6b3065eeeba"}, + {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7b49ab49a3bea17802df6872f8d44f664ba8f9be0632a60c99b20b6db2165b7"}, + {file = "matplotlib-3.8.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6728dde0a3997396b053602dbd907a9bd64ec7d5cf99e728b404083698d3ca01"}, + {file = "matplotlib-3.8.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:813925d08fb86aba139f2d31864928d67511f64e5945ca909ad5bc09a96189bb"}, + {file = "matplotlib-3.8.3-cp39-cp39-win_amd64.whl", hash = "sha256:cd3a0c2be76f4e7be03d34a14d49ded6acf22ef61f88da600a18a5cd8b3c5f3c"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:fa93695d5c08544f4a0dfd0965f378e7afc410d8672816aff1e81be1f45dbf2e"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e9764df0e8778f06414b9d281a75235c1e85071f64bb5d71564b97c1306a2afc"}, + {file = "matplotlib-3.8.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:5e431a09e6fab4012b01fc155db0ce6dccacdbabe8198197f523a4ef4805eb26"}, + {file = "matplotlib-3.8.3.tar.gz", hash = "sha256:7b416239e9ae38be54b028abbf9048aff5054a9aba5416bef0bd17f9162ce161"}, ] [package.dependencies] @@ -2194,13 +2194,13 @@ files = [ [[package]] name = "metadrive-simulator" -version = "0.4.2.2" +version = "0.4.2.3" description = "An open-ended driving simulator with infinite scenes" optional = false python-versions = ">=3.6, <3.12" files = [ - {file = "metadrive-simulator-0.4.2.2.tar.gz", hash = "sha256:dcce9f9c73b6055e70480af8543058b95e8c4f68d2595107f3ef36c9be03f6bb"}, - {file = "metadrive_simulator-0.4.2.2-py3-none-any.whl", hash = "sha256:165ba0b5275313a71090ba0e73d51b7b5e05391b071d3a2114d999ac9287d150"}, + {file = "metadrive-simulator-0.4.2.3.tar.gz", hash = "sha256:bcda7d07146128161b0bc2cc337e01612b9222202706370043b52f7936a8a277"}, + {file = "metadrive_simulator-0.4.2.3-py3-none-any.whl", hash = "sha256:f6fff20b931bb956c55e0e81bf1f6b641169a84a4c853435a8b26bd51c8e9876"}, ] [package.dependencies] @@ -2281,22 +2281,22 @@ tests = ["pytest (>=4.6)"] [[package]] name = "msal" -version = "1.26.0" +version = "1.27.0" 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." optional = false python-versions = ">=2.7" files = [ - {file = "msal-1.26.0-py2.py3-none-any.whl", hash = "sha256:be77ba6a8f49c9ff598bbcdc5dfcf1c9842f3044300109af738e8c3e371065b5"}, - {file = "msal-1.26.0.tar.gz", hash = "sha256:224756079fe338be838737682b49f8ebc20a87c1c5eeaf590daae4532b83de15"}, + {file = "msal-1.27.0-py2.py3-none-any.whl", hash = "sha256:572d07149b83e7343a85a3bcef8e581167b4ac76befcbbb6eef0c0e19643cdc0"}, + {file = "msal-1.27.0.tar.gz", hash = "sha256:3109503c038ba6b307152b0e8d34f98113f2e7a78986e28d0baf5b5303afda52"}, ] [package.dependencies] -cryptography = ">=0.6,<44" +cryptography = ">=0.6,<45" PyJWT = {version = ">=1.0.0,<3", extras = ["crypto"]} requests = ">=2.0.0,<3" [package.extras] -broker = ["pymsalruntime (>=0.13.2,<0.14)"] +broker = ["pymsalruntime (>=0.13.2,<0.15)"] [[package]] name = "msal-extensions" @@ -2319,85 +2319,101 @@ portalocker = [ [[package]] name = "multidict" -version = "6.0.4" +version = "6.0.5" description = "multidict implementation" optional = false python-versions = ">=3.7" files = [ - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1a97283e0c85772d613878028fec909f003993e1007eafa715b24b377cb9b8"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:eeb6dcc05e911516ae3d1f207d4b0520d07f54484c49dfc294d6e7d63b734171"}, - {file = "multidict-6.0.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:d6d635d5209b82a3492508cf5b365f3446afb65ae7ebd755e70e18f287b0adf7"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c048099e4c9e9d615545e2001d3d8a4380bd403e1a0578734e0d31703d1b0c0b"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ea20853c6dbbb53ed34cb4d080382169b6f4554d394015f1bef35e881bf83547"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:16d232d4e5396c2efbbf4f6d4df89bfa905eb0d4dc5b3549d872ab898451f569"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:36c63aaa167f6c6b04ef2c85704e93af16c11d20de1d133e39de6a0e84582a93"}, - {file = "multidict-6.0.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:64bdf1086b6043bf519869678f5f2757f473dee970d7abf6da91ec00acb9cb98"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:43644e38f42e3af682690876cff722d301ac585c5b9e1eacc013b7a3f7b696a0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7582a1d1030e15422262de9f58711774e02fa80df0d1578995c76214f6954988"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:ddff9c4e225a63a5afab9dd15590432c22e8057e1a9a13d28ed128ecf047bbdc"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:ee2a1ece51b9b9e7752e742cfb661d2a29e7bcdba2d27e66e28a99f1890e4fa0"}, - {file = "multidict-6.0.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a2e4369eb3d47d2034032a26c7a80fcb21a2cb22e1173d761a162f11e562caa5"}, - {file = "multidict-6.0.4-cp310-cp310-win32.whl", hash = "sha256:574b7eae1ab267e5f8285f0fe881f17efe4b98c39a40858247720935b893bba8"}, - {file = "multidict-6.0.4-cp310-cp310-win_amd64.whl", hash = "sha256:4dcbb0906e38440fa3e325df2359ac6cb043df8e58c965bb45f4e406ecb162cc"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0dfad7a5a1e39c53ed00d2dd0c2e36aed4650936dc18fd9a1826a5ae1cad6f03"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:64da238a09d6039e3bd39bb3aee9c21a5e34f28bfa5aa22518581f910ff94af3"}, - {file = "multidict-6.0.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff959bee35038c4624250473988b24f846cbeb2c6639de3602c073f10410ceba"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:01a3a55bd90018c9c080fbb0b9f4891db37d148a0a18722b42f94694f8b6d4c9"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c5cb09abb18c1ea940fb99360ea0396f34d46566f157122c92dfa069d3e0e982"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:666daae833559deb2d609afa4490b85830ab0dfca811a98b70a205621a6109fe"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:11bdf3f5e1518b24530b8241529d2050014c884cf18b6fc69c0c2b30ca248710"}, - {file = "multidict-6.0.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7d18748f2d30f94f498e852c67d61261c643b349b9d2a581131725595c45ec6c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:458f37be2d9e4c95e2d8866a851663cbc76e865b78395090786f6cd9b3bbf4f4"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b1a2eeedcead3a41694130495593a559a668f382eee0727352b9a41e1c45759a"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:7d6ae9d593ef8641544d6263c7fa6408cc90370c8cb2bbb65f8d43e5b0351d9c"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:5979b5632c3e3534e42ca6ff856bb24b2e3071b37861c2c727ce220d80eee9ed"}, - {file = "multidict-6.0.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:dcfe792765fab89c365123c81046ad4103fcabbc4f56d1c1997e6715e8015461"}, - {file = "multidict-6.0.4-cp311-cp311-win32.whl", hash = "sha256:3601a3cece3819534b11d4efc1eb76047488fddd0c85a3948099d5da4d504636"}, - {file = "multidict-6.0.4-cp311-cp311-win_amd64.whl", hash = "sha256:81a4f0b34bd92df3da93315c6a59034df95866014ac08535fc819f043bfd51f0"}, - {file = "multidict-6.0.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:67040058f37a2a51ed8ea8f6b0e6ee5bd78ca67f169ce6122f3e2ec80dfe9b78"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:853888594621e6604c978ce2a0444a1e6e70c8d253ab65ba11657659dcc9100f"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:39ff62e7d0f26c248b15e364517a72932a611a9b75f35b45be078d81bdb86603"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:af048912e045a2dc732847d33821a9d84ba553f5c5f028adbd364dd4765092ac"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b1e8b901e607795ec06c9e42530788c45ac21ef3aaa11dbd0c69de543bfb79a9"}, - {file = "multidict-6.0.4-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:62501642008a8b9871ddfccbf83e4222cf8ac0d5aeedf73da36153ef2ec222d2"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:99b76c052e9f1bc0721f7541e5e8c05db3941eb9ebe7b8553c625ef88d6eefde"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:509eac6cf09c794aa27bcacfd4d62c885cce62bef7b2c3e8b2e49d365b5003fe"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:21a12c4eb6ddc9952c415f24eef97e3e55ba3af61f67c7bc388dcdec1404a067"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:5cad9430ab3e2e4fa4a2ef4450f548768400a2ac635841bc2a56a2052cdbeb87"}, - {file = "multidict-6.0.4-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ab55edc2e84460694295f401215f4a58597f8f7c9466faec545093045476327d"}, - {file = "multidict-6.0.4-cp37-cp37m-win32.whl", hash = "sha256:5a4dcf02b908c3b8b17a45fb0f15b695bf117a67b76b7ad18b73cf8e92608775"}, - {file = "multidict-6.0.4-cp37-cp37m-win_amd64.whl", hash = "sha256:6ed5f161328b7df384d71b07317f4d8656434e34591f20552c7bcef27b0ab88e"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:5fc1b16f586f049820c5c5b17bb4ee7583092fa0d1c4e28b5239181ff9532e0c"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1502e24330eb681bdaa3eb70d6358e818e8e8f908a22a1851dfd4e15bc2f8161"}, - {file = "multidict-6.0.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:b692f419760c0e65d060959df05f2a531945af31fda0c8a3b3195d4efd06de11"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45e1ecb0379bfaab5eef059f50115b54571acfbe422a14f668fc8c27ba410e7e"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ddd3915998d93fbcd2566ddf9cf62cdb35c9e093075f862935573d265cf8f65d"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:59d43b61c59d82f2effb39a93c48b845efe23a3852d201ed2d24ba830d0b4cf2"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc8e1d0c705233c5dd0c5e6460fbad7827d5d36f310a0fadfd45cc3029762258"}, - {file = "multidict-6.0.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6aa0418fcc838522256761b3415822626f866758ee0bc6632c9486b179d0b52"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:6748717bb10339c4760c1e63da040f5f29f5ed6e59d76daee30305894069a660"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:4d1a3d7ef5e96b1c9e92f973e43aa5e5b96c659c9bc3124acbbd81b0b9c8a951"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4372381634485bec7e46718edc71528024fcdc6f835baefe517b34a33c731d60"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:fc35cb4676846ef752816d5be2193a1e8367b4c1397b74a565a9d0389c433a1d"}, - {file = "multidict-6.0.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:4b9d9e4e2b37daddb5c23ea33a3417901fa7c7b3dee2d855f63ee67a0b21e5b1"}, - {file = "multidict-6.0.4-cp38-cp38-win32.whl", hash = "sha256:e41b7e2b59679edfa309e8db64fdf22399eec4b0b24694e1b2104fb789207779"}, - {file = "multidict-6.0.4-cp38-cp38-win_amd64.whl", hash = "sha256:d6c254ba6e45d8e72739281ebc46ea5eb5f101234f3ce171f0e9f5cc86991480"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:16ab77bbeb596e14212e7bab8429f24c1579234a3a462105cda4a66904998664"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bc779e9e6f7fda81b3f9aa58e3a6091d49ad528b11ed19f6621408806204ad35"}, - {file = "multidict-6.0.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4ceef517eca3e03c1cceb22030a3e39cb399ac86bff4e426d4fc6ae49052cc60"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:281af09f488903fde97923c7744bb001a9b23b039a909460d0f14edc7bf59706"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:52f2dffc8acaba9a2f27174c41c9e57f60b907bb9f096b36b1a1f3be71c6284d"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b41156839806aecb3641f3208c0dafd3ac7775b9c4c422d82ee2a45c34ba81ca"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5e3fc56f88cc98ef8139255cf8cd63eb2c586531e43310ff859d6bb3a6b51f1"}, - {file = "multidict-6.0.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8316a77808c501004802f9beebde51c9f857054a0c871bd6da8280e718444449"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f70b98cd94886b49d91170ef23ec5c0e8ebb6f242d734ed7ed677b24d50c82cf"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bf6774e60d67a9efe02b3616fee22441d86fab4c6d335f9d2051d19d90a40063"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:e69924bfcdda39b722ef4d9aa762b2dd38e4632b3641b1d9a57ca9cd18f2f83a"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:6b181d8c23da913d4ff585afd1155a0e1194c0b50c54fcfe286f70cdaf2b7176"}, - {file = "multidict-6.0.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:52509b5be062d9eafc8170e53026fbc54cf3b32759a23d07fd935fb04fc22d95"}, - {file = "multidict-6.0.4-cp39-cp39-win32.whl", hash = "sha256:27c523fbfbdfd19c6867af7346332b62b586eed663887392cff78d614f9ec313"}, - {file = "multidict-6.0.4-cp39-cp39-win_amd64.whl", hash = "sha256:33029f5734336aa0d4c0384525da0387ef89148dc7191aae00ca5fb23d7aafc2"}, - {file = "multidict-6.0.4.tar.gz", hash = "sha256:3666906492efb76453c0e7b97f2cf459b0682e7402c0489a95484965dbc1da49"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:228b644ae063c10e7f324ab1ab6b548bdf6f8b47f3ec234fef1093bc2735e5f9"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:896ebdcf62683551312c30e20614305f53125750803b614e9e6ce74a96232604"}, + {file = "multidict-6.0.5-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:411bf8515f3be9813d06004cac41ccf7d1cd46dfe233705933dd163b60e37600"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1d147090048129ce3c453f0292e7697d333db95e52616b3793922945804a433c"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:215ed703caf15f578dca76ee6f6b21b7603791ae090fbf1ef9d865571039ade5"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7c6390cf87ff6234643428991b7359b5f59cc15155695deb4eda5c777d2b880f"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21fd81c4ebdb4f214161be351eb5bcf385426bf023041da2fd9e60681f3cebae"}, + {file = "multidict-6.0.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3cc2ad10255f903656017363cd59436f2111443a76f996584d1077e43ee51182"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:6939c95381e003f54cd4c5516740faba40cf5ad3eeff460c3ad1d3e0ea2549bf"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:220dd781e3f7af2c2c1053da9fa96d9cf3072ca58f057f4c5adaaa1cab8fc442"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:766c8f7511df26d9f11cd3a8be623e59cca73d44643abab3f8c8c07620524e4a"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:fe5d7785250541f7f5019ab9cba2c71169dc7d74d0f45253f8313f436458a4ef"}, + {file = "multidict-6.0.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c1c1496e73051918fcd4f58ff2e0f2f3066d1c76a0c6aeffd9b45d53243702cc"}, + {file = "multidict-6.0.5-cp310-cp310-win32.whl", hash = "sha256:7afcdd1fc07befad18ec4523a782cde4e93e0a2bf71239894b8d61ee578c1319"}, + {file = "multidict-6.0.5-cp310-cp310-win_amd64.whl", hash = "sha256:99f60d34c048c5c2fabc766108c103612344c46e35d4ed9ae0673d33c8fb26e8"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f285e862d2f153a70586579c15c44656f888806ed0e5b56b64489afe4a2dbfba"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:53689bb4e102200a4fafa9de9c7c3c212ab40a7ab2c8e474491914d2305f187e"}, + {file = "multidict-6.0.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:612d1156111ae11d14afaf3a0669ebf6c170dbb735e510a7438ffe2369a847fd"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7be7047bd08accdb7487737631d25735c9a04327911de89ff1b26b81745bd4e3"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de170c7b4fe6859beb8926e84f7d7d6c693dfe8e27372ce3b76f01c46e489fcf"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:04bde7a7b3de05732a4eb39c94574db1ec99abb56162d6c520ad26f83267de29"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85f67aed7bb647f93e7520633d8f51d3cbc6ab96957c71272b286b2f30dc70ed"}, + {file = "multidict-6.0.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425bf820055005bfc8aa9a0b99ccb52cc2f4070153e34b701acc98d201693733"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:d3eb1ceec286eba8220c26f3b0096cf189aea7057b6e7b7a2e60ed36b373b77f"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:7901c05ead4b3fb75113fb1dd33eb1253c6d3ee37ce93305acd9d38e0b5f21a4"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:e0e79d91e71b9867c73323a3444724d496c037e578a0e1755ae159ba14f4f3d1"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:29bfeb0dff5cb5fdab2023a7a9947b3b4af63e9c47cae2a10ad58394b517fddc"}, + {file = "multidict-6.0.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e030047e85cbcedbfc073f71836d62dd5dadfbe7531cae27789ff66bc551bd5e"}, + {file = "multidict-6.0.5-cp311-cp311-win32.whl", hash = "sha256:2f4848aa3baa109e6ab81fe2006c77ed4d3cd1e0ac2c1fbddb7b1277c168788c"}, + {file = "multidict-6.0.5-cp311-cp311-win_amd64.whl", hash = "sha256:2faa5ae9376faba05f630d7e5e6be05be22913782b927b19d12b8145968a85ea"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:51d035609b86722963404f711db441cf7134f1889107fb171a970c9701f92e1e"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:cbebcd5bcaf1eaf302617c114aa67569dd3f090dd0ce8ba9e35e9985b41ac35b"}, + {file = "multidict-6.0.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2ffc42c922dbfddb4a4c3b438eb056828719f07608af27d163191cb3e3aa6cc5"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ceb3b7e6a0135e092de86110c5a74e46bda4bd4fbfeeb3a3bcec79c0f861e450"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:79660376075cfd4b2c80f295528aa6beb2058fd289f4c9252f986751a4cd0496"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4428b29611e989719874670fd152b6625500ad6c686d464e99f5aaeeaca175a"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d84a5c3a5f7ce6db1f999fb9438f686bc2e09d38143f2d93d8406ed2dd6b9226"}, + {file = "multidict-6.0.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76c0de87358b192de7ea9649beb392f107dcad9ad27276324c24c91774ca5271"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:79a6d2ba910adb2cbafc95dad936f8b9386e77c84c35bc0add315b856d7c3abb"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:92d16a3e275e38293623ebf639c471d3e03bb20b8ebb845237e0d3664914caef"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:fb616be3538599e797a2017cccca78e354c767165e8858ab5116813146041a24"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:14c2976aa9038c2629efa2c148022ed5eb4cb939e15ec7aace7ca932f48f9ba6"}, + {file = "multidict-6.0.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:435a0984199d81ca178b9ae2c26ec3d49692d20ee29bc4c11a2a8d4514c67eda"}, + {file = "multidict-6.0.5-cp312-cp312-win32.whl", hash = "sha256:9fe7b0653ba3d9d65cbe7698cca585bf0f8c83dbbcc710db9c90f478e175f2d5"}, + {file = "multidict-6.0.5-cp312-cp312-win_amd64.whl", hash = "sha256:01265f5e40f5a17f8241d52656ed27192be03bfa8764d88e8220141d1e4b3556"}, + {file = "multidict-6.0.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:19fe01cea168585ba0f678cad6f58133db2aa14eccaf22f88e4a6dccadfad8b3"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6bf7a982604375a8d49b6cc1b781c1747f243d91b81035a9b43a2126c04766f5"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:107c0cdefe028703fb5dafe640a409cb146d44a6ae201e55b35a4af8e95457dd"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:403c0911cd5d5791605808b942c88a8155c2592e05332d2bf78f18697a5fa15e"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aeaf541ddbad8311a87dd695ed9642401131ea39ad7bc8cf3ef3967fd093b626"}, + {file = "multidict-6.0.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e4972624066095e52b569e02b5ca97dbd7a7ddd4294bf4e7247d52635630dd83"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:d946b0a9eb8aaa590df1fe082cee553ceab173e6cb5b03239716338629c50c7a"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:b55358304d7a73d7bdf5de62494aaf70bd33015831ffd98bc498b433dfe5b10c"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:a3145cb08d8625b2d3fee1b2d596a8766352979c9bffe5d7833e0503d0f0b5e5"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:d65f25da8e248202bd47445cec78e0025c0fe7582b23ec69c3b27a640dd7a8e3"}, + {file = "multidict-6.0.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:c9bf56195c6bbd293340ea82eafd0071cb3d450c703d2c93afb89f93b8386ccc"}, + {file = "multidict-6.0.5-cp37-cp37m-win32.whl", hash = "sha256:69db76c09796b313331bb7048229e3bee7928eb62bab5e071e9f7fcc4879caee"}, + {file = "multidict-6.0.5-cp37-cp37m-win_amd64.whl", hash = "sha256:fce28b3c8a81b6b36dfac9feb1de115bab619b3c13905b419ec71d03a3fc1423"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:76f067f5121dcecf0d63a67f29080b26c43c71a98b10c701b0677e4a065fbd54"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b82cc8ace10ab5bd93235dfaab2021c70637005e1ac787031f4d1da63d493c1d"}, + {file = "multidict-6.0.5-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cb241881eefd96b46f89b1a056187ea8e9ba14ab88ba632e68d7a2ecb7aadf7"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8e94e6912639a02ce173341ff62cc1201232ab86b8a8fcc05572741a5dc7d93"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09a892e4a9fb47331da06948690ae38eaa2426de97b4ccbfafbdcbe5c8f37ff8"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55205d03e8a598cfc688c71ca8ea5f66447164efff8869517f175ea632c7cb7b"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:37b15024f864916b4951adb95d3a80c9431299080341ab9544ed148091b53f50"}, + {file = "multidict-6.0.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2a1dee728b52b33eebff5072817176c172050d44d67befd681609b4746e1c2e"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:edd08e6f2f1a390bf137080507e44ccc086353c8e98c657e666c017718561b89"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:60d698e8179a42ec85172d12f50b1668254628425a6bd611aba022257cac1386"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:3d25f19500588cbc47dc19081d78131c32637c25804df8414463ec908631e453"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:4cc0ef8b962ac7a5e62b9e826bd0cd5040e7d401bc45a6835910ed699037a461"}, + {file = "multidict-6.0.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:eca2e9d0cc5a889850e9bbd68e98314ada174ff6ccd1129500103df7a94a7a44"}, + {file = "multidict-6.0.5-cp38-cp38-win32.whl", hash = "sha256:4a6a4f196f08c58c59e0b8ef8ec441d12aee4125a7d4f4fef000ccb22f8d7241"}, + {file = "multidict-6.0.5-cp38-cp38-win_amd64.whl", hash = "sha256:0275e35209c27a3f7951e1ce7aaf93ce0d163b28948444bec61dd7badc6d3f8c"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:e7be68734bd8c9a513f2b0cfd508802d6609da068f40dc57d4e3494cefc92929"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d9ea7a7e779d7a3561aade7d596649fbecfa5c08a7674b11b423783217933f9"}, + {file = "multidict-6.0.5-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ea1456df2a27c73ce51120fa2f519f1bea2f4a03a917f4a43c8707cf4cbbae1a"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cf590b134eb70629e350691ecca88eac3e3b8b3c86992042fb82e3cb1830d5e1"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5c0631926c4f58e9a5ccce555ad7747d9a9f8b10619621f22f9635f069f6233e"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dce1c6912ab9ff5f179eaf6efe7365c1f425ed690b03341911bf4939ef2f3046"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c0868d64af83169e4d4152ec612637a543f7a336e4a307b119e98042e852ad9c"}, + {file = "multidict-6.0.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:141b43360bfd3bdd75f15ed811850763555a251e38b2405967f8e25fb43f7d40"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7df704ca8cf4a073334e0427ae2345323613e4df18cc224f647f251e5e75a527"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:6214c5a5571802c33f80e6c84713b2c79e024995b9c5897f794b43e714daeec9"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:cd6c8fca38178e12c00418de737aef1261576bd1b6e8c6134d3e729a4e858b38"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:e02021f87a5b6932fa6ce916ca004c4d441509d33bbdbeca70d05dff5e9d2479"}, + {file = "multidict-6.0.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ebd8d160f91a764652d3e51ce0d2956b38efe37c9231cd82cfc0bed2e40b581c"}, + {file = "multidict-6.0.5-cp39-cp39-win32.whl", hash = "sha256:04da1bb8c8dbadf2a18a452639771951c662c5ad03aefe4884775454be322c9b"}, + {file = "multidict-6.0.5-cp39-cp39-win_amd64.whl", hash = "sha256:d6f6d4f185481c9669b9447bf9d9cf3b95a0e9df9d169bbc17e363b7d5487755"}, + {file = "multidict-6.0.5-py3-none-any.whl", hash = "sha256:0d63c74e3d7ab26de115c49bffc92cc77ed23395303d496eae515d4204a625e7"}, + {file = "multidict-6.0.5.tar.gz", hash = "sha256:f7e301075edaf50500f0b341543c41194d8df3ae5caf4702f2095f3ca73dd8da"}, ] [[package]] @@ -2592,35 +2608,36 @@ reference = ["Pillow", "google-re2"] [[package]] name = "onnxruntime" -version = "1.16.3" +version = "1.17.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime-1.16.3-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:3bc41f323ac77acfed190be8ffdc47a6a75e4beeb3473fbf55eeb075ccca8df2"}, - {file = "onnxruntime-1.16.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:212741b519ee61a4822c79c47147d63a8b0ffde25cd33988d3d7be9fbd51005d"}, - {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f91f5497fe3df4ceee2f9e66c6148d9bfeb320cd6a71df361c66c5b8bac985a"}, - {file = "onnxruntime-1.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ef2b1fc269cabd27f129fb9058917d6fdc89b188c49ed8700f300b945c81f889"}, - {file = "onnxruntime-1.16.3-cp310-cp310-win32.whl", hash = "sha256:f36b56a593b49a3c430be008c2aea6658d91a3030115729609ec1d5ffbaab1b6"}, - {file = "onnxruntime-1.16.3-cp310-cp310-win_amd64.whl", hash = "sha256:3c467eaa3d2429c026b10c3d17b78b7f311f718ef9d2a0d6938e5c3c2611b0cf"}, - {file = "onnxruntime-1.16.3-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:a225bb683991001d111f75323d355b3590e75e16b5e0f07a0401e741a0143ea1"}, - {file = "onnxruntime-1.16.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9aded21fe3d898edd86be8aa2eb995aa375e800ad3dfe4be9f618a20b8ee3630"}, - {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00cccc37a5195c8fca5011b9690b349db435986bd508eb44c9fce432da9228a4"}, - {file = "onnxruntime-1.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e253e572021563226a86f1c024f8f70cdae28f2fb1cc8c3a9221e8b1ce37db5"}, - {file = "onnxruntime-1.16.3-cp311-cp311-win32.whl", hash = "sha256:a82a8f0b4c978d08f9f5c7a6019ae51151bced9fd91e5aaa0c20a9e4ac7a60b6"}, - {file = "onnxruntime-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:78d81d9af457a1dc90db9a7da0d09f3ccb1288ea1236c6ab19f0ca61f3eee2d3"}, - {file = "onnxruntime-1.16.3-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:04ebcd29c20473596a1412e471524b2fb88d55e6301c40b98dd2407b5911595f"}, - {file = "onnxruntime-1.16.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9996bab0f202a6435ab867bc55598f15210d0b72794d5de83712b53d564084ae"}, - {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b8f5083f903408238883821dd8c775f8120cb4a604166dbdabe97f4715256d5"}, - {file = "onnxruntime-1.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c2dcf1b70f8434abb1116fe0975c00e740722aaf321997195ea3618cc00558e"}, - {file = "onnxruntime-1.16.3-cp38-cp38-win32.whl", hash = "sha256:d4a0151e1accd04da6711f6fd89024509602f82c65a754498e960b032359b02d"}, - {file = "onnxruntime-1.16.3-cp38-cp38-win_amd64.whl", hash = "sha256:e8aa5bba78afbd4d8a2654b14ec7462ff3ce4a6aad312a3c2d2c2b65009f2541"}, - {file = "onnxruntime-1.16.3-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:6829dc2a79d48c911fedaf4c0f01e03c86297d32718a3fdee7a282766dfd282a"}, - {file = "onnxruntime-1.16.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:76f876c53bfa912c6c242fc38213a6f13f47612d4360bc9d599bd23753e53161"}, - {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4137e5d443e2dccebe5e156a47f1d6d66f8077b03587c35f11ee0c7eda98b533"}, - {file = "onnxruntime-1.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c56695c1a343c7c008b647fff3df44da63741fbe7b6003ef576758640719be7b"}, - {file = "onnxruntime-1.16.3-cp39-cp39-win32.whl", hash = "sha256:985a029798744ce4743fcf8442240fed35c8e4d4d30ec7d0c2cdf1388cd44408"}, - {file = "onnxruntime-1.16.3-cp39-cp39-win_amd64.whl", hash = "sha256:28ff758b17ce3ca6bcad3d936ec53bd7f5482e7630a13f6dcae518eba8f71d85"}, + {file = "onnxruntime-1.17.1-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:d43ac17ac4fa3c9096ad3c0e5255bb41fd134560212dc124e7f52c3159af5d21"}, + {file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55b5e92a4c76a23981c998078b9bf6145e4fb0b016321a8274b1607bd3c6bd35"}, + {file = "onnxruntime-1.17.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ebbcd2bc3a066cf54e6f18c75708eb4d309ef42be54606d22e5bdd78afc5b0d7"}, + {file = "onnxruntime-1.17.1-cp310-cp310-win32.whl", hash = "sha256:5e3716b5eec9092e29a8d17aab55e737480487deabfca7eac3cd3ed952b6ada9"}, + {file = "onnxruntime-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:fbb98cced6782ae1bb799cc74ddcbbeeae8819f3ad1d942a74d88e72b6511337"}, + {file = "onnxruntime-1.17.1-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:36fd6f87a1ecad87e9c652e42407a50fb305374f9a31d71293eb231caae18784"}, + {file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:99a8bddeb538edabc524d468edb60ad4722cff8a49d66f4e280c39eace70500b"}, + {file = "onnxruntime-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fd7fddb4311deb5a7d3390cd8e9b3912d4d963efbe4dfe075edbaf18d01c024e"}, + {file = "onnxruntime-1.17.1-cp311-cp311-win32.whl", hash = "sha256:606a7cbfb6680202b0e4f1890881041ffc3ac6e41760a25763bd9fe146f0b335"}, + {file = "onnxruntime-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:53e4e06c0a541696ebdf96085fd9390304b7b04b748a19e02cf3b35c869a1e76"}, + {file = "onnxruntime-1.17.1-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:40f08e378e0f85929712a2b2c9b9a9cc400a90c8a8ca741d1d92c00abec60843"}, + {file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ac79da6d3e1bb4590f1dad4bb3c2979d7228555f92bb39820889af8b8e6bd472"}, + {file = "onnxruntime-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ae9ba47dc099004e3781f2d0814ad710a13c868c739ab086fc697524061695ea"}, + {file = "onnxruntime-1.17.1-cp312-cp312-win32.whl", hash = "sha256:2dff1a24354220ac30e4a4ce2fb1df38cb1ea59f7dac2c116238d63fe7f4c5ff"}, + {file = "onnxruntime-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:6226a5201ab8cafb15e12e72ff2a4fc8f50654e8fa5737c6f0bd57c5ff66827e"}, + {file = "onnxruntime-1.17.1-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:cd0c07c0d1dfb8629e820b05fda5739e4835b3b82faf43753d2998edf2cf00aa"}, + {file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:617ebdf49184efa1ba6e4467e602fbfa029ed52c92f13ce3c9f417d303006381"}, + {file = "onnxruntime-1.17.1-cp38-cp38-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9dae9071e3facdf2920769dceee03b71c684b6439021defa45b830d05e148924"}, + {file = "onnxruntime-1.17.1-cp38-cp38-win32.whl", hash = "sha256:835d38fa1064841679433b1aa8138b5e1218ddf0cfa7a3ae0d056d8fd9cec713"}, + {file = "onnxruntime-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:96621e0c555c2453bf607606d08af3f70fbf6f315230c28ddea91754e17ad4e6"}, + {file = "onnxruntime-1.17.1-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:7a9539935fb2d78ebf2cf2693cad02d9930b0fb23cdd5cf37a7df813e977674d"}, + {file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45c6a384e9d9a29c78afff62032a46a993c477b280247a7e335df09372aedbe9"}, + {file = "onnxruntime-1.17.1-cp39-cp39-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4e19f966450f16863a1d6182a685ca33ae04d7772a76132303852d05b95411ea"}, + {file = "onnxruntime-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e2ae712d64a42aac29ed7a40a426cb1e624a08cfe9273dcfe681614aa65b07dc"}, + {file = "onnxruntime-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:f7e9f7fb049825cdddf4a923cfc7c649d84d63c0134315f8e0aa9e0c3004672c"}, ] [package.dependencies] @@ -2633,19 +2650,21 @@ sympy = "*" [[package]] name = "onnxruntime-gpu" -version = "1.16.3" +version = "1.17.1" description = "ONNX Runtime is a runtime accelerator for Machine Learning models" optional = false python-versions = "*" files = [ - {file = "onnxruntime_gpu-1.16.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c14bc735ad2b2286be9eadeea09bc190df38e8bce17e37b601761019cc7cc24f"}, - {file = "onnxruntime_gpu-1.16.3-cp310-cp310-win_amd64.whl", hash = "sha256:8de5ccfc005ea5ec50fbd104b7210c97623a9f8c13de6e64ce559b55956b757f"}, - {file = "onnxruntime_gpu-1.16.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5703454521a9c080ff3ac79b5d266e959cc735d442a1d8796763c7f92d6069dc"}, - {file = "onnxruntime_gpu-1.16.3-cp311-cp311-win_amd64.whl", hash = "sha256:48bb615aed61f5620d1ad46b9005614e1a14de60f8218a1448cc9a643f23d399"}, - {file = "onnxruntime_gpu-1.16.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2811c8ea209aaedcc2600ca828025279c1b1242344af603122d28c2ea8ab26a4"}, - {file = "onnxruntime_gpu-1.16.3-cp38-cp38-win_amd64.whl", hash = "sha256:2e5a92770c9232776739f378804bf6fea20bae02878a50b7fe0f81e77a47ee92"}, - {file = "onnxruntime_gpu-1.16.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9305c7fc5981d7e04ad2afef1a403475fb84d658898567c91aa5a41c20ead356"}, - {file = "onnxruntime_gpu-1.16.3-cp39-cp39-win_amd64.whl", hash = "sha256:d3ad8e7fbb22493267c23d61e997a6b2ac6236a08aa6b58a3a91848124c9b037"}, + {file = "onnxruntime_gpu-1.17.1-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a55fe84ee11a59ea069c6a790ee960f1c7da0d7d6c74822b2a8b357027c93646"}, + {file = "onnxruntime_gpu-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:a9abefceb32879cbee9f57977d6bb8d58cbac501f8a64bf96bca2f4fdff157fe"}, + {file = "onnxruntime_gpu-1.17.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:b2cd54f2b0a05e6bc9ab30182b859364d30115a19c31be24aa2edef40be00277"}, + {file = "onnxruntime_gpu-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:bdffcced8a5f6275c0df202220e9232138b336f868cd671c9d2c571e834d2a80"}, + {file = "onnxruntime_gpu-1.17.1-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:a1c871e8d0ae4121ea6528fc9410a5a7cbc5e43714b30521d5514fd10b987c83"}, + {file = "onnxruntime_gpu-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:9a0a94eda080e9f4a8e5035fdf0b3c24f5533e7861d88833a94493e63fca0812"}, + {file = "onnxruntime_gpu-1.17.1-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:624fdb65a632833f13de36854855818680be4f77942d8114524491d58f60d3ab"}, + {file = "onnxruntime_gpu-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:29fa78d232bbb5a5be3a3e0a022148a7b3df2ca66b4c21a11eef56e6f22859e9"}, + {file = "onnxruntime_gpu-1.17.1-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b0f8c70f2f9aeae825f3a397cc0c5f45124f9ae7c173263cf13c495982b0b99a"}, + {file = "onnxruntime_gpu-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:b1a27a104334461b690e4fc62775e1e71c68936399874932225d7fea21a0c261"}, ] [package.dependencies] @@ -2825,40 +2844,40 @@ test = ["pylint (>=3.0.0,<3.1.0)", "pytest", "pytest-pylint"] [[package]] name = "pandas" -version = "2.2.0" +version = "2.2.1" description = "Powerful data structures for data analysis, time series, and statistics" optional = false python-versions = ">=3.9" files = [ - {file = "pandas-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670"}, - {file = "pandas-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a"}, - {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18"}, - {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e"}, - {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5"}, - {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1"}, - {file = "pandas-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430"}, - {file = "pandas-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5"}, - {file = "pandas-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b"}, - {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f"}, - {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88"}, - {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3"}, - {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71"}, - {file = "pandas-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9"}, - {file = "pandas-2.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc"}, - {file = "pandas-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1"}, - {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440"}, - {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106"}, - {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e"}, - {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042"}, - {file = "pandas-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30"}, - {file = "pandas-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d"}, - {file = "pandas-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab"}, - {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a"}, - {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a"}, - {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd"}, - {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7"}, - {file = "pandas-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae"}, - {file = "pandas-2.2.0.tar.gz", hash = "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2"}, + {file = "pandas-2.2.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8df8612be9cd1c7797c93e1c5df861b2ddda0b48b08f2c3eaa0702cf88fb5f88"}, + {file = "pandas-2.2.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0f573ab277252ed9aaf38240f3b54cfc90fff8e5cab70411ee1d03f5d51f3944"}, + {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f02a3a6c83df4026e55b63c1f06476c9aa3ed6af3d89b4f04ea656ccdaaaa359"}, + {file = "pandas-2.2.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c38ce92cb22a4bea4e3929429aa1067a454dcc9c335799af93ba9be21b6beb51"}, + {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:c2ce852e1cf2509a69e98358e8458775f89599566ac3775e70419b98615f4b06"}, + {file = "pandas-2.2.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:53680dc9b2519cbf609c62db3ed7c0b499077c7fefda564e330286e619ff0dd9"}, + {file = "pandas-2.2.1-cp310-cp310-win_amd64.whl", hash = "sha256:94e714a1cca63e4f5939cdce5f29ba8d415d85166be3441165edd427dc9f6bc0"}, + {file = "pandas-2.2.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:f821213d48f4ab353d20ebc24e4faf94ba40d76680642fb7ce2ea31a3ad94f9b"}, + {file = "pandas-2.2.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c70e00c2d894cb230e5c15e4b1e1e6b2b478e09cf27cc593a11ef955b9ecc81a"}, + {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e97fbb5387c69209f134893abc788a6486dbf2f9e511070ca05eed4b930b1b02"}, + {file = "pandas-2.2.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:101d0eb9c5361aa0146f500773395a03839a5e6ecde4d4b6ced88b7e5a1a6403"}, + {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:7d2ed41c319c9fb4fd454fe25372028dfa417aacb9790f68171b2e3f06eae8cd"}, + {file = "pandas-2.2.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:af5d3c00557d657c8773ef9ee702c61dd13b9d7426794c9dfeb1dc4a0bf0ebc7"}, + {file = "pandas-2.2.1-cp311-cp311-win_amd64.whl", hash = "sha256:06cf591dbaefb6da9de8472535b185cba556d0ce2e6ed28e21d919704fef1a9e"}, + {file = "pandas-2.2.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:88ecb5c01bb9ca927ebc4098136038519aa5d66b44671861ffab754cae75102c"}, + {file = "pandas-2.2.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:04f6ec3baec203c13e3f8b139fb0f9f86cd8c0b94603ae3ae8ce9a422e9f5bee"}, + {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a935a90a76c44fe170d01e90a3594beef9e9a6220021acfb26053d01426f7dc2"}, + {file = "pandas-2.2.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c391f594aae2fd9f679d419e9a4d5ba4bce5bb13f6a989195656e7dc4b95c8f0"}, + {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9d1265545f579edf3f8f0cb6f89f234f5e44ba725a34d86535b1a1d38decbccc"}, + {file = "pandas-2.2.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:11940e9e3056576ac3244baef2fedade891977bcc1cb7e5cc8f8cc7d603edc89"}, + {file = "pandas-2.2.1-cp312-cp312-win_amd64.whl", hash = "sha256:4acf681325ee1c7f950d058b05a820441075b0dd9a2adf5c4835b9bc056bf4fb"}, + {file = "pandas-2.2.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9bd8a40f47080825af4317d0340c656744f2bfdb6819f818e6ba3cd24c0e1397"}, + {file = "pandas-2.2.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:df0c37ebd19e11d089ceba66eba59a168242fc6b7155cba4ffffa6eccdfb8f16"}, + {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739cc70eaf17d57608639e74d63387b0d8594ce02f69e7a0b046f117974b3019"}, + {file = "pandas-2.2.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9d3558d263073ed95e46f4650becff0c5e1ffe0fc3a015de3c79283dfbdb3df"}, + {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4aa1d8707812a658debf03824016bf5ea0d516afdea29b7dc14cf687bc4d4ec6"}, + {file = "pandas-2.2.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:76f27a809cda87e07f192f001d11adc2b930e93a2b0c4a236fde5429527423be"}, + {file = "pandas-2.2.1-cp39-cp39-win_amd64.whl", hash = "sha256:1ba21b1d5c0e43416218db63037dbe1a01fc101dc6e6024bcad08123e48004ab"}, + {file = "pandas-2.2.1.tar.gz", hash = "sha256:0ab90f87093c13f3e8fa45b48ba9f39181046e8f3317d3aadb2fffbb1b978572"}, ] [package.dependencies] @@ -2886,6 +2905,7 @@ parquet = ["pyarrow (>=10.0.1)"] performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] plot = ["matplotlib (>=3.6.3)"] postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] +pyarrow = ["pyarrow (>=10.0.1)"] spss = ["pyreadstat (>=1.2.0)"] sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] @@ -2992,18 +3012,18 @@ xmp = ["defusedxml"] [[package]] name = "platformdirs" -version = "4.1.0" +version = "4.2.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.1.0-py3-none-any.whl", hash = "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380"}, - {file = "platformdirs-4.1.0.tar.gz", hash = "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420"}, + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, ] [package.extras] -docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] [[package]] name = "pluggy" @@ -3022,17 +3042,17 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "polyline" -version = "2.0.1" +version = "2.0.2" description = "A Python implementation of Google's Encoded Polyline Algorithm Format." optional = false python-versions = ">=3.7" files = [ - {file = "polyline-2.0.1-py3-none-any.whl", hash = "sha256:7b1ff647be393143c1b9268738d9efb98a327dd0b29f9c3b84552dff0e34ea3c"}, - {file = "polyline-2.0.1.tar.gz", hash = "sha256:74cb5cea098dddf09d1a5a1f17af9184d371cbf3e9723de0194e530ec39ca1f6"}, + {file = "polyline-2.0.2-py3-none-any.whl", hash = "sha256:389655c893bdabf2863c6aaa49490cf83dcdcec86ae715f67044ee98be57bef5"}, + {file = "polyline-2.0.2.tar.gz", hash = "sha256:10541e759c5fd51f746ee304e9af94744089a4055b6257b293b3afd1df64e369"}, ] [package.extras] -dev = ["pylint (>=2.15.10,<2.16.0)", "pytest (>=7.0,<8.0)", "pytest-cov (>=4.0,<5.0)", "sphinx (>=4.2.0,<4.3.0)", "sphinx-rtd-theme (>=1.0.0,<1.1.0)", "toml (>=0.10.2,<0.11.0)"] +dev = ["pylint (>=3.0.3,<3.1.0)", "pytest (>=7.0,<8.0)", "pytest-cov (>=4.0,<5.0)", "sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.0,<1.3.0)", "toml (>=0.10.2,<0.11.0)"] publish = ["build (>=0.8,<1.0)", "twine (>=4.0,<5.0)"] [[package]] @@ -3066,13 +3086,13 @@ files = [ [[package]] name = "pre-commit" -version = "3.6.0" +version = "3.6.2" description = "A framework for managing and maintaining multi-language pre-commit hooks." optional = false python-versions = ">=3.9" files = [ - {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"}, - {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"}, + {file = "pre_commit-3.6.2-py2.py3-none-any.whl", hash = "sha256:ba637c2d7a670c10daedc059f5c49b5bd0aadbccfcd7ec15592cf9665117532c"}, + {file = "pre_commit-3.6.2.tar.gz", hash = "sha256:c3ef34f463045c88658c5b99f38c1e297abdcc0ff13f98d3370055fbbfabc67e"}, ] [package.dependencies] @@ -3094,22 +3114,22 @@ files = [ [[package]] name = "protobuf" -version = "4.25.2" +version = "4.25.3" description = "" optional = false python-versions = ">=3.8" files = [ - {file = "protobuf-4.25.2-cp310-abi3-win32.whl", hash = "sha256:b50c949608682b12efb0b2717f53256f03636af5f60ac0c1d900df6213910fd6"}, - {file = "protobuf-4.25.2-cp310-abi3-win_amd64.whl", hash = "sha256:8f62574857ee1de9f770baf04dde4165e30b15ad97ba03ceac65f760ff018ac9"}, - {file = "protobuf-4.25.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:2db9f8fa64fbdcdc93767d3cf81e0f2aef176284071507e3ede160811502fd3d"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:10894a2885b7175d3984f2be8d9850712c57d5e7587a2410720af8be56cdaf62"}, - {file = "protobuf-4.25.2-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:fc381d1dd0516343f1440019cedf08a7405f791cd49eef4ae1ea06520bc1c020"}, - {file = "protobuf-4.25.2-cp38-cp38-win32.whl", hash = "sha256:33a1aeef4b1927431d1be780e87b641e322b88d654203a9e9d93f218ee359e61"}, - {file = "protobuf-4.25.2-cp38-cp38-win_amd64.whl", hash = "sha256:47f3de503fe7c1245f6f03bea7e8d3ec11c6c4a2ea9ef910e3221c8a15516d62"}, - {file = "protobuf-4.25.2-cp39-cp39-win32.whl", hash = "sha256:5e5c933b4c30a988b52e0b7c02641760a5ba046edc5e43d3b94a74c9fc57c1b3"}, - {file = "protobuf-4.25.2-cp39-cp39-win_amd64.whl", hash = "sha256:d66a769b8d687df9024f2985d5137a337f957a0916cf5464d1513eee96a63ff0"}, - {file = "protobuf-4.25.2-py3-none-any.whl", hash = "sha256:a8b7a98d4ce823303145bf3c1a8bdb0f2f4642a414b196f04ad9853ed0c8f830"}, - {file = "protobuf-4.25.2.tar.gz", hash = "sha256:fe599e175cb347efc8ee524bcd4b902d11f7262c0e569ececcb89995c15f0a5e"}, + {file = "protobuf-4.25.3-cp310-abi3-win32.whl", hash = "sha256:d4198877797a83cbfe9bffa3803602bbe1625dc30d8a097365dbc762e5790faa"}, + {file = "protobuf-4.25.3-cp310-abi3-win_amd64.whl", hash = "sha256:209ba4cc916bab46f64e56b85b090607a676f66b473e6b762e6f1d9d591eb2e8"}, + {file = "protobuf-4.25.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:f1279ab38ecbfae7e456a108c5c0681e4956d5b1090027c1de0f934dfdb4b35c"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:e7cb0ae90dd83727f0c0718634ed56837bfeeee29a5f82a7514c03ee1364c019"}, + {file = "protobuf-4.25.3-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:7c8daa26095f82482307bc717364e7c13f4f1c99659be82890dcfc215194554d"}, + {file = "protobuf-4.25.3-cp38-cp38-win32.whl", hash = "sha256:f4f118245c4a087776e0a8408be33cf09f6c547442c00395fbfb116fac2f8ac2"}, + {file = "protobuf-4.25.3-cp38-cp38-win_amd64.whl", hash = "sha256:c053062984e61144385022e53678fbded7aea14ebb3e0305ae3592fb219ccfa4"}, + {file = "protobuf-4.25.3-cp39-cp39-win32.whl", hash = "sha256:19b270aeaa0099f16d3ca02628546b8baefe2955bbe23224aaf856134eccf1e4"}, + {file = "protobuf-4.25.3-cp39-cp39-win_amd64.whl", hash = "sha256:e3c97a1555fd6388f857770ff8b9703083de6bf1f9274a002a332d65fbb56c8c"}, + {file = "protobuf-4.25.3-py3-none-any.whl", hash = "sha256:f0700d54bcf45424477e46a9f0944155b46fb0639d69728739c0e47bab83f2b9"}, + {file = "protobuf-4.25.3.tar.gz", hash = "sha256:25b5d0b42fd000320bd7830b349e3b696435f3b329810427a6bcce6a5492cc5c"}, ] [[package]] @@ -6449,13 +6469,13 @@ cp2110 = ["hidapi"] [[package]] name = "pytest" -version = "8.0.0" +version = "8.0.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.8" files = [ - {file = "pytest-8.0.0-py3-none-any.whl", hash = "sha256:50fb9cbe836c3f20f0dfa99c565201fb75dc54c8d76373cd1bde06b06657bdb6"}, - {file = "pytest-8.0.0.tar.gz", hash = "sha256:249b1b0864530ba251b7438274c4d251c58d868edaaec8762893ad4a0d71c36c"}, + {file = "pytest-8.0.2-py3-none-any.whl", hash = "sha256:edfaaef32ce5172d5466b5127b42e0d6d35ebbe4453f0e3505d96afd93f6b096"}, + {file = "pytest-8.0.2.tar.gz", hash = "sha256:d4051d623a2e0b7e51960ba963193b09ce6daeb9759a451844a21e4ddedfc1bd"}, ] [package.dependencies] @@ -6620,23 +6640,23 @@ numpy = ["numpy (>=1.6.0)"] [[package]] name = "pytweening" -version = "1.0.7" -description = "A collection of tweening / easing functions." +version = "1.2.0" +description = "A collection of tweening (aka easing) functions." optional = false python-versions = "*" files = [ - {file = "pytweening-1.0.7.tar.gz", hash = "sha256:767134f1bf57b76c1ce9f692dd1cfc776d9a279de6724e8d04854508fd7ededb"}, + {file = "pytweening-1.2.0.tar.gz", hash = "sha256:243318b7736698066c5f362ec5c2b6434ecf4297c3c8e7caa8abfe6af4cac71b"}, ] [[package]] name = "pytz" -version = "2023.4" +version = "2024.1" description = "World timezone definitions, modern and historical" optional = false python-versions = "*" files = [ - {file = "pytz-2023.4-py2.py3-none-any.whl", hash = "sha256:f90ef520d95e7c46951105338d918664ebfd6f1d995bd7d153127ce90efafa6a"}, - {file = "pytz-2023.4.tar.gz", hash = "sha256:31d4583c4ed539cd037956140d695e42c033a19e984bfce9964a3f7d59bc2b40"}, + {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, + {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, ] [[package]] @@ -6905,28 +6925,28 @@ docs = ["furo (==2023.9.10)", "pyenchant (==3.2.2)", "sphinx (==7.1.2)", "sphinx [[package]] name = "ruff" -version = "0.1.14" +version = "0.2.2" description = "An extremely fast Python linter and code formatter, written in Rust." optional = false python-versions = ">=3.7" files = [ - {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:96f76536df9b26622755c12ed8680f159817be2f725c17ed9305b472a757cdbb"}, - {file = "ruff-0.1.14-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:ab3f71f64498c7241123bb5a768544cf42821d2a537f894b22457a543d3ca7a9"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7060156ecc572b8f984fd20fd8b0fcb692dd5d837b7606e968334ab7ff0090ab"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a53d8e35313d7b67eb3db15a66c08434809107659226a90dcd7acb2afa55faea"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bea9be712b8f5b4ebed40e1949379cfb2a7d907f42921cf9ab3aae07e6fba9eb"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:2270504d629a0b064247983cbc495bed277f372fb9eaba41e5cf51f7ba705a6a"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80258bb3b8909b1700610dfabef7876423eed1bc930fe177c71c414921898efa"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:653230dd00aaf449eb5ff25d10a6e03bc3006813e2cb99799e568f55482e5cae"}, - {file = "ruff-0.1.14-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87b3acc6c4e6928459ba9eb7459dd4f0c4bf266a053c863d72a44c33246bfdbf"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:6b3dadc9522d0eccc060699a9816e8127b27addbb4697fc0c08611e4e6aeb8b5"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:1c8eca1a47b4150dc0fbec7fe68fc91c695aed798532a18dbb1424e61e9b721f"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_i686.whl", hash = "sha256:62ce2ae46303ee896fc6811f63d6dabf8d9c389da0f3e3f2bce8bc7f15ef5488"}, - {file = "ruff-0.1.14-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:b2027dde79d217b211d725fc833e8965dc90a16d0d3213f1298f97465956661b"}, - {file = "ruff-0.1.14-py3-none-win32.whl", hash = "sha256:722bafc299145575a63bbd6b5069cb643eaa62546a5b6398f82b3e4403329cab"}, - {file = "ruff-0.1.14-py3-none-win_amd64.whl", hash = "sha256:e3d241aa61f92b0805a7082bd89a9990826448e4d0398f0e2bc8f05c75c63d99"}, - {file = "ruff-0.1.14-py3-none-win_arm64.whl", hash = "sha256:269302b31ade4cde6cf6f9dd58ea593773a37ed3f7b97e793c8594b262466b67"}, - {file = "ruff-0.1.14.tar.gz", hash = "sha256:ad3f8088b2dfd884820289a06ab718cde7d38b94972212cc4ba90d5fbc9955f3"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:0a9efb032855ffb3c21f6405751d5e147b0c6b631e3ca3f6b20f917572b97eb6"}, + {file = "ruff-0.2.2-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:d450b7fbff85913f866a5384d8912710936e2b96da74541c82c1b458472ddb39"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ecd46e3106850a5c26aee114e562c329f9a1fbe9e4821b008c4404f64ff9ce73"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5e22676a5b875bd72acd3d11d5fa9075d3a5f53b877fe7b4793e4673499318ba"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1695700d1e25a99d28f7a1636d85bafcc5030bba9d0578c0781ba1790dbcf51c"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:b0c232af3d0bd8f521806223723456ffebf8e323bd1e4e82b0befb20ba18388e"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f63d96494eeec2fc70d909393bcd76c69f35334cdbd9e20d089fb3f0640216ca"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6a61ea0ff048e06de273b2e45bd72629f470f5da8f71daf09fe481278b175001"}, + {file = "ruff-0.2.2-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e1439c8f407e4f356470e54cdecdca1bd5439a0673792dbe34a2b0a551a2fe3"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:940de32dc8853eba0f67f7198b3e79bc6ba95c2edbfdfac2144c8235114d6726"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:0c126da55c38dd917621552ab430213bdb3273bb10ddb67bc4b761989210eb6e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_i686.whl", hash = "sha256:3b65494f7e4bed2e74110dac1f0d17dc8e1f42faaa784e7c58a98e335ec83d7e"}, + {file = "ruff-0.2.2-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:1ec49be4fe6ddac0503833f3ed8930528e26d1e60ad35c2446da372d16651ce9"}, + {file = "ruff-0.2.2-py3-none-win32.whl", hash = "sha256:d920499b576f6c68295bc04e7b17b6544d9d05f196bb3aac4358792ef6f34325"}, + {file = "ruff-0.2.2-py3-none-win_amd64.whl", hash = "sha256:cc9a91ae137d687f43a44c900e5d95e9617cb37d4c989e462980ba27039d239d"}, + {file = "ruff-0.2.2-py3-none-win_arm64.whl", hash = "sha256:c9d15fc41e6054bfc7200478720570078f0b41c9ae4f010bcc16bd6f4d1aacdd"}, + {file = "ruff-0.2.2.tar.gz", hash = "sha256:e62ed7f36b3068a30ba39193a14274cd706bc486fad521276458022f7bccb31d"}, ] [[package]] @@ -7008,13 +7028,13 @@ stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] [[package]] name = "sentry-sdk" -version = "1.39.2" +version = "1.40.5" description = "Python client for Sentry (https://sentry.io)" optional = false python-versions = "*" files = [ - {file = "sentry-sdk-1.39.2.tar.gz", hash = "sha256:24c83b0b41c887d33328a9166f5950dc37ad58f01c9f2fbff6b87a6f1094170c"}, - {file = "sentry_sdk-1.39.2-py2.py3-none-any.whl", hash = "sha256:acaf597b30258fc7663063b291aa99e58f3096e91fe1e6634f4b79f9c1943e8e"}, + {file = "sentry-sdk-1.40.5.tar.gz", hash = "sha256:d2dca2392cc5c9a2cc9bb874dd7978ebb759682fe4fe889ee7e970ee8dd1c61e"}, + {file = "sentry_sdk-1.40.5-py2.py3-none-any.whl", hash = "sha256:d188b407c9bacbe2a50a824e1f8fb99ee1aeb309133310488c570cb6d7056643"}, ] [package.dependencies] @@ -7040,7 +7060,7 @@ huey = ["huey (>=2)"] loguru = ["loguru (>=0.5)"] opentelemetry = ["opentelemetry-distro (>=0.35b0)"] opentelemetry-experimental = ["opentelemetry-distro (>=0.40b0,<1.0)", "opentelemetry-instrumentation-aiohttp-client (>=0.40b0,<1.0)", "opentelemetry-instrumentation-django (>=0.40b0,<1.0)", "opentelemetry-instrumentation-fastapi (>=0.40b0,<1.0)", "opentelemetry-instrumentation-flask (>=0.40b0,<1.0)", "opentelemetry-instrumentation-requests (>=0.40b0,<1.0)", "opentelemetry-instrumentation-sqlite3 (>=0.40b0,<1.0)", "opentelemetry-instrumentation-urllib (>=0.40b0,<1.0)"] -pure-eval = ["asttokens", "executing", "pure_eval"] +pure-eval = ["asttokens", "executing", "pure-eval"] pymongo = ["pymongo (>=3.1)"] pyspark = ["pyspark (>=2.4.4)"] quart = ["blinker (>=1.1)", "quart (>=0.16.1)"] @@ -7153,72 +7173,72 @@ test = ["pytest"] [[package]] name = "setuptools" -version = "69.0.3" +version = "69.1.1" description = "Easily download, build, install, upgrade, and uninstall Python packages" optional = false python-versions = ">=3.8" files = [ - {file = "setuptools-69.0.3-py3-none-any.whl", hash = "sha256:385eb4edd9c9d5c17540511303e39a147ce2fc04bc55289c322b9e5904fe2c05"}, - {file = "setuptools-69.0.3.tar.gz", hash = "sha256:be1af57fc409f93647f2e8e4573a142ed38724b8cdd389706a867bb4efcf1e78"}, + {file = "setuptools-69.1.1-py3-none-any.whl", hash = "sha256:02fa291a0471b3a18b2b2481ed902af520c69e8ae0919c13da936542754b4c56"}, + {file = "setuptools-69.1.1.tar.gz", hash = "sha256:5c0806c7d9af348e6dd3777b4f4dbb42c7ad85b190104837488eab9a7c945cf8"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] -testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "shapely" -version = "2.0.2" +version = "2.0.3" description = "Manipulation and analysis of geometric objects" optional = false python-versions = ">=3.7" files = [ - {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:6ca8cffbe84ddde8f52b297b53f8e0687bd31141abb2c373fd8a9f032df415d6"}, - {file = "shapely-2.0.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:baa14fc27771e180c06b499a0a7ba697c7988c7b2b6cba9a929a19a4d2762de3"}, - {file = "shapely-2.0.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:36480e32c434d168cdf2f5e9862c84aaf4d714a43a8465ae3ce8ff327f0affb7"}, - {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4ef753200cbffd4f652efb2c528c5474e5a14341a473994d90ad0606522a46a2"}, - {file = "shapely-2.0.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a9a41ff4323fc9d6257759c26eb1cf3a61ebc7e611e024e6091f42977303fd3a"}, - {file = "shapely-2.0.2-cp310-cp310-win32.whl", hash = "sha256:72b5997272ae8c25f0fd5b3b967b3237e87fab7978b8d6cd5fa748770f0c5d68"}, - {file = "shapely-2.0.2-cp310-cp310-win_amd64.whl", hash = "sha256:34eac2337cbd67650248761b140d2535855d21b969d76d76123317882d3a0c1a"}, - {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:5b0c052709c8a257c93b0d4943b0b7a3035f87e2d6a8ac9407b6a992d206422f"}, - {file = "shapely-2.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2d217e56ae067e87b4e1731d0dc62eebe887ced729ba5c2d4590e9e3e9fdbd88"}, - {file = "shapely-2.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:94ac128ae2ab4edd0bffcd4e566411ea7bdc738aeaf92c32a8a836abad725f9f"}, - {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa3ee28f5e63a130ec5af4dc3c4cb9c21c5788bb13c15e89190d163b14f9fb89"}, - {file = "shapely-2.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:737dba15011e5a9b54a8302f1748b62daa207c9bc06f820cd0ad32a041f1c6f2"}, - {file = "shapely-2.0.2-cp311-cp311-win32.whl", hash = "sha256:45ac6906cff0765455a7b49c1670af6e230c419507c13e2f75db638c8fc6f3bd"}, - {file = "shapely-2.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:dc9342fc82e374130db86a955c3c4525bfbf315a248af8277a913f30911bed9e"}, - {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:06f193091a7c6112fc08dfd195a1e3846a64306f890b151fa8c63b3e3624202c"}, - {file = "shapely-2.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:eebe544df5c018134f3c23b6515877f7e4cd72851f88a8d0c18464f414d141a2"}, - {file = "shapely-2.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7e92e7c255f89f5cdf777690313311f422aa8ada9a3205b187113274e0135cd8"}, - {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:be46d5509b9251dd9087768eaf35a71360de6afac82ce87c636990a0871aa18b"}, - {file = "shapely-2.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5533a925d8e211d07636ffc2fdd9a7f9f13d54686d00577eeb11d16f00be9c4"}, - {file = "shapely-2.0.2-cp312-cp312-win32.whl", hash = "sha256:084b023dae8ad3d5b98acee9d3bf098fdf688eb0bb9b1401e8b075f6a627b611"}, - {file = "shapely-2.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:ea84d1cdbcf31e619d672b53c4532f06253894185ee7acb8ceb78f5f33cbe033"}, - {file = "shapely-2.0.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:ed1e99702125e7baccf401830a3b94d810d5c70b329b765fe93451fe14cf565b"}, - {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7d897e6bdc6bc64f7f65155dbbb30e49acaabbd0d9266b9b4041f87d6e52b3a"}, - {file = "shapely-2.0.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0521d76d1e8af01e712db71da9096b484f081e539d4f4a8c97342e7971d5e1b4"}, - {file = "shapely-2.0.2-cp37-cp37m-win32.whl", hash = "sha256:5324be299d4c533ecfcfd43424dfd12f9428fd6f12cda38a4316da001d6ef0ea"}, - {file = "shapely-2.0.2-cp37-cp37m-win_amd64.whl", hash = "sha256:78128357a0cee573257a0c2c388d4b7bf13cb7dbe5b3fe5d26d45ebbe2a39e25"}, - {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:87dc2be34ac3a3a4a319b963c507ac06682978a5e6c93d71917618b14f13066e"}, - {file = "shapely-2.0.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:42997ac806e4583dad51c80a32d38570fd9a3d4778f5e2c98f9090aa7db0fe91"}, - {file = "shapely-2.0.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:ccfd5fa10a37e67dbafc601c1ddbcbbfef70d34c3f6b0efc866ddbdb55893a6c"}, - {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7c95d3379ae3abb74058938a9fcbc478c6b2e28d20dace38f8b5c587dde90aa"}, - {file = "shapely-2.0.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a21353d28209fb0d8cc083e08ca53c52666e0d8a1f9bbe23b6063967d89ed24"}, - {file = "shapely-2.0.2-cp38-cp38-win32.whl", hash = "sha256:03e63a99dfe6bd3beb8d5f41ec2086585bb969991d603f9aeac335ad396a06d4"}, - {file = "shapely-2.0.2-cp38-cp38-win_amd64.whl", hash = "sha256:c6fd29fbd9cd76350bd5cc14c49de394a31770aed02d74203e23b928f3d2f1aa"}, - {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:1f217d28ecb48e593beae20a0082a95bd9898d82d14b8fcb497edf6bff9a44d7"}, - {file = "shapely-2.0.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:394e5085b49334fd5b94fa89c086edfb39c3ecab7f669e8b2a4298b9d523b3a5"}, - {file = "shapely-2.0.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fd3ad17b64466a033848c26cb5b509625c87d07dcf39a1541461cacdb8f7e91c"}, - {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d41a116fcad58048d7143ddb01285e1a8780df6dc1f56c3b1e1b7f12ed296651"}, - {file = "shapely-2.0.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dea9a0651333cf96ef5bb2035044e3ad6a54f87d90e50fe4c2636debf1b77abc"}, - {file = "shapely-2.0.2-cp39-cp39-win32.whl", hash = "sha256:b8eb0a92f7b8c74f9d8fdd1b40d395113f59bd8132ca1348ebcc1f5aece94b96"}, - {file = "shapely-2.0.2-cp39-cp39-win_amd64.whl", hash = "sha256:794affd80ca0f2c536fc948a3afa90bd8fb61ebe37fe873483ae818e7f21def4"}, - {file = "shapely-2.0.2.tar.gz", hash = "sha256:1713cc04c171baffc5b259ba8531c58acc2a301707b7f021d88a15ed090649e7"}, -] - -[package.dependencies] -numpy = ">=1.14" + {file = "shapely-2.0.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:af7e9abe180b189431b0f490638281b43b84a33a960620e6b2e8d3e3458b61a1"}, + {file = "shapely-2.0.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:98040462b36ced9671e266b95c326b97f41290d9d17504a1ee4dc313a7667b9c"}, + {file = "shapely-2.0.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:71eb736ef2843f23473c6e37f6180f90f0a35d740ab284321548edf4e55d9a52"}, + {file = "shapely-2.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:881eb9dbbb4a6419667e91fcb20313bfc1e67f53dbb392c6840ff04793571ed1"}, + {file = "shapely-2.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f10d2ccf0554fc0e39fad5886c839e47e207f99fdf09547bc687a2330efda35b"}, + {file = "shapely-2.0.3-cp310-cp310-win32.whl", hash = "sha256:6dfdc077a6fcaf74d3eab23a1ace5abc50c8bce56ac7747d25eab582c5a2990e"}, + {file = "shapely-2.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:64c5013dacd2d81b3bb12672098a0b2795c1bf8190cfc2980e380f5ef9d9e4d9"}, + {file = "shapely-2.0.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:56cee3e4e8159d6f2ce32e421445b8e23154fd02a0ac271d6a6c0b266a8e3cce"}, + {file = "shapely-2.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:619232c8276fded09527d2a9fd91a7885ff95c0ff9ecd5e3cb1e34fbb676e2ae"}, + {file = "shapely-2.0.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b2a7d256db6f5b4b407dc0c98dd1b2fcf1c9c5814af9416e5498d0a2e4307a4b"}, + {file = "shapely-2.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e45f0c8cd4583647db3216d965d49363e6548c300c23fd7e57ce17a03f824034"}, + {file = "shapely-2.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13cb37d3826972a82748a450328fe02a931dcaed10e69a4d83cc20ba021bc85f"}, + {file = "shapely-2.0.3-cp311-cp311-win32.whl", hash = "sha256:9302d7011e3e376d25acd30d2d9e70d315d93f03cc748784af19b00988fc30b1"}, + {file = "shapely-2.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:6b464f2666b13902835f201f50e835f2f153f37741db88f68c7f3b932d3505fa"}, + {file = "shapely-2.0.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:e86e7cb8e331a4850e0c2a8b2d66dc08d7a7b301b8d1d34a13060e3a5b4b3b55"}, + {file = "shapely-2.0.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c91981c99ade980fc49e41a544629751a0ccd769f39794ae913e53b07b2f78b9"}, + {file = "shapely-2.0.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:bd45d456983dc60a42c4db437496d3f08a4201fbf662b69779f535eb969660af"}, + {file = "shapely-2.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:882fb1ffc7577e88c1194f4f1757e277dc484ba096a3b94844319873d14b0f2d"}, + {file = "shapely-2.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b9f2d93bff2ea52fa93245798cddb479766a18510ea9b93a4fb9755c79474889"}, + {file = "shapely-2.0.3-cp312-cp312-win32.whl", hash = "sha256:99abad1fd1303b35d991703432c9481e3242b7b3a393c186cfb02373bf604004"}, + {file = "shapely-2.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:6f555fe3304a1f40398977789bc4fe3c28a11173196df9ece1e15c5bc75a48db"}, + {file = "shapely-2.0.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:a983cc418c1fa160b7d797cfef0e0c9f8c6d5871e83eae2c5793fce6a837fad9"}, + {file = "shapely-2.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18bddb8c327f392189a8d5d6b9a858945722d0bb95ccbd6a077b8e8fc4c7890d"}, + {file = "shapely-2.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:442f4dcf1eb58c5a4e3428d88e988ae153f97ab69a9f24e07bf4af8038536325"}, + {file = "shapely-2.0.3-cp37-cp37m-win32.whl", hash = "sha256:31a40b6e3ab00a4fd3a1d44efb2482278642572b8e0451abdc8e0634b787173e"}, + {file = "shapely-2.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:59b16976c2473fec85ce65cc9239bef97d4205ab3acead4e6cdcc72aee535679"}, + {file = "shapely-2.0.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:705efbce1950a31a55b1daa9c6ae1c34f1296de71ca8427974ec2f27d57554e3"}, + {file = "shapely-2.0.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:601c5c0058a6192df704cb889439f64994708563f57f99574798721e9777a44b"}, + {file = "shapely-2.0.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f24ecbb90a45c962b3b60d8d9a387272ed50dc010bfe605f1d16dfc94772d8a1"}, + {file = "shapely-2.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8c2a2989222c6062f7a0656e16276c01bb308bc7e5d999e54bf4e294ce62e76"}, + {file = "shapely-2.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42bceb9bceb3710a774ce04908fda0f28b291323da2688f928b3f213373b5aee"}, + {file = "shapely-2.0.3-cp38-cp38-win32.whl", hash = "sha256:54d925c9a311e4d109ec25f6a54a8bd92cc03481a34ae1a6a92c1fe6729b7e01"}, + {file = "shapely-2.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:300d203b480a4589adefff4c4af0b13919cd6d760ba3cbb1e56275210f96f654"}, + {file = "shapely-2.0.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:083d026e97b6c1f4a9bd2a9171c7692461092ed5375218170d91705550eecfd5"}, + {file = "shapely-2.0.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:27b6e1910094d93e9627f2664121e0e35613262fc037051680a08270f6058daf"}, + {file = "shapely-2.0.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:71b2de56a9e8c0e5920ae5ddb23b923490557ac50cb0b7fa752761bf4851acde"}, + {file = "shapely-2.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d279e56bbb68d218d63f3efc80c819cedcceef0e64efbf058a1df89dc57201b"}, + {file = "shapely-2.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88566d01a30f0453f7d038db46bc83ce125e38e47c5f6bfd4c9c287010e9bf74"}, + {file = "shapely-2.0.3-cp39-cp39-win32.whl", hash = "sha256:58afbba12c42c6ed44c4270bc0e22f3dadff5656d711b0ad335c315e02d04707"}, + {file = "shapely-2.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:5026b30433a70911979d390009261b8c4021ff87c7c3cbd825e62bb2ffa181bc"}, + {file = "shapely-2.0.3.tar.gz", hash = "sha256:4d65d0aa7910af71efa72fd6447e02a8e5dd44da81a983de9d736d6e6ccbe674"}, +] + +[package.dependencies] +numpy = ">=1.14,<2" [package.extras] docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] @@ -7523,13 +7543,13 @@ doc = ["reno", "sphinx", "tornado (>=4.5)"] [[package]] name = "timezonefinder" -version = "6.2.0" -description = "fast python package for finding the timezone of any point on earth (coordinates) offline" +version = "6.4.0" +description = "python package for finding the timezone of any point on earth (coordinates) offline" optional = false -python-versions = ">=3.8,<4" +python-versions = ">=3.9,<4" files = [ - {file = "timezonefinder-6.2.0-cp38-cp38-manylinux_2_35_x86_64.whl", hash = "sha256:06aa5926ed31687ea9eb00ab53203631f09a78f307285b4929da4ac4e2889240"}, - {file = "timezonefinder-6.2.0.tar.gz", hash = "sha256:d41fd2650bb4221fae5a61f9c2767158f9727c4aaca95e24da86394feb704220"}, + {file = "timezonefinder-6.4.0-cp310-cp310-manylinux_2_35_x86_64.whl", hash = "sha256:b4d4cefcdfcd46ffc04858d12cb23b58faca06dad05f68b6b704421be9fc70bc"}, + {file = "timezonefinder-6.4.0.tar.gz", hash = "sha256:7d82ebbc822d5fa012dbfbde510999afce0d7d1482794838e6c4daf6cc327f97"}, ] [package.dependencies] @@ -7539,7 +7559,7 @@ numpy = ">=1.18,<2" setuptools = ">=65.5" [package.extras] -numba = ["numba (>=0.56,<1)"] +numba = ["numba (>=0.59,<1)"] pytz = ["pytz (>=2022.7.1)"] [[package]] @@ -7555,13 +7575,13 @@ files = [ [[package]] name = "tqdm" -version = "4.66.1" +version = "4.66.2" description = "Fast, Extensible Progress Meter" optional = false python-versions = ">=3.7" files = [ - {file = "tqdm-4.66.1-py3-none-any.whl", hash = "sha256:d302b3c5b53d47bce91fea46679d9c3c6508cf6332229aa1e7d8653723793386"}, - {file = "tqdm-4.66.1.tar.gz", hash = "sha256:d88e651f9db8d8551a62556d3cff9e3034274ca5d66e93197cf2490e2dcb69c7"}, + {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, + {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, ] [package.dependencies] @@ -7575,13 +7595,13 @@ telegram = ["requests"] [[package]] name = "types-requests" -version = "2.31.0.20240125" +version = "2.31.0.20240218" description = "Typing stubs for requests" optional = false python-versions = ">=3.8" files = [ - {file = "types-requests-2.31.0.20240125.tar.gz", hash = "sha256:03a28ce1d7cd54199148e043b2079cdded22d6795d19a2c2a6791a4b2b5e2eb5"}, - {file = "types_requests-2.31.0.20240125-py3-none-any.whl", hash = "sha256:9592a9a4cb92d6d75d9b491a41477272b710e021011a2a3061157e2fb1f1a5d1"}, + {file = "types-requests-2.31.0.20240218.tar.gz", hash = "sha256:f1721dba8385958f504a5386240b92de4734e047a08a40751c1654d1ac3349c5"}, + {file = "types_requests-2.31.0.20240218-py3-none-any.whl", hash = "sha256:a82807ec6ddce8f00fe0e949da6d6bc1fbf1715420218a9640d695f70a9e5a9b"}, ] [package.dependencies] @@ -7600,51 +7620,52 @@ files = [ [[package]] name = "typing-extensions" -version = "4.9.0" +version = "4.10.0" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, - {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, + {file = "typing_extensions-4.10.0-py3-none-any.whl", hash = "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475"}, + {file = "typing_extensions-4.10.0.tar.gz", hash = "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb"}, ] [[package]] name = "tzdata" -version = "2023.4" +version = "2024.1" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2023.4-py2.py3-none-any.whl", hash = "sha256:aa3ace4329eeacda5b7beb7ea08ece826c28d761cda36e747cfbf97996d39bf3"}, - {file = "tzdata-2023.4.tar.gz", hash = "sha256:dd54c94f294765522c77399649b4fefd95522479a664a0cec87f41bebc6148c9"}, + {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, + {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, ] [[package]] name = "urllib3" -version = "2.1.0" +version = "2.2.1" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.1.0-py3-none-any.whl", hash = "sha256:55901e917a5896a349ff771be919f8bd99aff50b79fe58fec595eb37bbc56bb3"}, - {file = "urllib3-2.1.0.tar.gz", hash = "sha256:df7aa8afb0148fa78488e7899b2c59b5f4ffcfa82e6c54ccb9dd37c1d7b52d54"}, + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] zstd = ["zstandard (>=0.18.0)"] [[package]] name = "virtualenv" -version = "20.25.0" +version = "20.25.1" description = "Virtual Python Environment builder" optional = false python-versions = ">=3.7" files = [ - {file = "virtualenv-20.25.0-py3-none-any.whl", hash = "sha256:4238949c5ffe6876362d9c0180fc6c3a824a7b12b80604eeb8085f2ed7460de3"}, - {file = "virtualenv-20.25.0.tar.gz", hash = "sha256:bf51c0d9c7dd63ea8e44086fa1e4fb1093a31e963b86959257378aef020e1f1b"}, + {file = "virtualenv-20.25.1-py3-none-any.whl", hash = "sha256:961c026ac520bac5f69acb8ea063e8a4f071bcc9457b9c1f28f6b085c511583a"}, + {file = "virtualenv-20.25.1.tar.gz", hash = "sha256:e08e13ecdca7a0bd53798f356d5831434afa5b07b93f0abdf0797b7a06ffe197"}, ] [package.dependencies] @@ -7809,4 +7830,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" python-versions = "~3.11" -content-hash = "8af315a175ab43dbc5a777f68fca6d76af1443b5b574ab8570ef5dad59f288fc" +content-hash = "cb63112bfe7ee2b3fc194a422479f788c9a8027d445ea51a31f4ee20299beb53" diff --git a/pyproject.toml b/pyproject.toml index 116c034bc9..ac5bb0922c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -63,6 +63,9 @@ warn_unused_ignores=true # restrict dynamic typing warn_return_any=true +# allow implicit optionals for default args +implicit_optional = true + [tool.poetry] name = "openpilot" @@ -126,7 +129,7 @@ inputs = "*" Jinja2 = "*" lru-dict = "*" matplotlib = "*" -metadrive-simulator = { version = "0.4.2.2", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies +metadrive-simulator = { version = "0.4.2.3", markers = "platform_machine != 'aarch64'" } # no linux/aarch64 wheels for certain dependencies mpld3 = "*" mypy = "*" myst-parser = "*" @@ -167,8 +170,8 @@ build-backend = "poetry.core.masonry.api" # https://beta.ruff.rs/docs/configuration/#using-pyprojecttoml [tool.ruff] -select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] -ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"] +lint.select = ["E", "F", "W", "PIE", "C4", "ISC", "RUF008", "RUF100", "A", "B", "TID251"] +lint.ignore = ["E741", "E402", "C408", "ISC003", "B027", "B024"] line-length = 160 target-version="py311" exclude = [ @@ -180,8 +183,8 @@ exclude = [ "teleoprtc_repo", "third_party", ] -flake8-implicit-str-concat.allow-multiline=false -[tool.ruff.flake8-tidy-imports.banned-api] +lint.flake8-implicit-str-concat.allow-multiline=false +[tool.ruff.lint.flake8-tidy-imports.banned-api] "selfdrive".msg = "Use openpilot.selfdrive" "common".msg = "Use openpilot.common" "system".msg = "Use openpilot.system" diff --git a/rednose_repo b/rednose_repo index 18b91458fd..c5762e8bc6 160000 --- a/rednose_repo +++ b/rednose_repo @@ -1 +1 @@ -Subproject commit 18b91458fd396530d43e1a2fe9a3ac9055fa9109 +Subproject commit c5762e8bc6f7338c7a30d2cd1cba8cc64e81ba19 diff --git a/release/files_common b/release/files_common index cf985f682d..d62ef8d1bb 100644 --- a/release/files_common +++ b/release/files_common @@ -23,6 +23,7 @@ common/.gitignore common/__init__.py common/*.py common/*.pyx +common/mock/* common/transformations/__init__.py common/transformations/camera.py @@ -87,6 +88,7 @@ selfdrive/car/docs_definitions.py selfdrive/car/car_helpers.py selfdrive/car/fingerprints.py selfdrive/car/interfaces.py +selfdrive/car/values.py selfdrive/car/vin.py selfdrive/car/disable_ecu.py selfdrive/car/fw_versions.py @@ -95,7 +97,7 @@ selfdrive/car/ecu_addrs.py selfdrive/car/isotp_parallel_query.py selfdrive/car/tests/__init__.py selfdrive/car/tests/test_car_interfaces.py -selfdrive/car/torque_data/*.toml +selfdrive/car/torque_data/* selfdrive/car/body/*.py selfdrive/car/chrysler/*.py @@ -394,6 +396,7 @@ third_party/acados/acados_template/** third_party/bootstrap/** third_party/qt5/larch64/bin/** +third_party/maplibre-native-qt/** scripts/update_now.sh scripts/stop_updater.sh diff --git a/release/files_pc b/release/files_pc index 13f1b52166..f2bf090f2c 100644 --- a/release/files_pc +++ b/release/files_pc @@ -1,5 +1,3 @@ -third_party/mapbox-gl-native-qt/x86_64/*.so - third_party/libyuv/x86_64/** third_party/snpe/x86_64/** third_party/snpe/x86_64-linux-clang/** diff --git a/release/files_tici b/release/files_tici index 5d65ef458a..1771c45138 100644 --- a/release/files_tici +++ b/release/files_tici @@ -1,7 +1,6 @@ third_party/libyuv/larch64/** third_party/snpe/larch64** third_party/snpe/aarch64-ubuntu-gcc7.5/* -third_party/mapbox-gl-native-qt/include/* third_party/acados/larch64/** system/camerad/cameras/camera_qcom2.cc diff --git a/scripts/pyupgrade.sh b/scripts/pyupgrade.sh new file mode 100755 index 0000000000..19aac4b5e2 --- /dev/null +++ b/scripts/pyupgrade.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -e + +pip install --upgrade pyupgrade + +git ls-files '*.py' | grep -v 'third_party/' | xargs pyupgrade --py311-plus diff --git a/selfdrive/athena/athenad.py b/selfdrive/athena/athenad.py index 0a5c9b7999..9f901498b7 100755 --- a/selfdrive/athena/athenad.py +++ b/selfdrive/athena/athenad.py @@ -19,7 +19,8 @@ from dataclasses import asdict, dataclass, replace from datetime import datetime from functools import partial from queue import Queue -from typing import Callable, Dict, List, Optional, Set, Union, cast +from typing import cast +from collections.abc import Callable import requests from jsonrpc import JSONRPCResponseManager, dispatcher @@ -36,7 +37,7 @@ from openpilot.common.realtime import set_core_affinity from openpilot.system.hardware import HARDWARE, PC from openpilot.system.loggerd.xattr_cache import getxattr, setxattr from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_commit, get_origin, get_short_branch, get_version +from openpilot.system.version import get_commit, get_normalized_origin, get_short_branch, get_version from openpilot.system.hardware.hw import Paths @@ -55,17 +56,17 @@ 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]]] +UploadFileDict = dict[str, str | int | float | bool] +UploadItemDict = dict[str, str | bool | int | float | dict[str, str]] -UploadFilesToUrlResponse = Dict[str, Union[int, List[UploadItemDict], List[str]]] +UploadFilesToUrlResponse = dict[str, int | list[UploadItemDict] | list[str]] @dataclass class UploadFile: fn: str url: str - headers: Dict[str, str] + headers: dict[str, str] allow_cellular: bool @classmethod @@ -77,9 +78,9 @@ class UploadFile: class UploadItem: path: str url: str - headers: Dict[str, str] + headers: dict[str, str] created_at: int - id: Optional[str] + id: str | None retry_count: int = 0 current: bool = False progress: float = 0 @@ -97,9 +98,9 @@ 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() +cancelled_uploads: set[str] = set() -cur_upload_items: Dict[int, Optional[UploadItem]] = {} +cur_upload_items: dict[int, UploadItem | None] = {} def strip_bz2_extension(fn: str) -> str: @@ -127,14 +128,14 @@ class UploadQueueCache: @staticmethod def cache(upload_queue: Queue[UploadItem]) -> None: try: - queue: List[Optional[UploadItem]] = list(upload_queue.queue) + queue: list[UploadItem | None] = list(upload_queue.queue) items = [asdict(i) for i in queue if i is not None and (i.id not in cancelled_uploads)] Params().put("AthenadUploadQueue", json.dumps(items)) except Exception: cloudlog.exception("athena.UploadQueueCache.cache.exception") -def handle_long_poll(ws: WebSocket, exit_event: Optional[threading.Event]) -> None: +def handle_long_poll(ws: WebSocket, exit_event: threading.Event | None) -> None: end_event = threading.Event() threads = [ @@ -206,13 +207,17 @@ def retry_upload(tid: int, end_event: threading.Event, increase_count: bool = Tr break -def cb(sm, item, tid, sz: int, cur: int) -> None: +def cb(sm, item, tid, end_event: threading.Event, sz: int, cur: int) -> None: # Abort transfer if connection changed to metered after starting upload + # or if athenad is shutting down to re-connect the websocket sm.update(0) metered = sm['deviceState'].networkMetered if metered and (not item.allow_cellular): raise AbortTransferException + if end_event.is_set(): + raise AbortTransferException + cur_upload_items[tid] = replace(item, progress=cur / sz if sz else 1) @@ -252,7 +257,7 @@ def upload_handler(end_event: threading.Event) -> None: sz = -1 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, partial(cb, sm, item, tid)) + response = _do_upload(item, partial(cb, sm, item, tid, end_event)) 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) @@ -274,7 +279,7 @@ def upload_handler(end_event: threading.Event) -> None: cloudlog.exception("athena.upload_handler.exception") -def _do_upload(upload_item: UploadItem, callback: Optional[Callable] = None) -> requests.Response: +def _do_upload(upload_item: UploadItem, callback: Callable = None) -> requests.Response: path = upload_item.path compress = False @@ -313,17 +318,17 @@ def getMessage(service: str, timeout: int = 1000) -> dict: @dispatcher.add_method -def getVersion() -> Dict[str, str]: +def getVersion() -> dict[str, str]: return { "version": get_version(), - "remote": get_origin(''), - "branch": get_short_branch(''), - "commit": get_commit(default=''), + "remote": get_normalized_origin(), + "branch": get_short_branch(), + "commit": get_commit(), } @dispatcher.add_method -def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optional[str] = None, place_details: Optional[str] = None) -> Dict[str, int]: +def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: str = None, place_details: str = None) -> dict[str, int]: destination = { "latitude": latitude, "longitude": longitude, @@ -335,7 +340,7 @@ def setNavDestination(latitude: int = 0, longitude: int = 0, place_name: Optiona return {"success": 1} -def scan_dir(path: str, prefix: str) -> List[str]: +def scan_dir(path: str, prefix: str) -> list[str]: files = [] # only walk directories that match the prefix # (glob and friends traverse entire dir tree) @@ -355,12 +360,12 @@ def scan_dir(path: str, prefix: str) -> List[str]: return files @dispatcher.add_method -def listDataDirectory(prefix='') -> List[str]: +def listDataDirectory(prefix='') -> list[str]: return scan_dir(Paths.log_root(), prefix) @dispatcher.add_method -def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesToUrlResponse: +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, @@ -371,11 +376,11 @@ def uploadFileToUrl(fn: str, url: str, headers: Dict[str, str]) -> UploadFilesTo @dispatcher.add_method -def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlResponse: +def uploadFilesToUrls(files_data: list[UploadFileDict]) -> UploadFilesToUrlResponse: files = map(UploadFile.from_dict, files_data) - items: List[UploadItemDict] = [] - failed: List[str] = [] + 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) @@ -414,13 +419,13 @@ def uploadFilesToUrls(files_data: List[UploadFileDict]) -> UploadFilesToUrlRespo @dispatcher.add_method -def listUploadQueue() -> List[UploadItemDict]: +def listUploadQueue() -> list[UploadItemDict]: items = list(upload_queue.queue) + list(cur_upload_items.values()) 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: Union[str, List[str]]) -> Dict[str, Union[int, str]]: +def cancelUpload(upload_id: str | list[str]) -> dict[str, int | str]: if not isinstance(upload_id, list): upload_id = [upload_id] @@ -433,7 +438,7 @@ def cancelUpload(upload_id: Union[str, List[str]]) -> Dict[str, Union[int, str]] return {"success": 1} @dispatcher.add_method -def setRouteViewed(route: str) -> Dict[str, Union[int, str]]: +def setRouteViewed(route: str) -> dict[str, int | str]: # maintain a list of the last 10 routes viewed in connect params = Params() @@ -448,7 +453,7 @@ def setRouteViewed(route: str) -> Dict[str, Union[int, str]]: return {"success": 1} -def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local_port: int) -> Dict[str, int]: +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") @@ -482,7 +487,7 @@ def startLocalProxy(global_end_event: threading.Event, remote_ws_uri: str, local @dispatcher.add_method -def getPublicKey() -> Optional[str]: +def getPublicKey() -> str | None: if not os.path.isfile(Paths.persist_root() + '/comma/id_rsa.pub'): return None @@ -522,7 +527,7 @@ def getNetworks(): @dispatcher.add_method -def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: +def takeSnapshot() -> str | dict[str, str] | None: from openpilot.system.camerad.snapshot.snapshot import jpeg_write, snapshot ret = snapshot() if ret is not None: @@ -539,7 +544,7 @@ def takeSnapshot() -> Optional[Union[str, Dict[str, str]]]: raise Exception("not available while camerad is started") -def get_logs_to_send_sorted() -> List[str]: +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 = [] @@ -746,6 +751,9 @@ def ws_manage(ws: WebSocket, end_event: threading.Event) -> None: onroad_prev = onroad if sock is not None: + # While not sending data, onroad, we can expect to time out in 7 + (7 * 2) = 21s + # offroad, we can expect to time out in 30 + (10 * 3) = 60s + # FIXME: TCP_USER_TIMEOUT is effectively 2x for some reason (32s), so it's mostly unused sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_USER_TIMEOUT, 16000 if onroad else 0) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 7 if onroad else 30) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 7 if onroad else 10) @@ -759,7 +767,7 @@ def backoff(retries: int) -> int: return random.randrange(0, min(128, int(2 ** retries))) -def main(exit_event: Optional[threading.Event] = None): +def main(exit_event: threading.Event = None): try: set_core_affinity([0, 1, 2, 3]) except Exception: diff --git a/selfdrive/athena/manage_athenad.py b/selfdrive/athena/manage_athenad.py index 2a4a12e559..486e426911 100755 --- a/selfdrive/athena/manage_athenad.py +++ b/selfdrive/athena/manage_athenad.py @@ -6,7 +6,8 @@ from multiprocessing import Process from openpilot.common.params import Params from openpilot.selfdrive.manager.process import launcher from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_version, is_dirty +from openpilot.system.hardware import HARDWARE +from openpilot.system.version import get_version, get_normalized_origin, get_short_branch, get_commit, is_dirty ATHENA_MGR_PID_PARAM = "AthenadPid" @@ -14,7 +15,13 @@ ATHENA_MGR_PID_PARAM = "AthenadPid" def main(): params = Params() dongle_id = params.get("DongleId").decode('utf-8') - cloudlog.bind_global(dongle_id=dongle_id, version=get_version(), dirty=is_dirty()) + cloudlog.bind_global(dongle_id=dongle_id, + version=get_version(), + origin=get_normalized_origin(), + branch=get_short_branch(), + commit=get_commit(), + dirty=is_dirty(), + device=HARDWARE.get_device_type()) try: while 1: diff --git a/selfdrive/athena/registration.py b/selfdrive/athena/registration.py index 7db94c28c8..6574d9ac20 100755 --- a/selfdrive/athena/registration.py +++ b/selfdrive/athena/registration.py @@ -3,7 +3,6 @@ import time import json import jwt from pathlib import Path -from typing import Optional from datetime import datetime, timedelta from openpilot.common.api import api_get @@ -23,12 +22,12 @@ def is_registered_device() -> bool: return dongle not in (None, UNREGISTERED_DONGLE_ID) -def register(show_spinner=False) -> Optional[str]: +def register(show_spinner=False) -> str | None: params = Params() IMEI = params.get("IMEI", encoding='utf8') HardwareSerial = params.get("HardwareSerial", encoding='utf8') - dongle_id: Optional[str] = params.get("DongleId", encoding='utf8') + dongle_id: str | None = params.get("DongleId", encoding='utf8') needs_registration = None in (IMEI, HardwareSerial, dongle_id) pubkey = Path(Paths.persist_root()+"/comma/id_rsa.pub") @@ -48,8 +47,8 @@ def register(show_spinner=False) -> Optional[str]: # Block until we get the imei serial = HARDWARE.get_serial() start_time = time.monotonic() - imei1: Optional[str] = None - imei2: Optional[str] = None + imei1: str | None = None + imei2: str | None = None while imei1 is None and imei2 is None: try: imei1, imei2 = HARDWARE.get_imei(0), HARDWARE.get_imei(1) diff --git a/selfdrive/athena/tests/helpers.py b/selfdrive/athena/tests/helpers.py index 87202665aa..3dd98f02c9 100644 --- a/selfdrive/athena/tests/helpers.py +++ b/selfdrive/athena/tests/helpers.py @@ -1,7 +1,5 @@ import http.server -import threading import socket -from functools import wraps class MockResponse: @@ -65,25 +63,3 @@ class HTTPRequestHandler(http.server.SimpleHTTPRequestHandler): self.rfile.read(length) self.send_response(201, "Created") self.end_headers() - - -def with_http_server(func, handler=http.server.BaseHTTPRequestHandler, setup=None): - @wraps(func) - def inner(*args, **kwargs): - host = '127.0.0.1' - server = http.server.HTTPServer((host, 0), handler) - port = server.server_port - t = threading.Thread(target=server.serve_forever) - t.start() - - if setup is not None: - setup(host, port) - - try: - return func(*args, f'http://{host}:{port}', **kwargs) - finally: - server.shutdown() - server.server_close() - t.join() - - return inner diff --git a/selfdrive/athena/tests/test_athenad.py b/selfdrive/athena/tests/test_athenad.py index 9c7582a260..4850ab9a3f 100755 --- a/selfdrive/athena/tests/test_athenad.py +++ b/selfdrive/athena/tests/test_athenad.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from functools import partial +from functools import partial, wraps import json import multiprocessing import os @@ -12,7 +12,6 @@ import unittest from dataclasses import asdict, replace from datetime import datetime, timedelta from parameterized import parameterized -from typing import Optional from unittest import mock from websocket import ABNF @@ -24,9 +23,9 @@ from openpilot.common.params import Params from openpilot.common.timeout import Timeout from openpilot.selfdrive.athena import athenad from openpilot.selfdrive.athena.athenad import MAX_RETRY_COUNT, dispatcher -from openpilot.selfdrive.athena.tests.helpers import MockWebsocket, MockApi, EchoSocket, with_http_server +from openpilot.selfdrive.athena.tests.helpers import HTTPRequestHandler, MockWebsocket, MockApi, EchoSocket +from openpilot.selfdrive.test.helpers import with_http_server from openpilot.system.hardware.hw import Paths -from openpilot.selfdrive.athena.tests.helpers import HTTPRequestHandler def seed_athena_server(host, port): @@ -42,6 +41,20 @@ def seed_athena_server(host, port): with_mock_athena = partial(with_http_server, handler=HTTPRequestHandler, setup=seed_athena_server) +def with_upload_handler(func): + @wraps(func) + def wrapper(*args, **kwargs): + end_event = threading.Event() + thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) + thread.start() + try: + return func(*args, **kwargs) + finally: + end_event.set() + thread.join() + return wrapper + + class TestAthenadMethods(unittest.TestCase): @classmethod def setUpClass(cls): @@ -83,7 +96,7 @@ class TestAthenadMethods(unittest.TestCase): break @staticmethod - def _create_file(file: str, parent: Optional[str] = None, data: bytes = b'') -> str: + def _create_file(file: str, parent: str = None, data: bytes = b'') -> str: fn = os.path.join(Paths.log_root() if parent is None else parent, file) os.makedirs(os.path.dirname(fn), exist_ok=True) with open(fn, 'wb') as f: @@ -209,77 +222,60 @@ class TestAthenadMethods(unittest.TestCase): self.assertEqual(not_exists_resp, {'enqueued': 0, 'items': [], 'failed': ['does_not_exist.bz2']}) @with_mock_athena + @with_upload_handler def test_upload_handler(self, host): 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) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - athenad.upload_queue.put_nowait(item) - try: - self._wait_for_upload() - time.sleep(0.1) + self._wait_for_upload() + time.sleep(0.1) - # TODO: verify that upload actually succeeded - self.assertEqual(athenad.upload_queue.qsize(), 0) - finally: - end_event.set() + # TODO: verify that upload actually succeeded + # TODO: also check that end_event and metered network raises AbortTransferException + self.assertEqual(athenad.upload_queue.qsize(), 0) + @parameterized.expand([(500, True), (412, False)]) @with_mock_athena @mock.patch('requests.put') - def test_upload_handler_retry(self, host, mock_put): - for status, retry in ((500, True), (412, False)): - mock_put.return_value.status_code = status - 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) - - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() + @with_upload_handler + def test_upload_handler_retry(self, status, retry, mock_put, host): + mock_put.return_value.status_code = status + 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) - athenad.upload_queue.put_nowait(item) - try: - self._wait_for_upload() - time.sleep(0.1) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) - finally: - end_event.set() + self.assertEqual(athenad.upload_queue.qsize(), 1 if retry else 0) - if retry: - self.assertEqual(athenad.upload_queue.get().retry_count, 1) + if retry: + self.assertEqual(athenad.upload_queue.get().retry_count, 1) + @with_upload_handler def test_upload_handler_timeout(self): """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 = replace(item, retry_count=MAX_RETRY_COUNT) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - - try: - athenad.upload_queue.put_nowait(item_no_retry) - self._wait_for_upload() - time.sleep(0.1) - - # Check that upload with retry count exceeded is not put back - self.assertEqual(athenad.upload_queue.qsize(), 0) + athenad.upload_queue.put_nowait(item_no_retry) + self._wait_for_upload() + time.sleep(0.1) - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - time.sleep(0.1) + # Check that upload with retry count exceeded is not put back + self.assertEqual(athenad.upload_queue.qsize(), 0) - # Check that upload item was put back in the queue with incremented retry count - self.assertEqual(athenad.upload_queue.qsize(), 1) - self.assertEqual(athenad.upload_queue.get().retry_count, 1) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - finally: - end_event.set() + # Check that upload item was put back in the queue with incremented retry count + self.assertEqual(athenad.upload_queue.qsize(), 1) + self.assertEqual(athenad.upload_queue.get().retry_count, 1) + @with_upload_handler def test_cancelUpload(self): item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={}, created_at=int(time.time()*1000), id='id', allow_cellular=True) @@ -288,18 +284,13 @@ class TestAthenadMethods(unittest.TestCase): self.assertIn(item.id, athenad.cancelled_uploads) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - try: - self._wait_for_upload() - time.sleep(0.1) + self._wait_for_upload() + time.sleep(0.1) - self.assertEqual(athenad.upload_queue.qsize(), 0) - self.assertEqual(len(athenad.cancelled_uploads), 0) - finally: - end_event.set() + self.assertEqual(athenad.upload_queue.qsize(), 0) + self.assertEqual(len(athenad.cancelled_uploads), 0) + @with_upload_handler def test_cancelExpiry(self): t_future = datetime.now() - timedelta(days=40) ts = int(t_future.strftime("%s")) * 1000 @@ -308,42 +299,28 @@ class TestAthenadMethods(unittest.TestCase): fn = self._create_file('qlog.bz2') item = athenad.UploadItem(path=fn, url="http://localhost:44444/qlog.bz2", headers={}, created_at=ts, id='', allow_cellular=True) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() + time.sleep(0.1) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - try: - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - time.sleep(0.1) - - self.assertEqual(athenad.upload_queue.qsize(), 0) - finally: - end_event.set() + self.assertEqual(athenad.upload_queue.qsize(), 0) def test_listUploadQueueEmpty(self): items = dispatcher["listUploadQueue"]() self.assertEqual(len(items), 0) @with_http_server + @with_upload_handler 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) - end_event = threading.Event() - thread = threading.Thread(target=athenad.upload_handler, args=(end_event,)) - thread.start() - - try: - athenad.upload_queue.put_nowait(item) - self._wait_for_upload() - - items = dispatcher["listUploadQueue"]() - self.assertEqual(len(items), 1) - self.assertTrue(items[0]['current']) + athenad.upload_queue.put_nowait(item) + self._wait_for_upload() - finally: - end_event.set() + items = dispatcher["listUploadQueue"]() + self.assertEqual(len(items), 1) + self.assertTrue(items[0]['current']) def test_listUploadQueue(self): item = athenad.UploadItem(path="qlog.bz2", url="http://localhost:44444/qlog.bz2", headers={}, diff --git a/selfdrive/athena/tests/test_athenad_ping.py b/selfdrive/athena/tests/test_athenad_ping.py index 5231b0475f..f56fcac8b5 100755 --- a/selfdrive/athena/tests/test_athenad_ping.py +++ b/selfdrive/athena/tests/test_athenad_ping.py @@ -3,7 +3,7 @@ import subprocess import threading import time import unittest -from typing import cast, Optional +from typing import cast from unittest import mock from openpilot.common.params import Params @@ -12,6 +12,8 @@ from openpilot.selfdrive.athena import athenad from openpilot.selfdrive.manager.helpers import write_onroad_params from openpilot.system.hardware import TICI +TIMEOUT_TOLERANCE = 20 # seconds + def wifi_radio(on: bool) -> None: if not TICI: @@ -27,8 +29,8 @@ class TestAthenadPing(unittest.TestCase): athenad: threading.Thread exit_event: threading.Event - def _get_ping_time(self) -> Optional[str]: - return cast(Optional[str], self.params.get("LastAthenaPingTime", encoding="utf-8")) + def _get_ping_time(self) -> str | None: + return cast(str | None, self.params.get("LastAthenaPingTime", encoding="utf-8")) def _clear_ping_time(self) -> None: self.params.remove("LastAthenaPingTime") @@ -55,7 +57,7 @@ class TestAthenadPing(unittest.TestCase): self.exit_event.set() self.athenad.join() - @mock.patch('openpilot.selfdrive.athena.athenad.create_connection', autospec=True) + @mock.patch('openpilot.selfdrive.athena.athenad.create_connection', new_callable=lambda: mock.MagicMock(wraps=athenad.create_connection)) def assertTimeout(self, reconnect_time: float, mock_create_connection: mock.MagicMock) -> None: self.athenad.start() @@ -63,7 +65,7 @@ class TestAthenadPing(unittest.TestCase): mock_create_connection.assert_called_once() mock_create_connection.reset_mock() - # check normal behaviour + # check normal behaviour, server pings on connection with self.subTest("Wi-Fi: receives ping"), Timeout(70, "no ping received"): while not self._received_ping(): time.sleep(0.1) @@ -92,12 +94,12 @@ class TestAthenadPing(unittest.TestCase): @unittest.skipIf(not TICI, "only run on desk") def test_offroad(self) -> None: write_onroad_params(False, self.params) - self.assertTimeout(100) # expect approx 90s + self.assertTimeout(60 + TIMEOUT_TOLERANCE) # based using TCP keepalive settings @unittest.skipIf(not TICI, "only run on desk") def test_onroad(self) -> None: write_onroad_params(True, self.params) - self.assertTimeout(30) # expect 20-30s + self.assertTimeout(21 + TIMEOUT_TOLERANCE) if __name__ == "__main__": diff --git a/selfdrive/boardd/boardd.cc b/selfdrive/boardd/boardd.cc index 9e2a960055..b2b59d3752 100644 --- a/selfdrive/boardd/boardd.cc +++ b/selfdrive/boardd/boardd.cc @@ -362,11 +362,11 @@ std::optional send_panda_states(PubMaster *pm, const std::vector ps.setHeartbeatLost((bool)(health.heartbeat_lost_pkt)); ps.setAlternativeExperience(health.alternative_experience_pkt); ps.setHarnessStatus(cereal::PandaState::HarnessStatus(health.car_harness_status_pkt)); - ps.setInterruptLoad(health.interrupt_load); + ps.setInterruptLoad(health.interrupt_load_pkt); ps.setFanPower(health.fan_power); ps.setFanStallCount(health.fan_stall_count); - ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid)); - ps.setSpiChecksumErrorCount(health.spi_checksum_error_count); + ps.setSafetyRxChecksInvalid((bool)(health.safety_rx_checks_invalid_pkt)); + ps.setSpiChecksumErrorCount(health.spi_checksum_error_count_pkt); ps.setSbu1Voltage(health.sbu1_voltage_mV / 1000.0f); ps.setSbu2Voltage(health.sbu2_voltage_mV / 1000.0f); diff --git a/selfdrive/boardd/pandad.py b/selfdrive/boardd/pandad.py index 672678778b..988d1a2409 100755 --- a/selfdrive/boardd/pandad.py +++ b/selfdrive/boardd/pandad.py @@ -4,7 +4,7 @@ import os import usb1 import time import subprocess -from typing import List, NoReturn +from typing import NoReturn from functools import cmp_to_key from panda import Panda, PandaDFU, PandaProtocolMismatch, FW_PATH @@ -93,6 +93,11 @@ def main() -> NoReturn: cloudlog.event("pandad.flash_and_connect", count=count) params.remove("PandaSignatures") + # TODO: remove this in the next AGNOS + # wait until USB is up before counting + if time.monotonic() < 25.: + no_internal_panda_count = 0 + # Handle missing internal panda if no_internal_panda_count > 0: if no_internal_panda_count == 3: @@ -119,7 +124,7 @@ def main() -> NoReturn: cloudlog.info(f"{len(panda_serials)} panda(s) found, connecting - {panda_serials}") # Flash pandas - pandas: List[Panda] = [] + pandas: list[Panda] = [] for serial in panda_serials: pandas.append(flash_panda(serial)) diff --git a/selfdrive/boardd/tests/test_boardd_loopback.py b/selfdrive/boardd/tests/test_boardd_loopback.py index dfce0e3710..148ce9a25d 100755 --- a/selfdrive/boardd/tests/test_boardd_loopback.py +++ b/selfdrive/boardd/tests/test_boardd_loopback.py @@ -32,7 +32,7 @@ class TestBoardd(unittest.TestCase): with Timeout(90, "boardd didn't start"): sm = messaging.SubMaster(['pandaStates']) - while sm.rcv_frame['pandaStates'] < 1 or len(sm['pandaStates']) == 0 or \ + while sm.recv_frame['pandaStates'] < 1 or len(sm['pandaStates']) == 0 or \ any(ps.pandaType == log.PandaState.PandaType.unknown for ps in sm['pandaStates']): sm.update(1000) diff --git a/selfdrive/car/__init__.py b/selfdrive/car/__init__.py index c90ae50ab9..75b85cab2f 100644 --- a/selfdrive/car/__init__.py +++ b/selfdrive/car/__init__.py @@ -1,11 +1,13 @@ # functions common among cars from collections import namedtuple -from typing import Dict, List, Optional +from dataclasses import dataclass, field +from enum import ReprEnum import capnp from cereal import car from openpilot.common.numpy_fast import clip, interp +from openpilot.selfdrive.car.docs_definitions import CarInfo # kg of standard extra cargo to count for drive, gas, etc... @@ -24,9 +26,9 @@ def apply_hysteresis(val: float, val_steady: float, hyst_gap: float) -> float: return val_steady -def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: Dict[int, capnp.lib.capnp._EnumModule], - unpressed_btn: int = 0) -> List[capnp.lib.capnp._DynamicStructBuilder]: - events: List[capnp.lib.capnp._DynamicStructBuilder] = [] +def create_button_events(cur_btn: int, prev_btn: int, buttons_dict: dict[int, capnp.lib.capnp._EnumModule], + unpressed_btn: int = 0) -> list[capnp.lib.capnp._DynamicStructBuilder]: + events: list[capnp.lib.capnp._DynamicStructBuilder] = [] if cur_btn == prev_btn: return events @@ -73,7 +75,10 @@ def scale_tire_stiffness(mass, wheelbase, center_to_front, tire_stiffness_factor return tire_stiffness_front, tire_stiffness_rear -def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> Dict[str, str]: +DbcDict = dict[str, str] + + +def dbc_dict(pt_dbc, radar_dbc, chassis_dbc=None, body_dbc=None) -> DbcDict: return {'pt': pt_dbc, 'radar': radar_dbc, 'chassis': chassis_dbc, 'body': body_dbc} @@ -208,7 +213,7 @@ def get_safety_config(safety_model, safety_param = None): class CanBusBase: offset: int - def __init__(self, CP, fingerprint: Optional[Dict[int, Dict[int, int]]]) -> None: + def __init__(self, CP, fingerprint: dict[int, dict[int, int]] | None) -> None: if CP is None: assert fingerprint is not None num = max([k for k, v in fingerprint.items() if len(v)], default=0) // 4 + 1 @@ -236,3 +241,46 @@ class CanSignalRateCalculator: self.previous_value = current_value return self.rate + + +CarInfos = CarInfo | list[CarInfo] + + +@dataclass(kw_only=True) +class CarSpecs: + mass: float + wheelbase: float + steerRatio: float + centerToFrontRatio: float = field(default=0.5) + minSteerSpeed: float = field(default=0.) + minEnableSpeed: float = field(default=-1.) + + +@dataclass(order=True) +class PlatformConfig: + platform_str: str + car_info: CarInfos + dbc_dict: DbcDict + + specs: CarSpecs | None = None + + def __hash__(self) -> int: + return hash(self.platform_str) + + +class Platforms(str, ReprEnum): + config: PlatformConfig + + def __new__(cls, platform_config: PlatformConfig): + member = str.__new__(cls, platform_config.platform_str) + member.config = platform_config + member._value_ = platform_config.platform_str + return member + + @classmethod + def create_dbc_map(cls) -> dict[str, DbcDict]: + return {p: p.config.dbc_dict for p in cls} + + @classmethod + def create_carinfo_map(cls) -> dict[str, CarInfos]: + return {p: p.config.car_info for p in cls} diff --git a/selfdrive/car/body/values.py b/selfdrive/car/body/values.py index 33119bf0fd..441905f28b 100644 --- a/selfdrive/car/body/values.py +++ b/selfdrive/car/body/values.py @@ -1,8 +1,5 @@ -from enum import StrEnum -from typing import Dict - from cereal import car -from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car import PlatformConfig, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarInfo from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -22,13 +19,12 @@ class CarControllerParams: pass -class CAR(StrEnum): - BODY = "COMMA BODY" - - -CAR_INFO: Dict[str, CarInfo] = { - CAR.BODY: CarInfo("comma body", package="All"), -} +class CAR(Platforms): + BODY = PlatformConfig( + "COMMA BODY", + CarInfo("comma body", package="All"), + dbc_dict('comma_body', None), + ) FW_QUERY_CONFIG = FwQueryConfig( @@ -41,7 +37,5 @@ FW_QUERY_CONFIG = FwQueryConfig( ], ) - -DBC = { - CAR.BODY: dbc_dict('comma_body', None), -} +CAR_INFO = CAR.create_carinfo_map() +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 47290c89ee..339be1912c 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -1,10 +1,11 @@ import os import time -from typing import Callable, Dict, List, Optional, Tuple +from collections.abc import Callable from cereal import car from openpilot.common.params import Params from openpilot.common.basedir import BASEDIR +from openpilot.selfdrive.car.values import PLATFORMS from openpilot.system.version import is_comma_remote, is_tested_branch from openpilot.selfdrive.car.interfaces import get_interface_attr from openpilot.selfdrive.car.fingerprints import eliminate_incompatible_cars, all_legacy_fingerprint_cars @@ -63,7 +64,7 @@ def load_interfaces(brand_names): return ret -def _get_interface_names() -> Dict[str, List[str]]: +def _get_interface_names() -> dict[str, list[str]]: # returns a dict of brand name and its respective models brand_names = {} for brand_name, brand_models in get_interface_attr("CAR").items(): @@ -77,7 +78,7 @@ interface_names = _get_interface_names() interfaces = load_interfaces(interface_names) -def can_fingerprint(next_can: Callable) -> Tuple[Optional[str], Dict[int, dict]]: +def can_fingerprint(next_can: Callable) -> tuple[str | None, dict[int, dict]]: finger = gen_empty_fingerprint() candidate_cars = {i: all_legacy_fingerprint_cars() for i in [0, 1]} # attempt fingerprint on both bus 0 and 1 frame = 0 @@ -141,9 +142,10 @@ def fingerprint(logcan, sendcan, num_pandas): cached = True else: cloudlog.warning("Getting VIN & FW versions") - # enable OBD multiplexing for Vin query, also allows time for sendcan subscriber to connect + # enable OBD multiplexing for VIN query + # NOTE: this takes ~0.1s and is relied on to allow sendcan subscriber to connect in time set_obd_multiplexing(params, True) - # Vin query only reliably works through OBDII + # VIN query only reliably works through OBDII vin_rx_addr, vin_rx_bus, vin = get_vin(logcan, sendcan, (0, 1)) 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) @@ -161,7 +163,7 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.warning("VIN %s", vin) params.put("CarVin", vin) - # disable OBD multiplexing for potential ECU knockouts + # disable OBD multiplexing for CAN fingerprinting and potential ECU knockouts set_obd_multiplexing(params, False) params.put_bool("FirmwareQueryDone", True) @@ -187,15 +189,18 @@ def fingerprint(logcan, sendcan, num_pandas): cloudlog.event("fingerprinted", car_fingerprint=car_fingerprint, source=source, fuzzy=not exact_match, cached=cached, fw_count=len(car_fw), ecu_responses=list(ecu_rx_addrs), vin_rx_addr=vin_rx_addr, vin_rx_bus=vin_rx_bus, - fingerprints=finger, fw_query_time=fw_query_time, error=True) - return car_fingerprint, finger, vin, car_fw, source, exact_match + fingerprints=repr(finger), fw_query_time=fw_query_time, error=True) + + car_platform = PLATFORMS.get(car_fingerprint, car_fingerprint) + + return car_platform, finger, vin, car_fw, source, exact_match 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.event("car doesn't match any fingerprints", fingerprints=fingerprints, error=True) + cloudlog.event("car doesn't match any fingerprints", fingerprints=repr(fingerprints), error=True) candidate = "mock" CarInterface, CarController, CarState = interfaces[candidate] diff --git a/selfdrive/car/chrysler/fingerprints.py b/selfdrive/car/chrysler/fingerprints.py index f9a3fe5b10..1df514e79b 100644 --- a/selfdrive/car/chrysler/fingerprints.py +++ b/selfdrive/car/chrysler/fingerprints.py @@ -67,6 +67,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'68267018AO ', b'68267020AJ ', + b'68303534AJ ', b'68340762AD ', b'68340764AD ', b'68352652AE ', @@ -299,6 +300,7 @@ FW_VERSIONS = { CAR.JEEP_GRAND_CHEROKEE_2019: { (Ecu.combinationMeter, 0x742, None): [ b'68402703AB', + b'68402704AB', b'68402708AB', b'68402971AD', b'68454144AD', @@ -326,6 +328,7 @@ FW_VERSIONS = { (Ecu.eps, 0x75a, None): [ b'68417279AA', b'68417280AA', + b'68417281AA', b'68453431AA', b'68453433AA', b'68453435AA', @@ -336,6 +339,7 @@ FW_VERSIONS = { (Ecu.engine, 0x7e0, None): [ b'05035674AB ', b'68412635AG ', + b'68412660AD ', b'68422860AB', b'68449435AE ', b'68496223AA ', @@ -346,6 +350,7 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'05035707AA', b'68419672AC', + b'68419678AB', b'68423905AB', b'68449258AC', b'68495807AA', @@ -359,6 +364,7 @@ FW_VERSIONS = { b'68294051AG', b'68294051AI', b'68294052AG', + b'68294052AH', b'68294063AG', b'68294063AH', b'68294063AI', @@ -395,6 +401,8 @@ FW_VERSIONS = { b'68527383AD', b'68527387AE', b'68527403AC', + b'68546047AF', + b'68631938AA', b'68631942AA', ], (Ecu.srs, 0x744, None): [ @@ -459,6 +467,7 @@ FW_VERSIONS = { b'68552791AB', b'68552794AA', b'68585106AB', + b'68585107AB', b'68585108AB', b'68585109AB', b'68585112AB', @@ -472,14 +481,17 @@ FW_VERSIONS = { b'05149591AD ', b'05149591AE ', b'05149592AE ', + b'05149599AE ', b'05149600AD ', b'05149605AE ', b'05149846AA ', b'05149848AA ', + b'05149848AC ', b'05190341AD', b'68378695AJ ', b'68378696AJ ', b'68378701AI ', + b'68378702AI ', b'68378710AL ', b'68378748AL ', b'68378758AM ', @@ -508,6 +520,7 @@ FW_VERSIONS = { b'68539651AD', b'68586101AA ', b'68586105AB ', + b'68629922AC ', b'68629926AC ', ], (Ecu.transmission, 0x7e1, None): [ @@ -600,4 +613,34 @@ FW_VERSIONS = { b'M2421132MB', ], }, + CAR.DODGE_DURANGO: { + (Ecu.combinationMeter, 0x742, None): [ + b'68454261AD', + b'68471535AE', + ], + (Ecu.srs, 0x744, None): [ + b'68355362AB', + b'68492238AD', + ], + (Ecu.abs, 0x747, None): [ + b'68408639AD', + b'68499978AB', + ], + (Ecu.fwdRadar, 0x753, None): [ + b'68440581AE', + b'68456722AC', + ], + (Ecu.eps, 0x75a, None): [ + b'68453435AA', + b'68498477AA', + ], + (Ecu.engine, 0x7e0, None): [ + b'05035786AE ', + b'68449476AE ', + ], + (Ecu.transmission, 0x7e1, None): [ + b'05035826AC', + b'68449265AC', + ], + }, } diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 10cdd0fd14..32a4f5dfcf 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -28,13 +28,13 @@ class CarInterface(CarInterfaceBase): 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_GRAND_CHEROKEE_2019) + new_eps_platform = candidate in (CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.JEEP_GRAND_CHEROKEE_2019, CAR.DODGE_DURANGO) 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): + if candidate in (CAR.PACIFICA_2017_HYBRID, CAR.PACIFICA_2018, CAR.PACIFICA_2018_HYBRID, CAR.PACIFICA_2019_HYBRID, CAR.PACIFICA_2020, CAR.DODGE_DURANGO): ret.mass = 2242. ret.wheelbase = 3.089 ret.steerRatio = 16.2 # Pacifica Hybrid 2017 @@ -80,6 +80,7 @@ class CarInterface(CarInterfaceBase): if ret.flags & ChryslerFlags.HIGHER_MIN_STEERING_SPEED: # TODO: allow these cars to steer down to 13 m/s if already engaged. + # TODO: Durango 2020 may be able to steer to zero once above 38 kph ret.minSteerSpeed = 17.5 # m/s 17 on the way up, 13 on the way down once engaged. ret.centerToFront = ret.wheelbase * 0.44 diff --git a/selfdrive/car/chrysler/values.py b/selfdrive/car/chrysler/values.py index 34e602562a..a7eec8fe5a 100644 --- a/selfdrive/car/chrysler/values.py +++ b/selfdrive/car/chrysler/values.py @@ -1,6 +1,5 @@ from enum import IntFlag, StrEnum from dataclasses import dataclass, field -from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds @@ -23,6 +22,9 @@ class CAR(StrEnum): PACIFICA_2018 = "CHRYSLER PACIFICA 2018" PACIFICA_2020 = "CHRYSLER PACIFICA 2020" + # Dodge + DODGE_DURANGO = "DODGE DURANGO 2021" + # Jeep JEEP_GRAND_CHEROKEE = "JEEP GRAND CHEROKEE V6 2018" # includes 2017 Trailhawk JEEP_GRAND_CHEROKEE_2019 = "JEEP GRAND CHEROKEE 2019" # includes 2020 Trailhawk @@ -63,7 +65,7 @@ class ChryslerCarInfo(CarInfo): car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.fca])) -CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { +CAR_INFO: dict[str, ChryslerCarInfo | list[ChryslerCarInfo] | None] = { CAR.PACIFICA_2017_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2017"), CAR.PACIFICA_2018_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2018"), CAR.PACIFICA_2019_HYBRID: ChryslerCarInfo("Chrysler Pacifica Hybrid 2019-23"), @@ -74,6 +76,7 @@ CAR_INFO: Dict[str, Optional[Union[ChryslerCarInfo, List[ChryslerCarInfo]]]] = { ], CAR.JEEP_GRAND_CHEROKEE: ChryslerCarInfo("Jeep Grand Cherokee 2016-18", video_link="https://www.youtube.com/watch?v=eLR9o2JkuRk"), CAR.JEEP_GRAND_CHEROKEE_2019: ChryslerCarInfo("Jeep Grand Cherokee 2019-21", video_link="https://www.youtube.com/watch?v=jBe4lWnRSu4"), + CAR.DODGE_DURANGO: ChryslerCarInfo("Dodge Durango 2020-21"), CAR.RAM_1500: ChryslerCarInfo("Ram 1500 2019-24", car_parts=CarParts.common([CarHarness.ram])), CAR.RAM_HD: [ ChryslerCarInfo("Ram 2500 2020-24", car_parts=CarParts.common([CarHarness.ram])), @@ -128,6 +131,7 @@ DBC = { CAR.PACIFICA_2020: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.PACIFICA_2018_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.PACIFICA_2019_HYBRID: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), + CAR.DODGE_DURANGO: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.JEEP_GRAND_CHEROKEE: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.JEEP_GRAND_CHEROKEE_2019: dbc_dict('chrysler_pacifica_2017_hybrid_generated', 'chrysler_pacifica_2017_hybrid_private_fusion'), CAR.RAM_1500: dbc_dict('chrysler_ram_dt_generated', None), diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py index 8475a69d8a..caa79a8f87 100755 --- a/selfdrive/car/docs.py +++ b/selfdrive/car/docs.py @@ -5,7 +5,6 @@ import jinja2 import os from enum import Enum from natsort import natsorted -from typing import Dict, List from cereal import car from openpilot.common.basedir import BASEDIR @@ -14,7 +13,7 @@ from openpilot.selfdrive.car.docs_definitions import CarInfo, Column, CommonFoot from openpilot.selfdrive.car.car_helpers import interfaces, get_interface_attr -def get_all_footnotes() -> Dict[Enum, int]: +def get_all_footnotes() -> dict[Enum, int]: all_footnotes = list(CommonFootnote) for footnotes in get_interface_attr("Footnote", ignore_none=True).values(): all_footnotes.extend(footnotes) @@ -25,8 +24,8 @@ CARS_MD_OUT = os.path.join(BASEDIR, "docs", "CARS.md") CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md") -def get_all_car_info() -> List[CarInfo]: - all_car_info: List[CarInfo] = [] +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(): # If available, uses experimental longitudinal limits for the docs @@ -47,19 +46,19 @@ def get_all_car_info() -> List[CarInfo]: all_car_info.append(_car_info) # Sort cars by make and model + year - sorted_cars: List[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower()) + sorted_cars: list[CarInfo] = natsorted(all_car_info, key=lambda car: car.name.lower()) return sorted_cars -def group_by_make(all_car_info: List[CarInfo]) -> Dict[str, List[CarInfo]]: +def group_by_make(all_car_info: list[CarInfo]) -> dict[str, list[CarInfo]]: sorted_car_info = defaultdict(list) for car_info in all_car_info: sorted_car_info[car_info.make].append(car_info) return dict(sorted_car_info) -def generate_cars_md(all_car_info: List[CarInfo], template_fn: str) -> str: - with open(template_fn, "r") as f: +def generate_cars_md(all_car_info: list[CarInfo], template_fn: str) -> str: + with open(template_fn) as f: template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True) footnotes = [fn.value.text for fn in get_all_footnotes()] diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py index f2ce743607..841cc3af2f 100644 --- a/selfdrive/car/docs_definitions.py +++ b/selfdrive/car/docs_definitions.py @@ -3,7 +3,6 @@ from collections import namedtuple import copy from dataclasses import dataclass, field from enum import Enum -from typing import Dict, List, Optional, Tuple, Union from cereal import car from openpilot.common.conversions import Conversions as CV @@ -35,7 +34,7 @@ class Star(Enum): @dataclass class BasePart: name: str - parts: List[Enum] = field(default_factory=list) + parts: list[Enum] = field(default_factory=list) def all_parts(self): # Recursively get all parts @@ -76,7 +75,7 @@ class Accessory(EnumBase): @dataclass class BaseCarHarness(BasePart): - parts: List[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft]) + parts: list[Enum] = field(default_factory=lambda: [Accessory.harness_box, Accessory.comma_power_v2, Cable.rj45_cable_7ft]) has_connector: bool = True # without are hidden on the harness connector page @@ -149,18 +148,18 @@ class PartType(Enum): tool = Tool -DEFAULT_CAR_PARTS: List[EnumBase] = [Device.threex] +DEFAULT_CAR_PARTS: list[EnumBase] = [Device.threex] @dataclass class CarParts: - parts: List[EnumBase] = field(default_factory=list) + parts: list[EnumBase] = field(default_factory=list) def __call__(self): return copy.deepcopy(self) @classmethod - def common(cls, add: Optional[List[EnumBase]] = None, remove: Optional[List[EnumBase]] = None): + def common(cls, add: list[EnumBase] = None, remove: list[EnumBase] = None): p = [part for part in (add or []) + DEFAULT_CAR_PARTS if part not in (remove or [])] return cls(p) @@ -186,7 +185,7 @@ class CommonFootnote(Enum): Column.LONGITUDINAL) -def get_footnotes(footnotes: List[Enum], column: Column) -> List[Enum]: +def get_footnotes(footnotes: list[Enum], column: Column) -> list[Enum]: # Returns applicable footnotes given current column return [fn for fn in footnotes if fn.value.column == column] @@ -209,7 +208,7 @@ def get_year_list(years): return years_list -def split_name(name: str) -> Tuple[str, str, str]: +def split_name(name: str) -> tuple[str, str, str]: make, model = name.split(" ", 1) years = "" match = re.search(MODEL_YEARS_RE, model) @@ -233,13 +232,13 @@ class CarInfo: # the minimum compatibility requirements for this model, regardless # of market. can be a package, trim, or list of features - requirements: Optional[str] = None + requirements: str | None = None - video_link: Optional[str] = None - footnotes: List[Enum] = field(default_factory=list) - min_steer_speed: Optional[float] = None - min_enable_speed: Optional[float] = None - auto_resume: Optional[bool] = None + video_link: str | None = None + footnotes: list[Enum] = field(default_factory=list) + min_steer_speed: float | None = None + min_enable_speed: float | None = None + auto_resume: bool | None = None # all the parts needed for the supported car car_parts: CarParts = field(default_factory=CarParts) @@ -248,7 +247,7 @@ class CarInfo: self.make, self.model, self.years = split_name(self.name) self.year_list = get_year_list(self.years) - def init(self, CP: car.CarParams, all_footnotes: Dict[Enum, int]): + def init(self, CP: car.CarParams, all_footnotes: dict[Enum, int]): self.car_name = CP.carName self.car_fingerprint = CP.carFingerprint @@ -293,7 +292,7 @@ class CarInfo: if len(tools_docs): hardware_col += f'
Tools{display_func(tools_docs)}
' - self.row: Dict[Enum, Union[str, Star]] = { + self.row: dict[Enum, str | Star] = { Column.MAKE: self.make, Column.MODEL: self.model, Column.PACKAGE: self.package, @@ -352,7 +351,7 @@ class CarInfo: raise Exception(f"This notCar does not have a detail sentence: {CP.carFingerprint}") def get_column(self, column: Column, star_icon: str, video_icon: str, footnote_tag: str) -> str: - item: Union[str, Star] = self.row[column] + item: str | Star = self.row[column] if isinstance(item, Star): item = star_icon.format(item.value) elif column == Column.MODEL and len(self.years): diff --git a/selfdrive/car/ecu_addrs.py b/selfdrive/car/ecu_addrs.py index 13f7926def..da5e7b4612 100755 --- a/selfdrive/car/ecu_addrs.py +++ b/selfdrive/car/ecu_addrs.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import capnp import time -from typing import Optional, Set import cereal.messaging as messaging from panda.python.uds import SERVICE_TYPE @@ -20,7 +19,7 @@ def make_tester_present_msg(addr, bus, subaddr=None): return make_can_msg(addr, bytes(dat), bus) -def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: Optional[int] = None) -> bool: +def is_tester_present_response(msg: capnp.lib.capnp._DynamicStructReader, subaddr: int = None) -> bool: # ISO-TP messages are always padded to 8 bytes # tester present response is always a single frame dat_offset = 1 if subaddr is not None else 0 @@ -34,16 +33,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[EcuAddrBusType]: +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[EcuAddrBusType] = {(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[EcuAddrBusType], - responses: Set[EcuAddrBusType], timeout: float = 1, debug: bool = False) -> Set[EcuAddrBusType]: - ecu_responses: Set[EcuAddrBusType] = 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] diff --git a/selfdrive/car/ford/carstate.py b/selfdrive/car/ford/carstate.py index 9230d16ef9..34006e8da4 100644 --- a/selfdrive/car/ford/carstate.py +++ b/selfdrive/car/ford/carstate.py @@ -18,17 +18,10 @@ class CarState(CarStateBase): self.shifter_values = can_define.dv["Gear_Shift_by_Wire_FD1"]["TrnRng_D_RqGsm"] self.vehicle_sensors_valid = False - self.unsupported_platform = False def update(self, cp, cp_cam): ret = car.CarState.new_message() - # Ford Q3 hybrid variants experience a bug where a message from the PCM sends invalid checksums, - # this must be root-caused before enabling support. Ford Q4 hybrids do not have this problem. - # TrnAin_Tq_Actl and its quality flag are only set on ICE platform variants - self.unsupported_platform = (cp.vl["VehicleOperatingModes"]["TrnAinTq_D_Qf"] == 0 and - self.CP.carFingerprint not in CANFD_CAR) - # Occasionally on startup, the ABS module recalibrates the steering pinion offset, so we need to block engagement # The vehicle usually recovers out of this state within a minute of normal driving self.vehicle_sensors_valid = cp.vl["SteeringPinion_Data"]["StePinCompAnEst_D_Qf"] == 3 @@ -54,7 +47,7 @@ class CarState(CarStateBase): 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 + ret.espDisabled = cp.vl["Cluster_Info1_FD1"]["DrvSlipCtlMde_D_Rq"] != 0 # 0 is default mode if self.CP.carFingerprint in CANFD_CAR: # this signal is always 0 on non-CAN FD cars diff --git a/selfdrive/car/ford/fingerprints.py b/selfdrive/car/ford/fingerprints.py index 0085b6b9c6..a5d465849a 100644 --- a/selfdrive/car/ford/fingerprints.py +++ b/selfdrive/car/ford/fingerprints.py @@ -24,6 +24,7 @@ FW_VERSIONS = { (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-AK\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): [ diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index cc013fb54b..fd4c381f88 100644 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -3,7 +3,7 @@ from panda import Panda from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.ford.fordcan import CanBus -from openpilot.selfdrive.car.ford.values import CANFD_CAR, CAR, Ecu +from openpilot.selfdrive.car.ford.values import CANFD_CAR, Ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase TransmissionType = car.CarParams.TransmissionType @@ -39,51 +39,6 @@ class CarInterface(CarInterfaceBase): if candidate in CANFD_CAR: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_FORD_CANFD - if candidate == CAR.BRONCO_SPORT_MK1: - ret.wheelbase = 2.67 - ret.steerRatio = 17.7 - ret.mass = 1625 - - elif candidate == CAR.ESCAPE_MK4: - ret.wheelbase = 2.71 - ret.steerRatio = 16.7 - ret.mass = 1750 - - elif candidate == CAR.EXPLORER_MK6: - ret.wheelbase = 3.025 - ret.steerRatio = 16.8 - ret.mass = 2050 - - elif candidate == CAR.F_150_MK14: - # required trim only on SuperCrew - ret.wheelbase = 3.69 - ret.steerRatio = 17.0 - ret.mass = 2000 - - elif candidate == CAR.F_150_LIGHTNING_MK1: - # required trim only on SuperCrew - ret.wheelbase = 3.70 - ret.steerRatio = 16.9 - ret.mass = 2948 - - elif candidate == CAR.MUSTANG_MACH_E_MK1: - ret.wheelbase = 2.984 - ret.steerRatio = 17.0 # guess - ret.mass = 2200 - - elif candidate == CAR.FOCUS_MK4: - ret.wheelbase = 2.7 - ret.steerRatio = 15.0 - ret.mass = 1350 - - elif candidate == CAR.MAVERICK_MK1: - ret.wheelbase = 3.076 - ret.steerRatio = 17.0 - ret.mass = 1650 - - else: - raise ValueError(f"Unsupported car: {candidate}") - # Auto Transmission: 0x732 ECU or Gear_Shift_by_Wire_FD1 found_ecus = [fw.ecu for fw in car_fw] if Ecu.shiftByWire in found_ecus or 0x5A in fingerprint[CAN.main] or docs: @@ -109,8 +64,6 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret, extra_gears=[GearShifter.manumatic]) if not self.CS.vehicle_sensors_valid: events.add(car.CarEvent.EventName.vehicleSensorsInvalid) - if self.CS.unsupported_platform: - events.add(car.CarEvent.EventName.startupNoControl) ret.events = events.to_msg() diff --git a/selfdrive/car/ford/tests/test_ford.py b/selfdrive/car/ford/tests/test_ford.py index bd311508e3..ea033d0aff 100755 --- a/selfdrive/car/ford/tests/test_ford.py +++ b/selfdrive/car/ford/tests/test_ford.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import unittest -from typing import Dict, Iterable, Optional, Tuple +from collections.abc import Iterable import capnp -from parameterized import parameterized from hypothesis import settings, given, strategies as st +from parameterized import parameterized from cereal import car from openpilot.selfdrive.car.ford.values import CAR, FW_QUERY_CONFIG, get_platform_codes @@ -48,7 +48,7 @@ class TestFordFW(unittest.TestCase): self.assertIsNone(subaddr, "Unexpected ECU subaddress") @parameterized.expand(FW_VERSIONS.items()) - def test_fw_versions(self, car_model: str, fw_versions: Dict[Tuple[capnp.lib.capnp._EnumModule, int, Optional[int]], Iterable[bytes]]): + def test_fw_versions(self, car_model: str, fw_versions: dict[tuple[capnp.lib.capnp._EnumModule, int, int | None], Iterable[bytes]]): for (ecu, addr, subaddr), fws in fw_versions.items(): self.assertIn(ecu, ECU_FW_CORE, "Unexpected ECU") self.assertEqual(addr, ECU_ADDRESSES[ecu], "ECU address mismatch") diff --git a/selfdrive/car/ford/values.py b/selfdrive/car/ford/values.py index fa6adb51b6..c417e8939f 100644 --- a/selfdrive/car/ford/values.py +++ b/selfdrive/car/ford/values.py @@ -1,11 +1,9 @@ import re -from collections import defaultdict -from dataclasses import dataclass -from enum import Enum, StrEnum -from typing import Dict, List, Union +from dataclasses import dataclass, field +from enum import Enum from cereal import car -from openpilot.selfdrive.car import AngleRateLimit, dbc_dict +from openpilot.selfdrive.car import AngleRateLimit, CarSpecs, dbc_dict, DbcDict, PlatformConfig, Platforms from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -41,33 +39,11 @@ class CarControllerParams: pass -class CAR(StrEnum): - BRONCO_SPORT_MK1 = "FORD BRONCO SPORT 1ST GEN" - ESCAPE_MK4 = "FORD ESCAPE 4TH GEN" - EXPLORER_MK6 = "FORD EXPLORER 6TH GEN" - F_150_MK14 = "FORD F-150 14TH GEN" - FOCUS_MK4 = "FORD FOCUS 4TH GEN" - MAVERICK_MK1 = "FORD MAVERICK 1ST GEN" - F_150_LIGHTNING_MK1 = "FORD F-150 LIGHTNING 1ST GEN" - MUSTANG_MACH_E_MK1 = "FORD MUSTANG MACH-E 1ST GEN" - - -CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1, CAR.MUSTANG_MACH_E_MK1} - - class RADAR: DELPHI_ESR = 'ford_fusion_2018_adas' DELPHI_MRR = 'FORD_CADS' -DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("ford_lincoln_base_pt", RADAR.DELPHI_MRR)) - -# F-150 radar is not yet supported -DBC[CAR.F_150_MK14] = dbc_dict("ford_lincoln_base_pt", None) -DBC[CAR.F_150_LIGHTNING_MK1] = dbc_dict("ford_lincoln_base_pt", None) -DBC[CAR.MUSTANG_MACH_E_MK1] = dbc_dict("ford_lincoln_base_pt", None) - - class Footnote(Enum): FOCUS = CarFootnote( "Refers only to the Focus Mk4 (C519) available in Europe/China/Taiwan/Australasia, not the Focus Mk3 (C346) in " + @@ -88,25 +64,82 @@ class FordCarInfo(CarInfo): self.car_parts = CarParts([Device.threex, harness]) -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-22"), - FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"), - ], - CAR.EXPLORER_MK6: [ - FordCarInfo("Ford Explorer 2020-23"), - FordCarInfo("Lincoln Aviator 2020-21", "Co-Pilot360 Plus"), - ], - CAR.F_150_MK14: FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), - CAR.F_150_LIGHTNING_MK1: FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"), - CAR.MUSTANG_MACH_E_MK1: FordCarInfo("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0"), - CAR.FOCUS_MK4: FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), - CAR.MAVERICK_MK1: [ - FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"), - FordCarInfo("Ford Maverick 2023", "Co-Pilot360 Assist"), - ], -} +@dataclass +class FordPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('ford_lincoln_base_pt', RADAR.DELPHI_MRR)) + + +class CAR(Platforms): + BRONCO_SPORT_MK1 = FordPlatformConfig( + "FORD BRONCO SPORT 1ST GEN", + FordCarInfo("Ford Bronco Sport 2021-22"), + specs=CarSpecs(mass=1625, wheelbase=2.67, steerRatio=17.7), + ) + ESCAPE_MK4 = FordPlatformConfig( + "FORD ESCAPE 4TH GEN", + [ + FordCarInfo("Ford Escape 2020-22"), + FordCarInfo("Ford Escape Hybrid 2020-22"), + FordCarInfo("Ford Escape Plug-in Hybrid 2020-22"), + FordCarInfo("Ford Kuga 2020-22", "Adaptive Cruise Control with Lane Centering"), + FordCarInfo("Ford Kuga Hybrid 2020-22", "Adaptive Cruise Control with Lane Centering"), + FordCarInfo("Ford Kuga Plug-in Hybrid 2020-22", "Adaptive Cruise Control with Lane Centering"), + ], + specs=CarSpecs(mass=1750, wheelbase=2.71, steerRatio=16.7), + ) + EXPLORER_MK6 = FordPlatformConfig( + "FORD EXPLORER 6TH GEN", + [ + FordCarInfo("Ford Explorer 2020-23"), + FordCarInfo("Ford Explorer Hybrid 2020-23"), # Limited and Platinum only + FordCarInfo("Lincoln Aviator 2020-23", "Co-Pilot360 Plus"), + FordCarInfo("Lincoln Aviator Plug-in Hybrid 2020-23", "Co-Pilot360 Plus"), # Grand Touring only + ], + specs=CarSpecs(mass=2050, wheelbase=3.025, steerRatio=16.8), + ) + F_150_MK14 = FordPlatformConfig( + "FORD F-150 14TH GEN", + [ + FordCarInfo("Ford F-150 2023", "Co-Pilot360 Active 2.0"), + FordCarInfo("Ford F-150 Hybrid 2023", "Co-Pilot360 Active 2.0"), + ], + dbc_dict=dbc_dict('ford_lincoln_base_pt', None), + specs=CarSpecs(mass=2000, wheelbase=3.69, steerRatio=17.0), + ) + F_150_LIGHTNING_MK1 = FordPlatformConfig( + "FORD F-150 LIGHTNING 1ST GEN", + FordCarInfo("Ford F-150 Lightning 2021-23", "Co-Pilot360 Active 2.0"), + dbc_dict=dbc_dict('ford_lincoln_base_pt', None), + specs=CarSpecs(mass=2948, wheelbase=3.70, steerRatio=16.9), + ) + FOCUS_MK4 = FordPlatformConfig( + "FORD FOCUS 4TH GEN", + [ + FordCarInfo("Ford Focus 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), + FordCarInfo("Ford Focus Hybrid 2018", "Adaptive Cruise Control with Lane Centering", footnotes=[Footnote.FOCUS]), # mHEV only + ], + specs=CarSpecs(mass=1350, wheelbase=2.7, steerRatio=15.0), + ) + MAVERICK_MK1 = FordPlatformConfig( + "FORD MAVERICK 1ST GEN", + [ + FordCarInfo("Ford Maverick 2022", "LARIAT Luxury"), + FordCarInfo("Ford Maverick Hybrid 2022", "LARIAT Luxury"), + FordCarInfo("Ford Maverick 2023", "Co-Pilot360 Assist"), + FordCarInfo("Ford Maverick Hybrid 2023", "Co-Pilot360 Assist"), + ], + specs=CarSpecs(mass=1650, wheelbase=3.076, steerRatio=17.0), + ) + MUSTANG_MACH_E_MK1 = FordPlatformConfig( + "FORD MUSTANG MACH-E 1ST GEN", + FordCarInfo("Ford Mustang Mach-E 2021-23", "Co-Pilot360 Active 2.0"), + dbc_dict=dbc_dict('ford_lincoln_base_pt', None), + specs=CarSpecs(mass=2200, wheelbase=2.984, steerRatio=17.0), # TODO: check steer ratio + ) + + +CANFD_CAR = {CAR.F_150_MK14, CAR.F_150_LIGHTNING_MK1, CAR.MUSTANG_MACH_E_MK1} + # FW response contains a combined software and part number @@ -184,6 +217,11 @@ FW_QUERY_CONFIG = FwQueryConfig( requests=[ # CAN and CAN FD queries are combined. # FIXME: For CAN FD, ECUs respond with frames larger than 8 bytes on the powertrain bus + Request( + [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], + [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], + logging=True, + ), Request( [StdQueries.TESTER_PRESENT_REQUEST, StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.TESTER_PRESENT_RESPONSE, StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], @@ -192,9 +230,13 @@ FW_QUERY_CONFIG = FwQueryConfig( ), ], extra_ecus=[ + # We are unlikely to get a response from the PCM from behind the gateway (Ecu.engine, 0x7e0, None), (Ecu.shiftByWire, 0x732, None), ], # Custom fuzzy fingerprinting function using platform codes, part numbers and software versions match_fw_to_car_fuzzy=match_fw_to_car_fuzzy, ) + +CAR_INFO = CAR.create_carinfo_map() +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/fw_query_definitions.py b/selfdrive/car/fw_query_definitions.py index e069fad13b..80492b4177 100755 --- a/selfdrive/car/fw_query_definitions.py +++ b/selfdrive/car/fw_query_definitions.py @@ -3,18 +3,21 @@ import capnp import copy from dataclasses import dataclass, field import struct -from typing import Callable, Dict, List, Optional, Set, Tuple +from collections.abc import Callable import panda.python.uds as uds -AddrType = Tuple[int, Optional[int]] -EcuAddrBusType = Tuple[int, Optional[int], int] -EcuAddrSubAddr = Tuple[int, int, Optional[int]] +AddrType = tuple[int, int | None] +EcuAddrBusType = tuple[int, int | None, int] +EcuAddrSubAddr = tuple[int, int, int | None] -LiveFwVersions = Dict[AddrType, Set[bytes]] -OfflineFwVersions = Dict[str, Dict[EcuAddrSubAddr, List[bytes]]] +LiveFwVersions = dict[AddrType, set[bytes]] +OfflineFwVersions = dict[str, dict[EcuAddrSubAddr, list[bytes]]] -STANDARD_VIN_ADDRS = [0x7e0, 0x7e2, 0x18da10f1, 0x18da0ef1] # engine, VMCU, 29-bit engine, PGM-FI +# A global list of addresses we will only ever consider for VIN responses +# engine, hybrid controller, Ford abs, Hyundai CAN FD cluster, 29-bit engine, PGM-FI +# TODO: move these to each brand's FW query config +STANDARD_VIN_ADDRS = [0x7e0, 0x7e2, 0x760, 0x7c6, 0x18da10f1, 0x18da0ef1] def p16(val): @@ -62,12 +65,15 @@ class StdQueries: GM_VIN_REQUEST = b'\x1a\x90' GM_VIN_RESPONSE = b'\x5a\x90' + KWP_VIN_REQUEST = b'\x21\x81' + KWP_VIN_RESPONSE = b'\x61\x81' + @dataclass class Request: - request: List[bytes] - response: List[bytes] - whitelist_ecus: List[int] = field(default_factory=list) + request: list[bytes] + response: list[bytes] + 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) @@ -80,15 +86,15 @@ class Request: @dataclass class FwQueryConfig: - requests: List[Request] + 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) + 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) + extra_ecus: list[tuple[capnp.lib.capnp._EnumModule, int, int | None]] = field(default_factory=list) # Function a brand can implement to provide better fuzzy matching. Takes in FW versions, # returns set of candidates. Only will match if one candidate is returned - match_fw_to_car_fuzzy: Optional[Callable[[LiveFwVersions, OfflineFwVersions], Set[str]]] = None + match_fw_to_car_fuzzy: Callable[[LiveFwVersions, OfflineFwVersions], set[str]] | None = None def __post_init__(self): for i in range(len(self.requests)): @@ -97,15 +103,12 @@ class FwQueryConfig: new_request.bus += 4 self.requests.append(new_request) - def get_all_ecus(self, offline_fw_versions: OfflineFwVersions, include_ecu_type: bool = True, - include_extra_ecus: bool = True) -> set[EcuAddrSubAddr] | set[AddrType]: + def get_all_ecus(self, offline_fw_versions: OfflineFwVersions, + include_extra_ecus: bool = True) -> set[EcuAddrSubAddr]: # Add ecus in database + extra ecus brand_ecus = {ecu for ecus in offline_fw_versions.values() for ecu in ecus} if include_extra_ecus: brand_ecus |= set(self.extra_ecus) - if not include_ecu_type: - return {(addr, subaddr) for _, addr, subaddr in brand_ecus} - return brand_ecus diff --git a/selfdrive/car/fw_versions.py b/selfdrive/car/fw_versions.py index c9ca249ceb..7673814195 100755 --- a/selfdrive/car/fw_versions.py +++ b/selfdrive/car/fw_versions.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 from collections import defaultdict -from typing import Any, DefaultDict, Dict, List, Optional, Set +from typing import Any, TypeVar +from collections.abc import Iterator from tqdm import tqdm import capnp @@ -8,7 +9,7 @@ import panda.python.uds as uds from cereal import car from openpilot.common.params import Params from openpilot.selfdrive.car.ecu_addrs import get_ecu_addrs -from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType +from openpilot.selfdrive.car.fw_query_definitions import AddrType, EcuAddrBusType, FwQueryConfig from openpilot.selfdrive.car.interfaces import get_interface_attr from openpilot.selfdrive.car.fingerprints import FW_VERSIONS from openpilot.selfdrive.car.isotp_parallel_query import IsoTpParallelQuery @@ -18,26 +19,28 @@ Ecu = car.CarParams.Ecu ESSENTIAL_ECUS = [Ecu.engine, Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.vsa] FUZZY_EXCLUDE_ECUS = [Ecu.fwdCamera, Ecu.fwdRadar, Ecu.eps, Ecu.debug] -FW_QUERY_CONFIGS = get_interface_attr('FW_QUERY_CONFIG', ignore_none=True) +FW_QUERY_CONFIGS: dict[str, FwQueryConfig] = 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, config, r) for brand, config in FW_QUERY_CONFIGS.items() for r in config.requests] +T = TypeVar('T') -def chunks(l, n=128): + +def chunks(l: list[T], n: int = 128) -> Iterator[list[T]]: for i in range(0, len(l), n): yield l[i:i + n] -def is_brand(brand: str, filter_brand: Optional[str]) -> bool: +def is_brand(brand: str, filter_brand: str | None) -> bool: """Returns if brand matches filter_brand or no brand filter is specified""" return filter_brand is None or brand == filter_brand -def build_fw_dict(fw_versions: List[capnp.lib.capnp._DynamicStructBuilder], - filter_brand: Optional[str] = None) -> Dict[AddrType, Set[bytes]]: - fw_versions_dict: DefaultDict[AddrType, Set[bytes]] = defaultdict(set) +def build_fw_dict(fw_versions: list[capnp.lib.capnp._DynamicStructBuilder], + filter_brand: str = None) -> dict[AddrType, set[bytes]]: + fw_versions_dict: defaultdict[AddrType, set[bytes]] = defaultdict(set) for fw in fw_versions: if is_brand(fw.brand, filter_brand) and not fw.logging: sub_addr = fw.subAddress if fw.subAddress != 0 else None @@ -95,7 +98,7 @@ def match_fw_to_car_fuzzy(live_fw_versions, match_brand=None, log=True, exclude= return set() -def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw_versions=None) -> Set[str]: +def match_fw_to_car_exact(live_fw_versions, match_brand=None, log=True, extra_fw_versions=None) -> 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 @@ -162,11 +165,11 @@ def match_fw_to_car(fw_versions, allow_exact=True, allow_fuzzy=True, log=True): return True, set() -def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: +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: []} + queries: dict[bool, list[list[EcuAddrBusType]]] = {True: [], False: []} + parallel_queries: dict[bool, list[EcuAddrBusType]] = {True: [], False: []} responses = set() for brand, config, r in REQUESTS: @@ -201,12 +204,12 @@ def get_present_ecus(logcan, sendcan, num_pandas=1) -> Set[EcuAddrBusType]: return ecu_responses -def get_brand_ecu_matches(ecu_rx_addrs): +def get_brand_ecu_matches(ecu_rx_addrs: set[EcuAddrBusType]) -> dict[str, set[AddrType]]: """Returns dictionary of brands and matches with ECUs in their FW versions""" - brand_addrs = {brand: config.get_all_ecus(VERSIONS[brand], include_ecu_type=False) for + brand_addrs = {brand: {(addr, subaddr) for _, addr, subaddr in config.get_all_ecus(VERSIONS[brand])} for brand, config in FW_QUERY_CONFIGS.items()} - brand_matches = {brand: set() for brand, _, _ in REQUESTS} + brand_matches: dict[str, set[AddrType]] = {brand: set() for brand, _, _ in REQUESTS} brand_rx_offsets = {(brand, r.rx_offset) for brand, _, r in REQUESTS} for addr, sub_addr, _ in ecu_rx_addrs: @@ -229,7 +232,7 @@ def set_obd_multiplexing(params: Params, obd_multiplexing: bool): def get_fw_versions_ordered(logcan, sendcan, ecu_rx_addrs, timeout=0.1, num_pandas=1, debug=False, progress=False) -> \ - List[capnp.lib.capnp._DynamicStructBuilder]: + list[capnp.lib.capnp._DynamicStructBuilder]: """Queries for FW versions ordering brands by likelihood, breaks when exact match is found""" all_car_fw = [] @@ -252,7 +255,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) -> \ - List[capnp.lib.capnp._DynamicStructBuilder]: + list[capnp.lib.capnp._DynamicStructBuilder]: versions = VERSIONS.copy() params = Params() @@ -287,8 +290,8 @@ 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, config, r) for brand, config, r in REQUESTS if is_brand(brand, query_brand)] - for addr in tqdm(addrs, disable=not progress): - for addr_chunk in chunks(addr): + for addr_group in tqdm(addrs, disable=not progress): # split by subaddr, if any + for addr_chunk in chunks(addr_group): for brand, config, r in requests: # Skip query if no panda available if r.bus > num_pandas * 4 - 1: diff --git a/selfdrive/car/gm/carstate.py b/selfdrive/car/gm/carstate.py index f7309939b1..c3d061de78 100644 --- a/selfdrive/car/gm/carstate.py +++ b/selfdrive/car/gm/carstate.py @@ -33,7 +33,9 @@ class CarState(CarStateBase): 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 + # This is to avoid a fault where you engage while still moving backwards after shifting to D. + # An Equinox has been seen with an unsupported status (3), so only check if either wheel is in reverse (2) + self.moving_backward = (pt_cp.vl["EBCMWheelSpdRear"]["RLWheelDir"] == 2) or (pt_cp.vl["EBCMWheelSpdRear"]["RRWheelDir"] == 2) # Variables used for avoiding LKAS faults self.loopback_lka_steering_cmd_updated = len(loopback_cp.vl_all["ASCMLKASteeringCmd"]["RollingCounter"]) > 0 diff --git a/selfdrive/car/gm/fingerprints.py b/selfdrive/car/gm/fingerprints.py index c349ee2856..73a205a250 100644 --- a/selfdrive/car/gm/fingerprints.py +++ b/selfdrive/car/gm/fingerprints.py @@ -2,6 +2,7 @@ from openpilot.selfdrive.car.gm.values import CAR # Trailblazer also matches as a SILVERADO, TODO: split with fw versions +# FIXME: There are Equinox users with different message lengths, specifically 304 and 320 FINGERPRINTS = { @@ -52,6 +53,9 @@ FINGERPRINTS = { }], CAR.EQUINOX: [{ 190: 6, 193: 8, 197: 8, 201: 8, 209: 7, 211: 2, 241: 6, 249: 8, 257: 8, 288: 5, 289: 8, 298: 8, 304: 1, 309: 8, 311: 8, 313: 8, 320: 3, 328: 1, 352: 5, 381: 8, 384: 4, 386: 8, 388: 8, 413: 8, 451: 8, 452: 8, 453: 6, 455: 7, 463: 3, 479: 3, 481: 7, 485: 8, 489: 8, 497: 8, 500: 6, 501: 8, 510: 8, 528: 5, 532: 6, 560: 8, 562: 8, 563: 5, 565: 5, 587: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 707: 8, 715: 8, 717: 5, 753: 5, 761: 7, 789: 5, 800: 6, 810: 8, 840: 5, 842: 5, 844: 8, 869: 4, 880: 6, 977: 8, 1001: 8, 1011: 6, 1017: 8, 1020: 8, 1033: 7, 1034: 7, 1217: 8, 1221: 5, 1233: 8, 1249: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1271: 8, 1280: 4, 1296: 4, 1300: 8, 1611: 8, 1930: 7 + }, + { + 190: 6, 201: 8, 211: 2, 717: 5, 241: 6, 451: 8, 298: 8, 452: 8, 453: 6, 479: 3, 485: 8, 249: 8, 500: 6, 587: 8, 1611: 8, 289: 8, 481: 7, 193: 8, 197: 8, 209: 7, 455: 7, 489: 8, 309: 8, 413: 8, 501: 8, 608: 8, 609: 6, 610: 6, 611: 6, 612: 8, 613: 8, 311: 8, 510: 8, 528: 5, 532: 6, 715: 8, 560: 8, 562: 8, 707: 8, 789: 5, 869: 4, 880: 6, 761: 7, 840: 5, 842: 5, 844: 8, 313: 8, 381: 8, 386: 8, 810: 8, 322: 7, 384: 4, 800: 6, 1033: 7, 1034: 7, 1296: 4, 753: 5, 388: 8, 288: 5, 497: 8, 463: 3, 304: 3, 977: 8, 1001: 8, 1280: 4, 320: 4, 352: 5, 563: 5, 565: 5, 1221: 5, 1011: 6, 1017: 8, 1020: 8, 1249: 8, 1300: 8, 328: 1, 1217: 8, 1233: 8, 1259: 8, 1261: 7, 1263: 4, 1265: 8, 1267: 1, 1930: 7, 1271: 8 }], } diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index af34100d74..e0dde4d0e9 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -1,13 +1,15 @@ #!/usr/bin/env python3 +import os from cereal import car from math import fabs, exp from panda import Panda +from openpilot.common.basedir import BASEDIR from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.gm.radar_interface import RADAR_HEADER_MSG from openpilot.selfdrive.car.gm.values import CAR, CruiseButtons, CarControllerParams, EV_CAR, CAMERA_ACC_CAR, CanBus -from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD +from openpilot.selfdrive.car.interfaces import CarInterfaceBase, TorqueFromLateralAccelCallbackType, FRICTION_THRESHOLD, LatControlInputs, NanoFFModel from openpilot.selfdrive.controls.lib.drive_helpers import get_friction ButtonType = car.CarState.ButtonEvent.Type @@ -25,6 +27,8 @@ NON_LINEAR_TORQUE_PARAMS = { CAR.SILVERADO: [3.29974374, 1.0, 0.25571356, 0.0465122] } +NEURAL_PARAMS_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/neural_ff_weights.json') + class CarInterface(CarInterfaceBase): @staticmethod @@ -44,8 +48,8 @@ class CarInterface(CarInterfaceBase): else: return CarInterfaceBase.get_steer_feedforward_default - def torque_from_lateral_accel_siglin(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, - lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: + def torque_from_lateral_accel_siglin(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float, + lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) def sig(val): @@ -58,11 +62,22 @@ class CarInterface(CarInterfaceBase): non_linear_torque_params = NON_LINEAR_TORQUE_PARAMS.get(self.CP.carFingerprint) assert non_linear_torque_params, "The params are not defined" a, b, c, _ = non_linear_torque_params - steer_torque = (sig(lateral_accel_value * a) * b) + (lateral_accel_value * c) + steer_torque = (sig(latcontrol_inputs.lateral_acceleration * a) * b) + (latcontrol_inputs.lateral_acceleration * c) return float(steer_torque) + friction + def torque_from_lateral_accel_neural(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, lateral_accel_error: float, + lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: + friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) + inputs = list(latcontrol_inputs) + if gravity_adjusted: + inputs[0] += inputs[1] + return float(self.neural_ff_model.predict(inputs)) + friction + def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: - if self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: + if self.CP.carFingerprint == CAR.BOLT_EUV: + self.neural_ff_model = NanoFFModel(NEURAL_PARAMS_PATH, self.CP.carFingerprint) + return self.torque_from_lateral_accel_neural + elif self.CP.carFingerprint in NON_LINEAR_TORQUE_PARAMS: return self.torque_from_lateral_accel_siglin else: return self.torque_from_lateral_accel_linear @@ -122,7 +137,7 @@ class CarInterface(CarInterfaceBase): # 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} or \ + ret.dashcamOnly = candidate in {CAR.CADILLAC_ATS, CAR.HOLDEN_ASTRA, CAR.MALIBU, CAR.BUICK_REGAL} 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. @@ -137,11 +152,7 @@ class CarInterface(CarInterfaceBase): ret.longitudinalActuatorDelayUpperBound = 0.5 # large delay to initially start braking if candidate == CAR.VOLT: - ret.mass = 1607. - ret.wheelbase = 2.69 - ret.steerRatio = 17.7 # Stock 15.7, LiveParameters ret.tireStiffnessFactor = 0.469 # Stock Michelin Energy Saver A/S, LiveParameters - ret.centerToFront = ret.wheelbase * 0.45 # Volt Gen 1, TODO corner weigh ret.lateralTuning.pid.kpBP = [0., 40.] ret.lateralTuning.pid.kpV = [0., 0.17] @@ -150,61 +161,20 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kf = 1. # get_steer_feedforward_volt() ret.steerActuatorDelay = 0.2 - elif candidate == CAR.MALIBU: - ret.mass = 1496. - ret.wheelbase = 2.83 - ret.steerRatio = 15.8 - ret.centerToFront = ret.wheelbase * 0.4 # wild guess - - elif candidate == CAR.HOLDEN_ASTRA: - ret.mass = 1363. - ret.wheelbase = 2.662 - # Remaining parameters copied from Volt for now - ret.centerToFront = ret.wheelbase * 0.4 - ret.steerRatio = 15.7 - elif candidate == CAR.ACADIA: ret.minEnableSpeed = -1. # engage speed is decided by pcm - ret.mass = 4353. * CV.LB_TO_KG - ret.wheelbase = 2.86 - ret.steerRatio = 14.4 # end to end is 13.46 - ret.centerToFront = ret.wheelbase * 0.4 ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.BUICK_LACROSSE: - ret.mass = 1712. - 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 # (3849+3708)/2 - ret.wheelbase = 2.83 # 111.4 inches in meters - ret.steerRatio = 14.4 # guess for tourx - ret.centerToFront = ret.wheelbase * 0.4 # guess for tourx - - elif candidate == CAR.CADILLAC_ATS: - ret.mass = 1601. - ret.wheelbase = 2.78 - 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 # (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 in (CAR.ESCALADE_ESV, CAR.ESCALADE_ESV_2019): ret.minEnableSpeed = -1. # engage speed is decided by pcm - ret.mass = 2739. - ret.wheelbase = 3.302 - ret.steerRatio = 17.3 - ret.centerToFront = ret.wheelbase * 0.5 ret.tireStiffnessFactor = 1.0 if candidate == CAR.ESCALADE_ESV: @@ -216,19 +186,11 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.BOLT_EUV: - ret.mass = 1669. - ret.wheelbase = 2.63779 - ret.steerRatio = 16.8 - ret.centerToFront = ret.wheelbase * 0.4 ret.tireStiffnessFactor = 1.0 ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.SILVERADO: - ret.mass = 2450. - ret.wheelbase = 3.75 - ret.steerRatio = 16.3 - ret.centerToFront = ret.wheelbase * 0.5 ret.tireStiffnessFactor = 1.0 # On the Bolt, the ECM and camera independently check that you are either above 5 kph or at a stop # with foot on brake to allow engagement, but this platform only has that check in the camera. @@ -238,17 +200,9 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.EQUINOX: - ret.mass = 3500. * CV.LB_TO_KG - ret.wheelbase = 2.72 - ret.steerRatio = 14.4 - ret.centerToFront = ret.wheelbase * 0.4 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) elif candidate == CAR.TRAILBLAZER: - ret.mass = 1345. - ret.wheelbase = 2.64 - ret.steerRatio = 16.8 - ret.centerToFront = ret.wheelbase * 0.4 ret.tireStiffnessFactor = 1.0 ret.steerActuatorDelay = 0.2 CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) diff --git a/selfdrive/car/gm/values.py b/selfdrive/car/gm/values.py index 8188ad4e6e..53dbde87f4 100644 --- a/selfdrive/car/gm/values.py +++ b/selfdrive/car/gm/values.py @@ -1,10 +1,8 @@ -from collections import defaultdict -from dataclasses import dataclass -from enum import Enum, StrEnum -from typing import Dict, List, Union +from dataclasses import dataclass, field +from enum import Enum from cereal import car -from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car import dbc_dict, PlatformConfig, DbcDict, Platforms, CarSpecs from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries @@ -62,23 +60,6 @@ class CarControllerParams: self.BRAKE_LOOKUP_V = [self.MAX_BRAKE, 0.] -class CAR(StrEnum): - HOLDEN_ASTRA = "HOLDEN ASTRA RS-V BK 2017" - VOLT = "CHEVROLET VOLT PREMIER 2017" - 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" - ESCALADE_ESV_2019 = "CADILLAC ESCALADE ESV 2019" - BOLT_EUV = "CHEVROLET BOLT EUV 2022" - SILVERADO = "CHEVROLET SILVERADO 1500 2020" - EQUINOX = "CHEVROLET EQUINOX 2019" - TRAILBLAZER = "CHEVROLET TRAILBLAZER 2021" - - class Footnote(Enum): OBD_II = CarFootnote( 'Requires a community built ASCM harness. ' + @@ -98,28 +79,88 @@ class GMCarInfo(CarInfo): 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, 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.ESCALADE_ESV_2019: GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"), - CAR.BOLT_EUV: [ - GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"), - GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), - ], - CAR.SILVERADO: [ - GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), - GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", video_link="https://youtu.be/5HbNoBLzRwE"), - ], - CAR.EQUINOX: GMCarInfo("Chevrolet Equinox 2019-22"), - CAR.TRAILBLAZER: GMCarInfo("Chevrolet Trailblazer 2021-22"), -} +@dataclass +class GMPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('gm_global_a_powertrain_generated', 'gm_global_a_object', chassis_dbc='gm_global_a_chassis')) + + +class CAR(Platforms): + HOLDEN_ASTRA = GMPlatformConfig( + "HOLDEN ASTRA RS-V BK 2017", + GMCarInfo("Holden Astra 2017"), + specs=CarSpecs(mass=1363, wheelbase=2.662, steerRatio=15.7, centerToFrontRatio=0.4), + ) + VOLT = GMPlatformConfig( + "CHEVROLET VOLT PREMIER 2017", + GMCarInfo("Chevrolet Volt 2017-18", min_enable_speed=0, video_link="https://youtu.be/QeMCN_4TFfQ"), + specs=CarSpecs(mass=1607, wheelbase=2.69, steerRatio=17.7, centerToFrontRatio=0.45), + ) + CADILLAC_ATS = GMPlatformConfig( + "CADILLAC ATS Premium Performance 2018", + GMCarInfo("Cadillac ATS Premium Performance 2018"), + specs=CarSpecs(mass=1601, wheelbase=2.78, steerRatio=15.3), + ) + MALIBU = GMPlatformConfig( + "CHEVROLET MALIBU PREMIER 2017", + GMCarInfo("Chevrolet Malibu Premier 2017"), + specs=CarSpecs(mass=1496, wheelbase=2.83, steerRatio=15.8, centerToFrontRatio=0.4), + ) + ACADIA = GMPlatformConfig( + "GMC ACADIA DENALI 2018", + GMCarInfo("GMC Acadia 2018", video_link="https://www.youtube.com/watch?v=0ZN6DdsBUZo"), + specs=CarSpecs(mass=1975, wheelbase=2.86, steerRatio=14.4, centerToFrontRatio=0.4), + ) + BUICK_LACROSSE = GMPlatformConfig( + "BUICK LACROSSE 2017", + GMCarInfo("Buick LaCrosse 2017-19", "Driver Confidence Package 2"), + specs=CarSpecs(mass=1712, wheelbase=2.91, steerRatio=15.8, centerToFrontRatio=0.4), + ) + BUICK_REGAL = GMPlatformConfig( + "BUICK REGAL ESSENCE 2018", + GMCarInfo("Buick Regal Essence 2018"), + specs=CarSpecs(mass=1714, wheelbase=2.83, steerRatio=14.4, centerToFrontRatio=0.4), + ) + ESCALADE = GMPlatformConfig( + "CADILLAC ESCALADE 2017", + GMCarInfo("Cadillac Escalade 2017", "Driver Assist Package"), + specs=CarSpecs(mass=2564, wheelbase=2.95, steerRatio=17.3), + ) + ESCALADE_ESV = GMPlatformConfig( + "CADILLAC ESCALADE ESV 2016", + GMCarInfo("Cadillac Escalade ESV 2016", "Adaptive Cruise Control (ACC) & LKAS"), + specs=CarSpecs(mass=2739, wheelbase=3.302, steerRatio=17.3), + ) + ESCALADE_ESV_2019 = GMPlatformConfig( + "CADILLAC ESCALADE ESV 2019", + GMCarInfo("Cadillac Escalade ESV 2019", "Adaptive Cruise Control (ACC) & LKAS"), + specs=ESCALADE_ESV.specs, + ) + BOLT_EUV = GMPlatformConfig( + "CHEVROLET BOLT EUV 2022", + [ + GMCarInfo("Chevrolet Bolt EUV 2022-23", "Premier or Premier Redline Trim without Super Cruise Package", video_link="https://youtu.be/xvwzGMUA210"), + GMCarInfo("Chevrolet Bolt EV 2022-23", "2LT Trim with Adaptive Cruise Control Package"), + ], + specs=CarSpecs(mass=1669, wheelbase=2.63779, steerRatio=16.8, centerToFrontRatio=0.4), + ) + SILVERADO = GMPlatformConfig( + "CHEVROLET SILVERADO 1500 2020", + [ + GMCarInfo("Chevrolet Silverado 1500 2020-21", "Safety Package II"), + GMCarInfo("GMC Sierra 1500 2020-21", "Driver Alert Package II", video_link="https://youtu.be/5HbNoBLzRwE"), + ], + specs=CarSpecs(mass=2450, wheelbase=3.75, steerRatio=16.3), + ) + EQUINOX = GMPlatformConfig( + "CHEVROLET EQUINOX 2019", + GMCarInfo("Chevrolet Equinox 2019-22"), + specs=CarSpecs(mass=1588, wheelbase=2.72, steerRatio=14.4, centerToFrontRatio=0.4), + ) + TRAILBLAZER = GMPlatformConfig( + "CHEVROLET TRAILBLAZER 2021", + GMCarInfo("Chevrolet Trailblazer 2021-22"), + specs=CarSpecs(mass=1345, wheelbase=2.64, steerRatio=16.8, centerToFrontRatio=0.4), + ) class CruiseButtons: @@ -181,11 +222,12 @@ FW_QUERY_CONFIG = FwQueryConfig( extra_ecus=[(Ecu.fwdCamera, 0x24b, None)], ) -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_EUV} # We're integrated at the camera with VOACC on these cars (instead of ASCM w/ OBD-II harness) CAMERA_ACC_CAR = {CAR.BOLT_EUV, CAR.SILVERADO, CAR.EQUINOX, CAR.TRAILBLAZER} STEER_THRESHOLD = 1.0 + +CAR_INFO = CAR.create_carinfo_map() +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/honda/carstate.py b/selfdrive/car/honda/carstate.py index 03aedb31d2..9025f72397 100644 --- a/selfdrive/car/honda/carstate.py +++ b/selfdrive/car/honda/carstate.py @@ -7,8 +7,8 @@ from opendbc.can.can_define import CANDefine from opendbc.can.parser import CANParser from openpilot.selfdrive.car.honda.hondacan import get_cruise_speed_conversion, get_pt_bus from openpilot.selfdrive.car.honda.values import CAR, DBC, STEER_THRESHOLD, HONDA_BOSCH, \ - HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_ALT_BRAKE_SIGNAL, \ - HONDA_BOSCH_RADARLESS + HONDA_NIDEC_ALT_SCM_MESSAGES, HONDA_BOSCH_RADARLESS, \ + HondaFlags from openpilot.selfdrive.car.interfaces import CarStateBase TransmissionType = car.CarParams.TransmissionType @@ -44,7 +44,7 @@ def get_can_messages(CP, gearbox_msg): else: messages.append((gearbox_msg, 100)) - if CP.carFingerprint in HONDA_BOSCH_ALT_BRAKE_SIGNAL: + if CP.flags & HondaFlags.BOSCH_ALT_BRAKE: messages.append(("BRAKE_MODULE", 50)) if CP.carFingerprint in (HONDA_BOSCH | {CAR.CIVIC, CAR.ODYSSEY, CAR.ODYSSEY_CHN}): @@ -217,7 +217,7 @@ class CarState(CarStateBase): else: ret.cruiseState.speed = cp.vl["CRUISE"]["CRUISE_SPEED_PCM"] * CV.KPH_TO_MS - if self.CP.carFingerprint in HONDA_BOSCH_ALT_BRAKE_SIGNAL: + if self.CP.flags & HondaFlags.BOSCH_ALT_BRAKE: ret.brakePressed = cp.vl["BRAKE_MODULE"]["BRAKE_PRESSED"] != 0 else: # brake switch has shown some single time step noise, so only considered when diff --git a/selfdrive/car/honda/fingerprints.py b/selfdrive/car/honda/fingerprints.py index 0a64a73d72..359ae83b15 100644 --- a/selfdrive/car/honda/fingerprints.py +++ b/selfdrive/car/honda/fingerprints.py @@ -101,10 +101,6 @@ FW_VERSIONS = { b'39990-TVA-X040\x00\x00', b'39990-TVE-H130\x00\x00', ], - (Ecu.unknown, 0x18da3af1, None): [ - b'39390-TVA-A020\x00\x00', - b'39390-TVA-A120\x00\x00', - ], (Ecu.srs, 0x18da53f1, None): [ b'77959-TBX-H230\x00\x00', b'77959-TVA-A460\x00\x00', @@ -299,6 +295,7 @@ FW_VERSIONS = { (Ecu.srs, 0x18da53f1, None): [ b'77959-TBA-A030\x00\x00', b'77959-TBA-A040\x00\x00', + b'77959-TBG-A020\x00\x00', b'77959-TBG-A030\x00\x00', b'77959-TEA-Q820\x00\x00', ], @@ -680,6 +677,7 @@ FW_VERSIONS = { b'36802-TLA-A040\x00\x00', b'36802-TLA-A050\x00\x00', b'36802-TLA-A060\x00\x00', + b'36802-TLA-A070\x00\x00', b'36802-TMC-Q040\x00\x00', b'36802-TMC-Q070\x00\x00', b'36802-TNY-A030\x00\x00', @@ -834,6 +832,7 @@ FW_VERSIONS = { (Ecu.programmedFuelInjection, 0x18da10f1, None): [ b'37805-5MR-3050\x00\x00', b'37805-5MR-3250\x00\x00', + b'37805-5MR-4070\x00\x00', b'37805-5MR-4080\x00\x00', b'37805-5MR-4180\x00\x00', b'37805-5MR-A240\x00\x00', @@ -879,6 +878,7 @@ FW_VERSIONS = { b'28102-5MX-A900\x00\x00', b'28102-5MX-A910\x00\x00', b'28102-5MX-C001\x00\x00', + b'28102-5MX-C910\x00\x00', b'28102-5MX-D001\x00\x00', b'28102-5MX-D710\x00\x00', b'28102-5MX-K610\x00\x00', @@ -916,6 +916,7 @@ FW_VERSIONS = { b'78109-THR-C320\x00\x00', b'78109-THR-C330\x00\x00', b'78109-THR-CE20\x00\x00', + b'78109-THR-CL10\x00\x00', b'78109-THR-DA20\x00\x00', b'78109-THR-DA30\x00\x00', b'78109-THR-DA40\x00\x00', @@ -985,6 +986,7 @@ FW_VERSIONS = { b'37805-RLV-F120\x00\x00', b'37805-RLV-L080\x00\x00', b'37805-RLV-L090\x00\x00', + b'37805-RLV-L150\x00\x00', b'37805-RLV-L160\x00\x00', b'37805-RLV-L180\x00\x00', b'37805-RLV-L350\x00\x00', @@ -1205,6 +1207,7 @@ FW_VERSIONS = { b'39990-T6Z-A020\x00\x00', b'39990-T6Z-A030\x00\x00', b'39990-T6Z-A050\x00\x00', + b'39990-T6Z-A110\x00\x00', ], (Ecu.fwdRadar, 0x18dab0f1, None): [ b'36161-T6Z-A020\x00\x00', @@ -1212,6 +1215,7 @@ FW_VERSIONS = { b'36161-T6Z-A420\x00\x00', b'36161-T6Z-A520\x00\x00', b'36161-T6Z-A620\x00\x00', + b'36161-T6Z-A720\x00\x00', b'36161-TJZ-A120\x00\x00', ], (Ecu.gateway, 0x18daeff1, None): [ @@ -1219,6 +1223,7 @@ FW_VERSIONS = { b'38897-T6Z-A110\x00\x00', ], (Ecu.combinationMeter, 0x18da60f1, None): [ + b'78108-T6Z-AF10\x00\x00', b'78109-T6Z-A420\x00\x00', b'78109-T6Z-A510\x00\x00', b'78109-T6Z-A710\x00\x00', @@ -1236,6 +1241,7 @@ FW_VERSIONS = { b'57114-T6Z-A120\x00\x00', b'57114-T6Z-A130\x00\x00', b'57114-T6Z-A520\x00\x00', + b'57114-T6Z-A610\x00\x00', b'57114-TJZ-A520\x00\x00', ], }, diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index 9f228cd8fb..153fa1e635 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -3,8 +3,9 @@ from cereal import car from panda import Panda from openpilot.common.conversions import Conversions as CV from openpilot.common.numpy_fast import interp +from openpilot.selfdrive.car.honda.hondacan import get_pt_bus from openpilot.selfdrive.car.honda.values import CarControllerParams, CruiseButtons, HondaFlags, CAR, HONDA_BOSCH, HONDA_NIDEC_ALT_SCM_MESSAGES, \ - HONDA_BOSCH_ALT_BRAKE_SIGNAL, HONDA_BOSCH_RADARLESS + HONDA_BOSCH_RADARLESS from openpilot.selfdrive.car import create_button_events, get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu @@ -275,7 +276,8 @@ class CarInterface(CarInterfaceBase): raise ValueError(f"unsupported car {candidate}") # These cars use alternate user brake msg (0x1BE) - if candidate in HONDA_BOSCH_ALT_BRAKE_SIGNAL: + if 0x1BE in fingerprint[get_pt_bus(candidate)] and candidate in HONDA_BOSCH: + ret.flags |= HondaFlags.BOSCH_ALT_BRAKE.value ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HONDA_ALT_BRAKE # These cars use alternate SCM messages (SCM_FEEDBACK AND SCM_BUTTON) diff --git a/selfdrive/car/honda/values.py b/selfdrive/car/honda/values.py index 1d4a174b9b..a2ef757d15 100644 --- a/selfdrive/car/honda/values.py +++ b/selfdrive/car/honda/values.py @@ -1,6 +1,5 @@ from dataclasses import dataclass from enum import Enum, IntFlag, StrEnum -from typing import Dict, List, Optional, Union from cereal import car from openpilot.common.conversions import Conversions as CV @@ -49,6 +48,7 @@ class CarControllerParams: class HondaFlags(IntFlag): # Bosch models with alternate set of LKAS_HUD messages BOSCH_EXT_HUD = 1 + BOSCH_ALT_BRAKE = 2 # Car button codes @@ -115,7 +115,7 @@ class HondaCarInfo(CarInfo): self.car_parts = CarParts.common([CarHarness.nidec]) -CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { +CAR_INFO: dict[str, HondaCarInfo | list[HondaCarInfo] | None] = { CAR.ACCORD: [ HondaCarInfo("Honda Accord 2018-22", "All", video_link="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), @@ -149,7 +149,7 @@ CAR_INFO: Dict[str, Optional[Union[HondaCarInfo, List[HondaCarInfo]]]] = { HondaCarInfo("Honda Pilot 2016-22", min_steer_speed=12. * CV.MPH_TO_MS), HondaCarInfo("Honda Passport 2019-23", "All", min_steer_speed=12. * CV.MPH_TO_MS), ], - CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-23", min_steer_speed=12. * CV.MPH_TO_MS), + CAR.RIDGELINE: HondaCarInfo("Honda Ridgeline 2017-24", 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), } @@ -195,10 +195,19 @@ FW_QUERY_CONFIG = FwQueryConfig( [StdQueries.UDS_VERSION_REQUEST], [StdQueries.UDS_VERSION_RESPONSE], bus=1, - logging=True, obd_multiplexing=False, ), ], + # We lose these ECUs without the comma power on these cars. + # Note that we still attempt to match with them when they are present + non_essential_ecus={ + Ecu.programmedFuelInjection: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + Ecu.transmission: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + Ecu.vsa: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + Ecu.combinationMeter: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + Ecu.gateway: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + Ecu.electricBrakeBooster: [CAR.CIVIC_BOSCH, CAR.CRV_5G], + }, extra_ecus=[ # The only other ECU on PT bus accessible by camera on radarless Civic (Ecu.unknown, 0x18DAB3F1, None), @@ -243,5 +252,4 @@ HONDA_NIDEC_ALT_SCM_MESSAGES = {CAR.ACURA_ILX, CAR.ACURA_RDX, CAR.CRV, CAR.CRV_E 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, CAR.HRV_3G} -HONDA_BOSCH_ALT_BRAKE_SIGNAL = {CAR.ACCORD, CAR.CRV_5G, CAR.ACURA_RDX_3G, CAR.HRV_3G} HONDA_BOSCH_RADARLESS = {CAR.CIVIC_2022, CAR.HRV_3G} diff --git a/selfdrive/car/hyundai/fingerprints.py b/selfdrive/car/hyundai/fingerprints.py index 35ee682b4b..d1fc1faabb 100644 --- a/selfdrive/car/hyundai/fingerprints.py +++ b/selfdrive/car/hyundai/fingerprints.py @@ -914,12 +914,14 @@ FW_VERSIONS = { (Ecu.transmission, 0x7e1, None): [ b'\xf1\x87VDGMD15352242DD3w\x87gxwvgv\x87wvw\x88wXwffVfffUfw\x88o\xff\x06J\xf1\x81E14\x00\x00\x00\x00\x00\x00\x00\xf1\x00bcshcm49 E14\x00\x00\x00\x00\x00\x00\x00SHI0G50NB1tc5\xb7', 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', + b'\xf1\x87VDHMD16446682DD3WwwxxvGw\x88\x88\x87\x88\x88whxx\x87\x87\x87\x85fUfwu_\xffT\xf8\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 ', ], (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2020 160302', + b'\xf1\x00HI LKAS AT USA LHD 1.00 1.00 95895-D2030 170208', ], (Ecu.engine, 0x7e0, None): [ b'\xf1\x810000000000\x00', @@ -971,6 +973,7 @@ FW_VERSIONS = { b'\xf1\x00BD MDPS C 1.00 1.02 56310-XX000 4BD2C102', b'\xf1\x00BD MDPS C 1.00 1.08 56310/M6300 4BDDC108', b'\xf1\x00BD MDPS C 1.00 1.08 56310M6300\x00 4BDDC108', + b'\xf1\x00BDm MDPS C A.01 1.01 56310M7800\x00 4BPMC101', b'\xf1\x00BDm MDPS C A.01 1.03 56310M7800\x00 4BPMC103', ], (Ecu.fwdCamera, 0x7c4, None): [ @@ -987,11 +990,13 @@ FW_VERSIONS = { b'\xf1\x81616F2051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7d1, None): [ + b'\xf1\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816VGRAH00018.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x8758900-M7AB0 \xf1\x816VQRAD00127.ELF\xf1\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006V2B0_C2\x00\x006V2C6051\x00\x00CBD0N20NL1\x00\x00\x00\x00', + b'\xf1\x006V2B0_C2\x00\x006V2C6051\x00\x00CBD0N20NL1\x90@\xc6\xae', b'\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\x00\x00\x00\x00', b"\xf1\x816U2VC051\x00\x00\xf1\x006U2V0_C2\x00\x006U2VC051\x00\x00DBD0T16SS0\xcf\x1e'\xc3", ], @@ -1198,16 +1203,19 @@ FW_VERSIONS = { }, CAR.KIA_NIRO_PHEV_2022: { (Ecu.engine, 0x7e0, None): [ + b'\xf1\x816H6G5051\x00\x00\x00\x00\x00\x00\x00\x00', b'\xf1\x816H6G6051\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', + b'\xf1\x816U3J9051\x00\x00\xf1\x006U3H1_C2\x00\x006U3J9051\x00\x00PDE0G16NL3\x00\x00\x00\x00', ], (Ecu.eps, 0x7d4, None): [ 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.00 99211-G5500 210428', + b'\xf1\x00DEP MFC AT USA LHD 1.00 1.06 99211-G5000 201028', ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00DEhe SCC F-CUP 1.00 1.00 99110-G5600 ', @@ -1347,22 +1355,28 @@ FW_VERSIONS = { CAR.ELANTRA_GT_I30: { (Ecu.fwdCamera, 0x7c4, None): [ b'\xf1\x00PD LKAS AT KOR LHD 1.00 1.02 95740-G3000 A51', + b'\xf1\x00PD LKAS AT USA LHD 1.00 1.02 95740-G3000 A51', b'\xf1\x00PD LKAS AT USA LHD 1.01 1.01 95740-G3100 A54', ], (Ecu.transmission, 0x7e1, None): [ b'\xf1\x006U2U0_C2\x00\x006U2T0051\x00\x00DPD0D16KS0u\xce\x1fk', + b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DPD0T16NS4\x00\x00\x00\x00', + b'\xf1\x006U2V0_C2\x00\x006U2V8051\x00\x00DPD0T16NS4\xda\x7f\xd6\xa7', b'\xf1\x006U2V0_C2\x00\x006U2VA051\x00\x00DPD0H16NS0e\x0e\xcd\x8e', ], (Ecu.eps, 0x7d4, None): [ b'\xf1\x00PD MDPS C 1.00 1.00 56310G3300\x00 4PDDC100', + b'\xf1\x00PD MDPS C 1.00 1.03 56310/G3300 4PDDC103', b'\xf1\x00PD MDPS C 1.00 1.04 56310/G3300 4PDDC104', ], (Ecu.abs, 0x7d1, None): [ b'\xf1\x00PD ESC \t 104\x18\t\x03 58920-G3350', + b'\xf1\x00PD ESC \x0b 103\x17\x110 58920-G3350', b'\xf1\x00PD ESC \x0b 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 F-CUP 1.01 1.00 96400-G3100 ', b'\xf1\x00PD__ SCC FNCUP 1.01 1.00 96400-G3000 ', ], }, @@ -1535,6 +1549,7 @@ FW_VERSIONS = { b'\xf1\x00NE1 MFC AT EUR LHD 1.00 1.06 99211-GI000 210813', b'\xf1\x00NE1 MFC AT EUR RHD 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 KOR LHD 1.00 1.00 99211-GI020 230719', b'\xf1\x00NE1 MFC AT KOR LHD 1.00 1.05 99211-GI010 220614', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.00 99211-GI020 230719', b'\xf1\x00NE1 MFC AT USA LHD 1.00 1.01 99211-GI010 211007', @@ -1555,6 +1570,7 @@ FW_VERSIONS = { }, CAR.TUCSON_4TH_GEN: { (Ecu.fwdCamera, 0x7c4, None): [ + b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 1.00 99211-N9220 14K', b'\xf1\x00NX4 FR_CMR AT EUR LHD 1.00 2.02 99211-N9000 14E', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9210 14G', b'\xf1\x00NX4 FR_CMR AT USA LHD 1.00 1.00 99211-N9220 14K', @@ -1567,6 +1583,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00NX4__ 1.00 1.00 99110-N9100 ', b'\xf1\x00NX4__ 1.00 1.01 99110-N9000 ', + b'\xf1\x00NX4__ 1.00 1.02 99110-N9000 ', b'\xf1\x00NX4__ 1.01 1.00 99110-N9100 ', ], }, @@ -1625,6 +1642,7 @@ FW_VERSIONS = { ], (Ecu.fwdRadar, 0x7d0, None): [ b'\xf1\x00MQ4_ SCC F-CUP 1.00 1.06 99110-P2000 ', + b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.00 99110-R5000 ', b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.06 99110-P2000 ', b'\xf1\x00MQ4_ SCC FHCUP 1.00 1.08 99110-P2000 ', ], diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index a3210f8071..049a63399c 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -11,7 +11,6 @@ from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.disable_ecu import disable_ecu Ecu = car.CarParams.Ecu -SafetyModel = car.CarParams.SafetyModel ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName ENABLE_BUTTONS = (Buttons.RES_ACCEL, Buttons.SET_DECEL, Buttons.CANCEL) @@ -19,17 +18,6 @@ BUTTONS_DICT = {Buttons.RES_ACCEL: ButtonType.accelCruise, Buttons.SET_DECEL: Bu Buttons.GAP_DIST: ButtonType.gapAdjustCruise, Buttons.CANCEL: ButtonType.cancel} -def set_safety_config_hyundai(candidate, CAN, can_fd=False): - platform = SafetyModel.hyundaiCanfd if can_fd else \ - SafetyModel.hyundaiLegacy if candidate in LEGACY_SAFETY_MODE_CAR else \ - SafetyModel.hyundai - cfgs = [get_safety_config(platform), ] - if CAN.ECAN >= 4: - cfgs.insert(0, get_safety_config(SafetyModel.noOutput)) - - return cfgs - - class CarInterface(CarInterfaceBase): @staticmethod def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): @@ -54,6 +42,7 @@ class CarInterface(CarInterfaceBase): # detect HDA2 with ADAS Driving ECU if hda2: + ret.flags |= HyundaiFlags.CANFD_HDA2.value if 0x110 in fingerprint[CAN.CAM]: ret.flags |= HyundaiFlags.CANFD_HDA2_ALT_STEERING.value else: @@ -303,20 +292,30 @@ class CarInterface(CarInterfaceBase): ret.enableBsm = 0x58b in fingerprint[0] # *** panda safety config *** - ret.safetyConfigs = set_safety_config_hyundai(candidate, CAN, can_fd=(candidate in CANFD_CAR)) - - if hda2: - ret.flags |= HyundaiFlags.CANFD_HDA2.value - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 - if candidate in CANFD_CAR: - if hda2 and ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING: - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING + cfgs = [get_safety_config(car.CarParams.SafetyModel.hyundaiCanfd), ] + if CAN.ECAN >= 4: + cfgs.insert(0, get_safety_config(car.CarParams.SafetyModel.noOutput)) + ret.safetyConfigs = cfgs + + if ret.flags & HyundaiFlags.CANFD_HDA2: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2 + if ret.flags & HyundaiFlags.CANFD_HDA2_ALT_STEERING: + ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CANFD_HDA2_ALT_STEERING 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: + 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)] + else: + ret.safetyConfigs = [get_safety_config(car.CarParams.SafetyModel.hyundai, 0)] + + if candidate in CAMERA_SCC_CAR: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC - if ret.flags & HyundaiFlags.CANFD_CAMERA_SCC or candidate in CAMERA_SCC_CAR: - ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_CAMERA_SCC if ret.openpilotLongitudinalControl: ret.safetyConfigs[-1].safetyParam |= Panda.FLAG_HYUNDAI_LONG if ret.flags & HyundaiFlags.HYBRID: diff --git a/selfdrive/car/hyundai/values.py b/selfdrive/car/hyundai/values.py index dfa4f70c2c..76ff0b39ce 100644 --- a/selfdrive/car/hyundai/values.py +++ b/selfdrive/car/hyundai/values.py @@ -1,7 +1,6 @@ import re from dataclasses import dataclass from enum import Enum, IntFlag, StrEnum -from typing import Dict, List, Optional, Set, Tuple, Union from cereal import car from panda.python import uds @@ -157,7 +156,7 @@ class HyundaiCarInfo(CarInfo): self.footnotes.insert(0, Footnote.CANFD) -CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { +CAR_INFO: dict[str, HyundaiCarInfo | list[HyundaiCarInfo] | None] = { CAR.AZERA_6TH_GEN: HyundaiCarInfo("Hyundai Azera 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_k])), CAR.AZERA_HEV_6TH_GEN: [ HyundaiCarInfo("Hyundai Azera Hybrid 2019", "All", car_parts=CarParts.common([CarHarness.hyundai_c])), @@ -248,7 +247,10 @@ CAR_INFO: Dict[str, Optional[Union[HyundaiCarInfo, List[HyundaiCarInfo]]]] = { HyundaiCarInfo("Kia Niro Plug-in Hybrid 2018-19", "All", min_enable_speed=10. * CV.MPH_TO_MS, car_parts=CarParts.common([CarHarness.hyundai_c])), HyundaiCarInfo("Kia Niro Plug-in Hybrid 2020", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), ], - CAR.KIA_NIRO_PHEV_2022: HyundaiCarInfo("Kia Niro Plug-in Hybrid 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), + CAR.KIA_NIRO_PHEV_2022: [ + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2021", "All", car_parts=CarParts.common([CarHarness.hyundai_d])), + HyundaiCarInfo("Kia Niro Plug-in Hybrid 2022", "All", car_parts=CarParts.common([CarHarness.hyundai_f])), + ], CAR.KIA_NIRO_HEV_2021: [ HyundaiCarInfo("Kia Niro Hybrid 2021", car_parts=CarParts.common([CarHarness.hyundai_d])), HyundaiCarInfo("Kia Niro Hybrid 2022", car_parts=CarParts.common([CarHarness.hyundai_f])), @@ -313,7 +315,7 @@ class Buttons: CANCEL = 4 # on newer models, this is a pause/resume button -def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[bytes]]]: +def get_platform_codes(fw_versions: list[bytes]) -> set[tuple[bytes, bytes | None]]: # Returns unique, platform-specific identification codes for a set of versions codes = set() # (code-Optional[part], date) for fw in fw_versions: @@ -332,12 +334,12 @@ def get_platform_codes(fw_versions: List[bytes]) -> Set[Tuple[bytes, Optional[by return codes -def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> Set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> set[str]: # Non-electric CAN FD platforms often do not have platform code specifiers needed # to distinguish between hybrid and ICE. All EVs so far are either exclusively # electric or specify electric in the platform code. fuzzy_platform_blacklist = {str(c) for c in (CANFD_CAR - EV_CAR - CANFD_FUZZY_WHITELIST)} - candidates: Set[str] = set() + candidates: set[str] = set() for candidate, fws in offline_fw_versions.items(): # Keep track of ECUs which pass all checks (platform codes, within date range) @@ -393,6 +395,9 @@ HYUNDAI_VERSION_REQUEST_MULTI = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER] p16(uds.DATA_IDENTIFIER_TYPE.APPLICATION_SOFTWARE_IDENTIFICATION) + \ p16(0xf100) +HYUNDAI_ECU_MANUFACTURING_DATE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER]) + \ + p16(uds.DATA_IDENTIFIER_TYPE.ECU_MANUFACTURING_DATE) + HYUNDAI_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + 0x40]) # Regex patterns for parsing platform code, FW date, and part number from FW versions @@ -411,6 +416,8 @@ PLATFORM_CODE_ECUS = [Ecu.fwdRadar, Ecu.fwdCamera, Ecu.eps] # TODO: there are date codes in the ABS firmware versions in hex DATE_FW_ECUS = [Ecu.fwdCamera] +ALL_HYUNDAI_ECUS = [Ecu.eps, Ecu.abs, Ecu.fwdRadar, Ecu.fwdCamera, Ecu.engine, Ecu.parkingAdas, Ecu.transmission, Ecu.adas, Ecu.hvac, Ecu.cornerRadar] + FW_QUERY_CONFIG = FwQueryConfig( requests=[ # TODO: minimize shared whitelists for CAN and cornerRadar for CAN-FD @@ -444,13 +451,52 @@ FW_QUERY_CONFIG = FwQueryConfig( obd_multiplexing=False, ), - # CAN-FD debugging queries + # CAN & CAN FD query to understand the three digit date code + # HDA2 cars usually use 6 digit date codes, so skip bus 1 + Request( + [HYUNDAI_ECU_MANUFACTURING_DATE], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=[Ecu.fwdCamera], + bus=0, + auxiliary=True, + logging=True, + ), + + # CAN & CAN FD logging queries (from camera) + Request( + [HYUNDAI_VERSION_REQUEST_LONG], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=ALL_HYUNDAI_ECUS, + bus=0, + auxiliary=True, + logging=True, + ), + Request( + [HYUNDAI_VERSION_REQUEST_MULTI], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=ALL_HYUNDAI_ECUS, + bus=0, + auxiliary=True, + logging=True, + ), + Request( + [HYUNDAI_VERSION_REQUEST_LONG], + [HYUNDAI_VERSION_RESPONSE], + whitelist_ecus=ALL_HYUNDAI_ECUS, + bus=1, + auxiliary=True, + obd_multiplexing=False, + logging=True, + ), + + # CAN-FD alt request logging queries Request( [HYUNDAI_VERSION_REQUEST_ALT], [HYUNDAI_VERSION_RESPONSE], whitelist_ecus=[Ecu.parkingAdas, Ecu.hvac], bus=0, auxiliary=True, + logging=True, ), Request( [HYUNDAI_VERSION_REQUEST_ALT], @@ -458,6 +504,7 @@ FW_QUERY_CONFIG = FwQueryConfig( whitelist_ecus=[Ecu.parkingAdas, Ecu.hvac], bus=1, auxiliary=True, + logging=True, obd_multiplexing=False, ), ], diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index 59657c5437..0a2d978004 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -1,10 +1,11 @@ +import json import os -import time import numpy as np import tomllib from abc import abstractmethod, ABC from enum import StrEnum -from typing import Any, Dict, Optional, Tuple, List, Callable +from typing import Any, NamedTuple, cast +from collections.abc import Callable from cereal import car from openpilot.common.basedir import BASEDIR @@ -12,7 +13,8 @@ from openpilot.common.conversions import Conversions as CV from openpilot.common.simple_kalman import KF1D, get_kalman_gain from openpilot.common.numpy_fast import clip from openpilot.common.realtime import DT_CTRL -from openpilot.selfdrive.car import apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG +from openpilot.selfdrive.car import PlatformConfig, apply_hysteresis, gen_empty_fingerprint, scale_rot_inertia, scale_tire_stiffness, STD_CARGO_KG +from openpilot.selfdrive.car.values import Platform from openpilot.selfdrive.controls.lib.drive_helpers import V_CRUISE_MAX, get_friction from openpilot.selfdrive.controls.lib.events import Events from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel @@ -20,7 +22,6 @@ from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel ButtonType = car.CarState.ButtonEvent.Type GearShifter = car.CarState.GearShifter EventName = car.CarEvent.EventName -TorqueFromLateralAccelCallbackType = Callable[[float, car.CarParams.LateralTorqueTuning, float, float, bool], float] MAX_CTRL_SPEED = (V_CRUISE_MAX + 4) * CV.KPH_TO_MS ACCEL_MAX = 2.0 @@ -32,6 +33,16 @@ TORQUE_OVERRIDE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/override TORQUE_SUBSTITUTE_PATH = os.path.join(BASEDIR, 'selfdrive/car/torque_data/substitute.toml') +class LatControlInputs(NamedTuple): + lateral_acceleration: float + roll_compensation: float + vego: float + aego: float + + +TorqueFromLateralAccelCallbackType = Callable[[LatControlInputs, car.CarParams.LateralTorqueTuning, float, float, bool, bool], float] + + def get_torque_params(candidate): with open(TORQUE_SUBSTITUTE_PATH, 'rb') as f: sub = tomllib.load(f) @@ -91,15 +102,26 @@ class CarInterfaceBase(ABC): return ACCEL_MIN, ACCEL_MAX @classmethod - def get_non_essential_params(cls, candidate: str): + def get_non_essential_params(cls, candidate: Platform): """ 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, False) @classmethod - def get_params(cls, candidate: str, fingerprint: Dict[int, Dict[int, int]], car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool): + def get_params(cls, candidate: Platform, fingerprint: dict[int, dict[int, int]], car_fw: list[car.CarParams.CarFw], experimental_long: bool, docs: bool): ret = CarInterfaceBase.get_std_params(candidate) + + if hasattr(candidate, "config"): + platform_config = cast(PlatformConfig, candidate.config) + if platform_config.specs is not None: + ret.mass = platform_config.specs.mass + ret.wheelbase = platform_config.specs.wheelbase + ret.steerRatio = platform_config.specs.steerRatio + ret.centerToFront = ret.wheelbase * platform_config.specs.centerToFrontRatio + ret.minEnableSpeed = platform_config.specs.minEnableSpeed + ret.minSteerSpeed = platform_config.specs.minSteerSpeed + ret = cls._get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs) # Vehicle mass is published curb weight plus assumed payload such as a human driver; notCars have no assumed payload @@ -114,8 +136,8 @@ class CarInterfaceBase(ABC): @staticmethod @abstractmethod - def _get_params(ret: car.CarParams, candidate: str, fingerprint: Dict[int, Dict[int, int]], - car_fw: List[car.CarParams.CarFw], experimental_long: bool, docs: bool): + def _get_params(ret: car.CarParams, candidate: Platform, fingerprint: dict[int, dict[int, int]], + car_fw: list[car.CarParams.CarFw], experimental_long: bool, docs: bool): raise NotImplementedError @staticmethod @@ -130,11 +152,11 @@ class CarInterfaceBase(ABC): def get_steer_feedforward_function(self): return self.get_steer_feedforward_default - def torque_from_lateral_accel_linear(self, lateral_accel_value: float, torque_params: car.CarParams.LateralTorqueTuning, - lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool) -> float: + def torque_from_lateral_accel_linear(self, latcontrol_inputs: LatControlInputs, torque_params: car.CarParams.LateralTorqueTuning, + lateral_accel_error: float, lateral_accel_deadzone: float, friction_compensation: bool, gravity_adjusted: bool) -> float: # The default is a linear relationship between torque and lateral acceleration (accounting for road roll and steering friction) friction = get_friction(lateral_accel_error, lateral_accel_deadzone, FRICTION_THRESHOLD, torque_params, friction_compensation) - return (lateral_accel_value / float(torque_params.latAccelFactor)) + friction + return (latcontrol_inputs.lateral_acceleration / float(torque_params.latAccelFactor)) + friction def torque_from_lateral_accel(self) -> TorqueFromLateralAccelCallbackType: return self.torque_from_lateral_accel_linear @@ -195,7 +217,7 @@ class CarInterfaceBase(ABC): def _update(self, c: car.CarControl) -> car.CarState: pass - def update(self, c: car.CarControl, can_strings: List[bytes]) -> car.CarState: + def update(self, c: car.CarControl, can_strings: list[bytes]) -> car.CarState: # parse can for cp in self.can_parsers: if cp is not None: @@ -229,7 +251,7 @@ class CarInterfaceBase(ABC): return reader @abstractmethod - def apply(self, c: car.CarControl, now_nanos: int) -> 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, @@ -312,13 +334,14 @@ class RadarInterfaceBase(ABC): self.pts = {} self.delay = 0 self.radar_ts = CP.radarTimeStep + self.frame = 0 self.no_radar_sleep = 'NO_RADAR_SLEEP' in os.environ def update(self, can_strings): - ret = car.RadarData.new_message() - if not self.no_radar_sleep: - time.sleep(self.radar_ts) # radard runs on RI updates - return ret + self.frame += 1 + if (self.frame % int(100 * self.radar_ts)) == 0: + return car.RadarData.new_message() + return None class CarStateBase(ABC): @@ -399,11 +422,11 @@ class CarStateBase(ABC): return bool(left_blinker_stalk or self.left_blinker_cnt > 0), bool(right_blinker_stalk or self.right_blinker_cnt > 0) @staticmethod - def parse_gear_shifter(gear: Optional[str]) -> car.CarState.GearShifter: + def parse_gear_shifter(gear: str | None) -> car.CarState.GearShifter: if gear is None: return GearShifter.unknown - d: Dict[str, car.CarState.GearShifter] = { + d: dict[str, car.CarState.GearShifter] = { 'P': GearShifter.park, 'PARK': GearShifter.park, 'R': GearShifter.reverse, 'REVERSE': GearShifter.reverse, 'N': GearShifter.neutral, 'NEUTRAL': GearShifter.neutral, @@ -440,7 +463,7 @@ INTERFACE_ATTR_FILE = { # interface-specific helpers -def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> Dict[str | StrEnum, Any]: +def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: bool = False) -> dict[str | StrEnum, Any]: # read all the folders in selfdrive/car and return a dict where: # - keys are all the car models or brand names # - values are attr values from all car folders @@ -464,3 +487,35 @@ def get_interface_attr(attr: str, combine_brands: bool = False, ignore_none: boo pass return result + + +class NanoFFModel: + def __init__(self, weights_loc: str, platform: str): + self.weights_loc = weights_loc + self.platform = platform + self.load_weights(platform) + + def load_weights(self, platform: str): + with open(self.weights_loc) as fob: + self.weights = {k: np.array(v) for k, v in json.load(fob)[platform].items()} + + def relu(self, x: np.ndarray): + return np.maximum(0.0, x) + + def forward(self, x: np.ndarray): + assert x.ndim == 1 + x = (x - self.weights['input_norm_mat'][:, 0]) / (self.weights['input_norm_mat'][:, 1] - self.weights['input_norm_mat'][:, 0]) + x = self.relu(np.dot(x, self.weights['w_1']) + self.weights['b_1']) + x = self.relu(np.dot(x, self.weights['w_2']) + self.weights['b_2']) + x = self.relu(np.dot(x, self.weights['w_3']) + self.weights['b_3']) + x = np.dot(x, self.weights['w_4']) + self.weights['b_4'] + return x + + def predict(self, x: list[float], do_sample: bool = False): + x = self.forward(np.array(x)) + if do_sample: + pred = np.random.laplace(x[0], np.exp(x[1]) / self.weights['temperature']) + else: + pred = x[0] + pred = pred * (self.weights['output_norm_mat'][1] - self.weights['output_norm_mat'][0]) + self.weights['output_norm_mat'][0] + return pred diff --git a/selfdrive/car/isotp_parallel_query.py b/selfdrive/car/isotp_parallel_query.py index 696935fd35..8fdc747e9e 100644 --- a/selfdrive/car/isotp_parallel_query.py +++ b/selfdrive/car/isotp_parallel_query.py @@ -5,11 +5,14 @@ from functools import partial import cereal.messaging as messaging from openpilot.common.swaglog import cloudlog from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp +from openpilot.selfdrive.car.fw_query_definitions import AddrType from panda.python.uds import CanClient, IsoTpMessage, FUNCTIONAL_ADDRS, get_rx_addr_for_tx_addr class IsoTpParallelQuery: - def __init__(self, sendcan, logcan, bus, addrs, request, response, response_offset=0x8, functional_addrs=None, debug=False, response_pending_timeout=10): + def __init__(self, sendcan: messaging.PubSocket, logcan: messaging.SubSocket, bus: int, addrs: list[int] | list[AddrType], + request: list[bytes], response: list[bytes], response_offset: int = 0x8, + functional_addrs: list[int] = None, debug: bool = False, response_pending_timeout: float = 10) -> None: self.sendcan = sendcan self.logcan = logcan self.bus = bus @@ -24,7 +27,7 @@ class IsoTpParallelQuery: assert tx_addr not in FUNCTIONAL_ADDRS, f"Functional address should be defined in functional_addrs: {hex(tx_addr)}" self.msg_addrs = {tx_addr: get_rx_addr_for_tx_addr(tx_addr[0], rx_offset=response_offset) for tx_addr in real_addrs} - self.msg_buffer = defaultdict(list) + self.msg_buffer: dict[int, list[tuple[int, int, bytes, int]]] = defaultdict(list) def rx(self): """Drain can socket and sort messages into buffers based on address""" @@ -63,7 +66,7 @@ class IsoTpParallelQuery: messaging.drain_sock_raw(self.logcan) self.msg_buffer = defaultdict(list) - def _create_isotp_msg(self, tx_addr, sub_addr, rx_addr): + def _create_isotp_msg(self, tx_addr: int, sub_addr: int | None, rx_addr: int): can_client = CanClient(self._can_tx, partial(self._can_rx, rx_addr, sub_addr=sub_addr), tx_addr, rx_addr, self.bus, sub_addr=sub_addr, debug=self.debug) @@ -73,7 +76,7 @@ class IsoTpParallelQuery: # as well as reduces chances we process messages from previous queries return IsoTpMessage(can_client, timeout=0, separation_time=0.01, debug=self.debug, max_len=max_len) - def get_data(self, timeout, total_timeout=60.): + def get_data(self, timeout: float, total_timeout: float = 60.) -> dict[AddrType, bytes]: self._drain_rx() # Create message objects diff --git a/selfdrive/car/mazda/carstate.py b/selfdrive/car/mazda/carstate.py index 1f7846ca06..c0819592d4 100644 --- a/selfdrive/car/mazda/carstate.py +++ b/selfdrive/car/mazda/carstate.py @@ -32,7 +32,7 @@ class CarState(CarStateBase): # Match panda speed reading speed_kph = cp.vl["ENGINE_DATA"]["SPEED"] - ret.standstill = speed_kph < .1 + ret.standstill = speed_kph <= .1 can_gear = int(cp.vl["GEAR"]["GEAR"]) ret.gearShifter = self.parse_gear_shifter(self.shifter_values.get(can_gear, None)) diff --git a/selfdrive/car/mazda/values.py b/selfdrive/car/mazda/values.py index c95ae162f9..eaf76d6a72 100644 --- a/selfdrive/car/mazda/values.py +++ b/selfdrive/car/mazda/values.py @@ -1,6 +1,5 @@ from dataclasses import dataclass, field from enum import StrEnum -from typing import Dict, List, Union from cereal import car from openpilot.selfdrive.car import dbc_dict @@ -41,7 +40,7 @@ class MazdaCarInfo(CarInfo): car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.mazda])) -CAR_INFO: Dict[str, Union[MazdaCarInfo, List[MazdaCarInfo]]] = { +CAR_INFO: dict[str, MazdaCarInfo | list[MazdaCarInfo]] = { CAR.CX5: MazdaCarInfo("Mazda CX-5 2017-21"), CAR.CX9: MazdaCarInfo("Mazda CX-9 2016-20"), CAR.MAZDA3: MazdaCarInfo("Mazda 3 2017-18"), @@ -67,16 +66,11 @@ class Buttons: FW_QUERY_CONFIG = FwQueryConfig( requests=[ - Request( - [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], - [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], - ), - # Log responses on powertrain bus + # TODO: check data to ensure ABS does not skip ISO-TP frames on bus 0 Request( [StdQueries.MANUFACTURER_SOFTWARE_VERSION_REQUEST], [StdQueries.MANUFACTURER_SOFTWARE_VERSION_RESPONSE], bus=0, - logging=True, ), ], ) diff --git a/selfdrive/car/mock/interface.py b/selfdrive/car/mock/interface.py index c7821bdb97..2e4ac43033 100755 --- a/selfdrive/car/mock/interface.py +++ b/selfdrive/car/mock/interface.py @@ -22,7 +22,7 @@ class CarInterface(CarInterfaceBase): def _update(self, c): self.sm.update(0) - gps_sock = 'gpsLocationExternal' if self.sm.rcv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' + gps_sock = 'gpsLocationExternal' if self.sm.recv_frame['gpsLocationExternal'] > 1 else 'gpsLocation' ret = car.CarState.new_message() ret.vEgo = self.sm[gps_sock].speed diff --git a/selfdrive/car/mock/values.py b/selfdrive/car/mock/values.py index c6c96579b4..e75665c98f 100644 --- a/selfdrive/car/mock/values.py +++ b/selfdrive/car/mock/values.py @@ -1,5 +1,4 @@ from enum import StrEnum -from typing import Dict, List, Optional, Union from openpilot.selfdrive.car.docs_definitions import CarInfo @@ -8,6 +7,6 @@ class CAR(StrEnum): MOCK = 'mock' -CAR_INFO: Dict[str, Optional[Union[CarInfo, List[CarInfo]]]] = { +CAR_INFO: dict[str, CarInfo | list[CarInfo] | None] = { CAR.MOCK: None, } diff --git a/selfdrive/car/nissan/fingerprints.py b/selfdrive/car/nissan/fingerprints.py index 69e88be898..19267ded46 100644 --- a/selfdrive/car/nissan/fingerprints.py +++ b/selfdrive/car/nissan/fingerprints.py @@ -45,15 +45,30 @@ FW_VERSIONS = { }, CAR.LEAF: { (Ecu.abs, 0x740, None): [ + b'476605SA1C', + b'476605SA7D', + b'476605SC2D', + b'476606WK7B', b'476606WK9B', ], (Ecu.eps, 0x742, None): [ + b'5SA2A\x99A\x05\x02N123F\x15b\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SA2A\xb7A\x05\x02N123F\x15\xa2\x00\x00\x00\x00\x00\x00\x00\x80', + b'5SN2A\xb7A\x05\x02N123F\x15\xa2\x00\x00\x00\x00\x00\x00\x00\x80', b'5SN2A\xb7A\x05\x02N126F\x15\xb2\x00\x00\x00\x00\x00\x00\x00\x80', ], (Ecu.fwdCamera, 0x707, None): [ + b'5SA0ADB\x04\x18\x00\x00\x00\x00\x00_*6\x04\x94a\x00\x00\x00\x80', + b'5SA2ADB\x04\x18\x00\x00\x00\x00\x00_*6\x04\x94a\x00\x00\x00\x80', + b'6WK2ADB\x04\x18\x00\x00\x00\x00\x00R;1\x18\x99\x10\x00\x00\x00\x80', + b'6WK2BDB\x04\x18\x00\x00\x00\x00\x00R;1\x18\x99\x10\x00\x00\x00\x80', b'6WK2CDB\x04\x18\x00\x00\x00\x00\x00R=1\x18\x99\x10\x00\x00\x00\x80', ], (Ecu.gateway, 0x18dad0f1, None): [ + b'284U25SA3C', + b'284U25SP0C', + b'284U25SP1C', + b'284U26WK0A', b'284U26WK0C', ], }, diff --git a/selfdrive/car/nissan/values.py b/selfdrive/car/nissan/values.py index c013b2056f..c0308f9e0d 100644 --- a/selfdrive/car/nissan/values.py +++ b/selfdrive/car/nissan/values.py @@ -1,6 +1,5 @@ from dataclasses import dataclass, field from enum import StrEnum -from typing import Dict, List, Optional, Union from cereal import car from panda.python import uds @@ -37,7 +36,7 @@ class NissanCarInfo(CarInfo): car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.nissan_a])) -CAR_INFO: Dict[str, Optional[Union[NissanCarInfo, List[NissanCarInfo]]]] = { +CAR_INFO: dict[str, NissanCarInfo | list[NissanCarInfo] | None] = { CAR.XTRAIL: NissanCarInfo("Nissan X-Trail 2017"), CAR.LEAF: NissanCarInfo("Nissan Leaf 2018-23", video_link="https://youtu.be/vaMbtAh_0cY"), CAR.LEAF_IC: None, # same platforms @@ -59,7 +58,7 @@ NISSAN_VERSION_RESPONSE_KWP = b'\x61\x83' NISSAN_RX_OFFSET = 0x20 FW_QUERY_CONFIG = FwQueryConfig( - requests=[request for bus, logging in ((0, True), (1, False)) for request in [ + requests=[request for bus, logging in ((0, False), (1, True)) for request in [ Request( [NISSAN_DIAGNOSTIC_REQUEST_KWP, NISSAN_VERSION_REQUEST_KWP], [NISSAN_DIAGNOSTIC_RESPONSE_KWP, NISSAN_VERSION_RESPONSE_KWP], diff --git a/selfdrive/car/subaru/fingerprints.py b/selfdrive/car/subaru/fingerprints.py index ad8ebe87cd..90fa6093d9 100644 --- a/selfdrive/car/subaru/fingerprints.py +++ b/selfdrive/car/subaru/fingerprints.py @@ -509,17 +509,20 @@ FW_VERSIONS = { (Ecu.fwdCamera, 0x787, None): [ b'\x04!\x01\x1eD\x07!\x00\x04,', b'\x04!\x08\x01.\x07!\x08\x022', + b'\r!\x08\x017\n!\x08\x003', ], (Ecu.engine, 0x7e0, None): [ b'\xd5"`0\x07', b'\xd5"a0\x07', b'\xf1"`q\x07', b'\xf1"aq\x07', + b'\xfa"ap\x07', ], (Ecu.transmission, 0x7e1, None): [ b'\x1d\x86B0\x00', b'\x1d\xf6B0\x00', b'\x1e\x86B0\x00', + b'\x1e\x86F0\x00', b'\x1e\xf6D0\x00', ], }, diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index 1296aead5e..2dfb4f9565 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -1,6 +1,7 @@ from cereal import car from panda import Panda from openpilot.selfdrive.car import get_safety_config +from openpilot.selfdrive.car.values import Platform from openpilot.selfdrive.car.disable_ecu import disable_ecu from openpilot.selfdrive.car.interfaces import CarInterfaceBase from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, LKAS_ANGLE, GLOBAL_GEN2, PREGLOBAL_CARS, HYBRID_CARS, SubaruFlags @@ -9,7 +10,7 @@ from openpilot.selfdrive.car.subaru.values import CAR, GLOBAL_ES_ADDR, LKAS_ANGL class CarInterface(CarInterfaceBase): @staticmethod - def _get_params(ret, candidate, fingerprint, car_fw, experimental_long, docs): + def _get_params(ret, candidate: Platform, fingerprint, car_fw, experimental_long, docs): ret.carName = "subaru" ret.radarUnavailable = True # for HYBRID CARS to be upstreamed, we need: @@ -41,10 +42,6 @@ class CarInterface(CarInterfaceBase): CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) if candidate in (CAR.ASCENT, CAR.ASCENT_2023): - ret.mass = 2031. - ret.wheelbase = 2.89 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 13.5 ret.steerActuatorDelay = 0.3 # end-to-end angle controller ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00003 @@ -52,10 +49,6 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.0025, 0.1], [0.00025, 0.01]] elif candidate == CAR.IMPREZA: - ret.mass = 1568. - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 15 ret.steerActuatorDelay = 0.4 # end-to-end angle controller ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00005 @@ -63,58 +56,31 @@ class CarInterface(CarInterfaceBase): ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.2, 0.3], [0.02, 0.03]] elif candidate == CAR.IMPREZA_2020: - ret.mass = 1480. - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 17 # learned, 14 stock ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.00005 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.045, 0.042, 0.20], [0.04, 0.035, 0.045]] elif candidate == CAR.CROSSTREK_HYBRID: - ret.mass = 1668. - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 17 ret.steerActuatorDelay = 0.1 elif candidate in (CAR.FORESTER, CAR.FORESTER_2022, CAR.FORESTER_HYBRID): - ret.mass = 1568. - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 17 # learned, 14 stock ret.lateralTuning.init('pid') ret.lateralTuning.pid.kf = 0.000038 ret.lateralTuning.pid.kiBP, ret.lateralTuning.pid.kpBP = [[0., 14., 23.], [0., 14., 23.]] ret.lateralTuning.pid.kpV, ret.lateralTuning.pid.kiV = [[0.01, 0.065, 0.2], [0.001, 0.015, 0.025]] elif candidate in (CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023): - ret.mass = 1568. - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 17 ret.steerActuatorDelay = 0.1 elif candidate in (CAR.FORESTER_PREGLOBAL, CAR.OUTBACK_PREGLOBAL_2018): ret.safetyConfigs[0].safetyParam = Panda.FLAG_SUBARU_PREGLOBAL_REVERSED_DRIVER_TORQUE # Outback 2018-2019 and Forester have reversed driver torque signal - ret.mass = 1568 - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 20 # learned, 14 stock elif candidate == CAR.LEGACY_PREGLOBAL: - ret.mass = 1568 - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 12.5 # 14.5 stock ret.steerActuatorDelay = 0.15 elif candidate == CAR.OUTBACK_PREGLOBAL: - ret.mass = 1568 - ret.wheelbase = 2.67 - ret.centerToFront = ret.wheelbase * 0.5 - ret.steerRatio = 20 # learned, 14 stock + pass else: raise ValueError(f"unknown car: {candidate}") diff --git a/selfdrive/car/subaru/subarucan.py b/selfdrive/car/subaru/subarucan.py index 290737c3e6..86d39ff885 100644 --- a/selfdrive/car/subaru/subarucan.py +++ b/selfdrive/car/subaru/subarucan.py @@ -13,6 +13,15 @@ def create_steering_control(packer, apply_steer, steer_req): return packer.make_can_msg("ES_LKAS", 0, values) +def create_steering_control_angle(packer, apply_steer, steer_req): + values = { + "LKAS_Output": apply_steer, + "LKAS_Request": steer_req, + "SET_3": 3 + } + return packer.make_can_msg("ES_LKAS_ANGLE", 0, values) + + def create_steering_status(packer): return packer.make_can_msg("ES_LKAS_State", 0, {}) diff --git a/selfdrive/car/subaru/values.py b/selfdrive/car/subaru/values.py index d9d2f78cea..0fd7ab9c13 100644 --- a/selfdrive/car/subaru/values.py +++ b/selfdrive/car/subaru/values.py @@ -1,10 +1,9 @@ from dataclasses import dataclass, field -from enum import Enum, IntFlag, StrEnum -from typing import Dict, List, Union +from enum import Enum, IntFlag from cereal import car from panda.python import uds -from openpilot.selfdrive.car import dbc_dict +from openpilot.selfdrive.car import CarSpecs, DbcDict, PlatformConfig, Platforms, dbc_dict from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Tool, Column from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, StdQueries, p16 @@ -68,27 +67,6 @@ class CanBus: camera = 2 -class CAR(StrEnum): - # Global platform - ASCENT = "SUBARU ASCENT LIMITED 2019" - ASCENT_2023 = "SUBARU ASCENT 2023" - IMPREZA = "SUBARU IMPREZA LIMITED 2019" - IMPREZA_2020 = "SUBARU IMPREZA SPORT 2020" - FORESTER = "SUBARU FORESTER 2019" - OUTBACK = "SUBARU OUTBACK 6TH GEN" - CROSSTREK_HYBRID = "SUBARU CROSSTREK HYBRID 2020" - FORESTER_HYBRID = "SUBARU FORESTER HYBRID 2020" - LEGACY = "SUBARU LEGACY 7TH GEN" - FORESTER_2022 = "SUBARU FORESTER 2022" - OUTBACK_2023 = "SUBARU OUTBACK 7TH GEN" - - # Pre-global - FORESTER_PREGLOBAL = "SUBARU FORESTER 2017 - 2018" - LEGACY_PREGLOBAL = "SUBARU LEGACY 2015 - 2018" - OUTBACK_PREGLOBAL = "SUBARU OUTBACK 2015 - 2017" - OUTBACK_PREGLOBAL_2018 = "SUBARU OUTBACK 2018 - 2019" - - class Footnote(Enum): GLOBAL = CarFootnote( "In the non-US market, openpilot requires the car to come equipped with EyeSight with Lane Keep Assistance.", @@ -102,7 +80,7 @@ class Footnote(Enum): class SubaruCarInfo(CarInfo): package: str = "EyeSight Driver Assistance" car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.subaru_a])) - footnotes: List[Enum] = field(default_factory=lambda: [Footnote.GLOBAL]) + footnotes: list[Enum] = field(default_factory=lambda: [Footnote.GLOBAL]) def init_make(self, CP: car.CarParams): self.car_parts.parts.extend([Tool.socket_8mm_deep, Tool.pry_tool]) @@ -110,32 +88,107 @@ class SubaruCarInfo(CarInfo): if CP.experimentalLongitudinalAvailable: self.footnotes.append(Footnote.EXP_LONG) -CAR_INFO: Dict[str, Union[SubaruCarInfo, List[SubaruCarInfo]]] = { - CAR.ASCENT: SubaruCarInfo("Subaru Ascent 2019-21", "All"), - CAR.OUTBACK: SubaruCarInfo("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])), - CAR.LEGACY: SubaruCarInfo("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])), - CAR.IMPREZA: [ - SubaruCarInfo("Subaru Impreza 2017-19"), - SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), - SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), - ], - CAR.IMPREZA_2020: [ - SubaruCarInfo("Subaru Impreza 2020-22"), - SubaruCarInfo("Subaru Crosstrek 2020-23"), - SubaruCarInfo("Subaru XV 2020-21"), - ], + +@dataclass +class SubaruPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('subaru_global_2017_generated', None)) + + +class CAR(Platforms): + # Global platform + ASCENT = SubaruPlatformConfig( + "SUBARU ASCENT LIMITED 2019", + SubaruCarInfo("Subaru Ascent 2019-21", "All"), + specs=CarSpecs(mass=2031, wheelbase=2.89, steerRatio=13.5), + ) + OUTBACK = SubaruPlatformConfig( + "SUBARU OUTBACK 6TH GEN", + SubaruCarInfo("Subaru Outback 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])), + specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17), + ) + LEGACY = SubaruPlatformConfig( + "SUBARU LEGACY 7TH GEN", + SubaruCarInfo("Subaru Legacy 2020-22", "All", car_parts=CarParts.common([CarHarness.subaru_b])), + specs=OUTBACK.specs, + ) + IMPREZA = SubaruPlatformConfig( + "SUBARU IMPREZA LIMITED 2019", + [ + SubaruCarInfo("Subaru Impreza 2017-19"), + SubaruCarInfo("Subaru Crosstrek 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), + SubaruCarInfo("Subaru XV 2018-19", video_link="https://youtu.be/Agww7oE1k-s?t=26"), + ], + specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=15), + ) + IMPREZA_2020 = SubaruPlatformConfig( + "SUBARU IMPREZA SPORT 2020", + [ + SubaruCarInfo("Subaru Impreza 2020-22"), + SubaruCarInfo("Subaru Crosstrek 2020-23"), + SubaruCarInfo("Subaru XV 2020-21"), + ], + specs=CarSpecs(mass=1480, wheelbase=2.67, steerRatio=17), + ) # TODO: is there an XV and Impreza too? - CAR.CROSSTREK_HYBRID: SubaruCarInfo("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b])), - CAR.FORESTER_HYBRID: SubaruCarInfo("Subaru Forester Hybrid 2020"), - CAR.FORESTER: SubaruCarInfo("Subaru Forester 2019-21", "All"), - CAR.FORESTER_PREGLOBAL: SubaruCarInfo("Subaru Forester 2017-18"), - CAR.LEGACY_PREGLOBAL: SubaruCarInfo("Subaru Legacy 2015-18"), - CAR.OUTBACK_PREGLOBAL: SubaruCarInfo("Subaru Outback 2015-17"), - CAR.OUTBACK_PREGLOBAL_2018: SubaruCarInfo("Subaru Outback 2018-19"), - CAR.FORESTER_2022: SubaruCarInfo("Subaru Forester 2022-23", "All", car_parts=CarParts.common([CarHarness.subaru_c])), - CAR.OUTBACK_2023: SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), - CAR.ASCENT_2023: SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), -} + CROSSTREK_HYBRID = SubaruPlatformConfig( + "SUBARU CROSSTREK HYBRID 2020", + SubaruCarInfo("Subaru Crosstrek Hybrid 2020", car_parts=CarParts.common([CarHarness.subaru_b])), + dbc_dict('subaru_global_2020_hybrid_generated', None), + specs=CarSpecs(mass=1668, wheelbase=2.67, steerRatio=17), + ) + FORESTER = SubaruPlatformConfig( + "SUBARU FORESTER 2019", + SubaruCarInfo("Subaru Forester 2019-21", "All"), + specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=17), + ) + FORESTER_HYBRID = SubaruPlatformConfig( + "SUBARU FORESTER HYBRID 2020", + SubaruCarInfo("Subaru Forester Hybrid 2020"), + dbc_dict('subaru_global_2020_hybrid_generated', None), + specs=FORESTER.specs, + ) + # Pre-global + FORESTER_PREGLOBAL = SubaruPlatformConfig( + "SUBARU FORESTER 2017 - 2018", + SubaruCarInfo("Subaru Forester 2017-18"), + dbc_dict('subaru_forester_2017_generated', None), + specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=20), + ) + LEGACY_PREGLOBAL = SubaruPlatformConfig( + "SUBARU LEGACY 2015 - 2018", + SubaruCarInfo("Subaru Legacy 2015-18"), + dbc_dict('subaru_outback_2015_generated', None), + specs=CarSpecs(mass=1568, wheelbase=2.67, steerRatio=12.5), + ) + OUTBACK_PREGLOBAL = SubaruPlatformConfig( + "SUBARU OUTBACK 2015 - 2017", + SubaruCarInfo("Subaru Outback 2015-17"), + dbc_dict('subaru_outback_2015_generated', None), + specs=FORESTER_PREGLOBAL.specs, + ) + OUTBACK_PREGLOBAL_2018 = SubaruPlatformConfig( + "SUBARU OUTBACK 2018 - 2019", + SubaruCarInfo("Subaru Outback 2018-19"), + dbc_dict('subaru_outback_2019_generated', None), + specs=FORESTER_PREGLOBAL.specs, + ) + # Angle LKAS + FORESTER_2022 = SubaruPlatformConfig( + "SUBARU FORESTER 2022", + SubaruCarInfo("Subaru Forester 2022-24", "All", car_parts=CarParts.common([CarHarness.subaru_c])), + specs=FORESTER.specs, + ) + OUTBACK_2023 = SubaruPlatformConfig( + "SUBARU OUTBACK 7TH GEN", + SubaruCarInfo("Subaru Outback 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), + specs=OUTBACK.specs, + ) + ASCENT_2023 = SubaruPlatformConfig( + "SUBARU ASCENT 2023", + SubaruCarInfo("Subaru Ascent 2023", "All", car_parts=CarParts.common([CarHarness.subaru_d])), + specs=ASCENT.specs, + ) + LKAS_ANGLE = {CAR.FORESTER_2022, CAR.OUTBACK_2023, CAR.ASCENT_2023} GLOBAL_GEN2 = {CAR.OUTBACK, CAR.LEGACY, CAR.OUTBACK_2023, CAR.ASCENT_2023} @@ -186,20 +239,5 @@ FW_QUERY_CONFIG = FwQueryConfig( } ) -DBC = { - CAR.ASCENT: dbc_dict('subaru_global_2017_generated', None), - CAR.ASCENT_2023: dbc_dict('subaru_global_2017_generated', None), - CAR.IMPREZA: dbc_dict('subaru_global_2017_generated', None), - CAR.IMPREZA_2020: dbc_dict('subaru_global_2017_generated', None), - CAR.FORESTER: dbc_dict('subaru_global_2017_generated', None), - CAR.FORESTER_2022: dbc_dict('subaru_global_2017_generated', None), - CAR.OUTBACK: dbc_dict('subaru_global_2017_generated', None), - CAR.FORESTER_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None), - CAR.CROSSTREK_HYBRID: dbc_dict('subaru_global_2020_hybrid_generated', None), - CAR.OUTBACK_2023: dbc_dict('subaru_global_2017_generated', None), - CAR.LEGACY: dbc_dict('subaru_global_2017_generated', None), - CAR.FORESTER_PREGLOBAL: dbc_dict('subaru_forester_2017_generated', None), - CAR.LEGACY_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), - CAR.OUTBACK_PREGLOBAL: dbc_dict('subaru_outback_2015_generated', None), - CAR.OUTBACK_PREGLOBAL_2018: dbc_dict('subaru_outback_2019_generated', None), -} +CAR_INFO = CAR.create_carinfo_map() +DBC = CAR.create_dbc_map() diff --git a/selfdrive/car/tesla/values.py b/selfdrive/car/tesla/values.py index 12877f1344..2a51d15da8 100644 --- a/selfdrive/car/tesla/values.py +++ b/selfdrive/car/tesla/values.py @@ -1,6 +1,5 @@ from collections import namedtuple from enum import StrEnum -from typing import Dict, List, Union from cereal import car from openpilot.selfdrive.car import AngleRateLimit, dbc_dict @@ -17,7 +16,7 @@ class CAR(StrEnum): AP2_MODELS = 'TESLA AP2 MODEL S' -CAR_INFO: Dict[str, Union[CarInfo, List[CarInfo]]] = { +CAR_INFO: dict[str, CarInfo | list[CarInfo]] = { CAR.AP1_MODELS: CarInfo("Tesla AP1 Model S", "All"), CAR.AP2_MODELS: CarInfo("Tesla AP2 Model S", "All"), } diff --git a/selfdrive/car/tests/big_cars_test.sh b/selfdrive/car/tests/big_cars_test.sh new file mode 100755 index 0000000000..af45c9cd14 --- /dev/null +++ b/selfdrive/car/tests/big_cars_test.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +SCRIPT_DIR=$(dirname "$0") +BASEDIR=$(realpath "$SCRIPT_DIR/../../../") +cd $BASEDIR + +MAX_EXAMPLES=300 +INTERNAL_SEG_CNT=300 +FILEREADER_CACHE=1 +INTERNAL_SEG_LIST=selfdrive/car/tests/test_models_segs.txt + +cd selfdrive/car/tests && pytest test_models.py test_car_interfaces.py \ No newline at end of file diff --git a/selfdrive/car/tests/routes.py b/selfdrive/car/tests/routes.py index 8db950ced0..9c14c0d252 100755 --- a/selfdrive/car/tests/routes.py +++ b/selfdrive/car/tests/routes.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -from typing import NamedTuple, Optional +from typing import NamedTuple from openpilot.selfdrive.car.chrysler.values import CAR as CHRYSLER from openpilot.selfdrive.car.gm.values import CAR as GM @@ -20,7 +20,6 @@ non_tested_cars = [ GM.CADILLAC_ATS, GM.HOLDEN_ASTRA, GM.MALIBU, - GM.EQUINOX, HYUNDAI.GENESIS_G90, HONDA.ODYSSEY_CHN, VOLKSWAGEN.CRAFTER_MK2, # need a route from an ACC-equipped Crafter @@ -30,8 +29,8 @@ non_tested_cars = [ class CarTestRoute(NamedTuple): route: str - car_model: Optional[str] - segment: Optional[int] = None + car_model: str | None + segment: int | None = None routes = [ @@ -46,6 +45,7 @@ routes = [ CarTestRoute("3d84727705fecd04|2021-05-25--08-38-56", CHRYSLER.PACIFICA_2020), 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("3379c85aeedc8285|2023-12-07--17-49-39", CHRYSLER.DODGE_DURANGO), CarTestRoute("54827bf84c38b14f|2023-01-25--14-14-11", FORD.BRONCO_SPORT_MK1), CarTestRoute("f8eaaccd2a90aef8|2023-05-04--15-10-09", FORD.ESCAPE_MK4), @@ -59,6 +59,7 @@ routes = [ 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("e746f59bc96fd789|2024-01-31--22-25-58", GM.EQUINOX), CarTestRoute("ef8f2185104d862e|2023-02-09--18-37-13", GM.ESCALADE), CarTestRoute("46460f0da08e621e|2021-10-26--07-21-46", GM.ESCALADE_ESV), CarTestRoute("168f8b3be57f66ae|2023-09-12--21-44-42", GM.ESCALADE_ESV_2019), @@ -186,6 +187,7 @@ routes = [ CarTestRoute("5f5afb36036506e4|2019-05-14--02-09-54", TOYOTA.COROLLA_TSS2), CarTestRoute("5ceff72287a5c86c|2019-10-19--10-59-02", TOYOTA.COROLLA_TSS2), # hybrid CarTestRoute("d2525c22173da58b|2021-04-25--16-47-04", TOYOTA.PRIUS), + CarTestRoute("b14c5b4742e6fc85|2020-07-28--19-50-11", TOYOTA.RAV4), CarTestRoute("32a7df20486b0f70|2020-02-06--16-06-50", TOYOTA.RAV4H), CarTestRoute("cdf2f7de565d40ae|2019-04-25--03-53-41", TOYOTA.RAV4_TSS2), CarTestRoute("a5c341bb250ca2f0|2022-05-18--16-05-17", TOYOTA.RAV4_TSS2_2022), @@ -207,6 +209,7 @@ routes = [ CarTestRoute("ec429c0f37564e3c|2020-02-01--17-28-12", TOYOTA.LEXUS_NX), # hybrid CarTestRoute("3fd5305f8b6ca765|2021-04-28--19-26-49", TOYOTA.LEXUS_NX_TSS2), CarTestRoute("09ae96064ed85a14|2022-06-09--12-22-31", TOYOTA.LEXUS_NX_TSS2), # hybrid + CarTestRoute("4765fbbf59e3cd88|2024-02-06--17-45-32", TOYOTA.LEXUS_LC_TSS2), CarTestRoute("0a302ffddbb3e3d3|2020-02-08--16-19-08", TOYOTA.HIGHLANDER_TSS2), CarTestRoute("437e4d2402abf524|2021-05-25--07-58-50", TOYOTA.HIGHLANDER_TSS2), # hybrid CarTestRoute("3183cd9b021e89ce|2021-05-25--10-34-44", TOYOTA.HIGHLANDER), @@ -221,7 +224,7 @@ routes = [ CarTestRoute("ea8fbe72b96a185c|2023-02-08--15-11-46", TOYOTA.CHR_TSS2), CarTestRoute("ea8fbe72b96a185c|2023-02-22--09-20-34", TOYOTA.CHR_TSS2), # openpilot longitudinal, with smartDSU CarTestRoute("6719965b0e1d1737|2023-02-09--22-44-05", TOYOTA.CHR_TSS2), # hybrid - # CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled + CarTestRoute("6719965b0e1d1737|2023-08-29--06-40-05", TOYOTA.CHR_TSS2), # hybrid, openpilot longitudinal, radar disabled CarTestRoute("14623aae37e549f3|2021-10-24--01-20-49", TOYOTA.PRIUS_V), CarTestRoute("202c40641158a6e5|2021-09-21--09-43-24", VOLKSWAGEN.ARTEON_MK1), @@ -289,7 +292,6 @@ routes = [ # Segments that test specific issues # Controls mismatch due to interceptor threshold CarTestRoute("cfb32f0fb91b173b|2022-04-06--14-54-45", HONDA.CIVIC, segment=21), - CarTestRoute("5a8762b91fc70467|2022-04-14--21-26-20", TOYOTA.RAV4, segment=2), # Controls mismatch due to standstill threshold CarTestRoute("bec2dcfde6a64235|2022-04-08--14-21-32", HONDA.CRV_HYBRID, segment=22), ] diff --git a/selfdrive/car/tests/test_car_interfaces.py b/selfdrive/car/tests/test_car_interfaces.py index a920bd45da..a454f616cb 100755 --- a/selfdrive/car/tests/test_car_interfaces.py +++ b/selfdrive/car/tests/test_car_interfaces.py @@ -14,6 +14,10 @@ from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.car.fingerprints import all_known_cars from openpilot.selfdrive.car.fw_versions import FW_VERSIONS from openpilot.selfdrive.car.interfaces import get_interface_attr +from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle +from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID +from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque +from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.test.fuzzy_generation import DrawType, FuzzyGenerator ALL_ECUS = list({ecu for ecus in FW_VERSIONS.values() for ecu in ecus.keys()}) @@ -42,11 +46,6 @@ def get_fuzzy_car_interface_args(draw: DrawType) -> dict: class TestCarInterfaces(unittest.TestCase): - - @classmethod - def setUpClass(cls): - os.environ['NO_RADAR_SLEEP'] = '1' - # FIXME: Due to the lists used in carParams, Phase.target is very slow and will cause # many generated examples to overrun when max_examples > ~20, don't use it @parameterized.expand([(car,) for car in sorted(all_known_cars())]) @@ -75,6 +74,10 @@ class TestCarInterfaces(unittest.TestCase): self.assertEqual(len(car_params.longitudinalTuning.kiV), len(car_params.longitudinalTuning.kiBP)) self.assertEqual(len(car_params.longitudinalTuning.deadzoneV), len(car_params.longitudinalTuning.deadzoneBP)) + # If we're using the interceptor for gasPressed, we should be commanding gas with it + if car_params.enableGasInterceptor: + self.assertTrue(car_params.openpilotLongitudinalControl) + # Lateral sanity checks if car_params.steerControlType != car.CarParams.SteerControlType.angle: tune = car_params.lateralTuning @@ -105,6 +108,17 @@ class TestCarInterfaces(unittest.TestCase): car_interface.apply(CC, now_nanos) now_nanos += DT_CTRL * 1e9 # 10ms + # Test controller initialization + # TODO: wait until card refactor is merged to run controller a few times, + # hypothesis also slows down significantly with just one more message draw + LongControl(car_params) + if car_params.steerControlType == car.CarParams.SteerControlType.angle: + LatControlAngle(car_params, car_interface) + elif car_params.lateralTuning.which() == 'pid': + LatControlPID(car_params, car_interface) + elif car_params.lateralTuning.which() == 'torque': + LatControlTorque(car_params, car_interface) + # Test radar interface RadarInterface = importlib.import_module(f'selfdrive.car.{car_params.carName}.radar_interface').RadarInterface radar_interface = RadarInterface(car_params) diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py index d9d67fe87d..0cafb508f7 100755 --- a/selfdrive/car/tests/test_docs.py +++ b/selfdrive/car/tests/test_docs.py @@ -20,7 +20,7 @@ class TestCarDocs(unittest.TestCase): def test_generator(self): generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE) - with open(CARS_MD_OUT, "r") as f: + with open(CARS_MD_OUT) as f: current_cars_md = f.read() self.assertEqual(generated_cars_md, current_cars_md, @@ -45,7 +45,7 @@ class TestCarDocs(unittest.TestCase): all_car_info_platforms = get_interface_attr("CAR_INFO", combine_brands=True).keys() for platform in sorted(interfaces.keys()): with self.subTest(platform=platform): - self.assertTrue(platform in all_car_info_platforms, "Platform: {} doesn't exist in CarInfo".format(platform)) + self.assertTrue(platform in all_car_info_platforms, f"Platform: {platform} doesn't exist in CarInfo") def test_naming_conventions(self): # Asserts market-standard car naming conventions by brand diff --git a/selfdrive/car/tests/test_fingerprints.py b/selfdrive/car/tests/test_fingerprints.py index 61e9a4d165..34f30bc703 100755 --- a/selfdrive/car/tests/test_fingerprints.py +++ b/selfdrive/car/tests/test_fingerprints.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os import sys -from typing import Dict, List from openpilot.common.basedir import BASEDIR @@ -64,7 +63,7 @@ def check_can_ignition_conflicts(fingerprints, brands): if __name__ == "__main__": fingerprints = _get_fingerprints() - fingerprints_flat: List[Dict] = [] + fingerprints_flat: list[dict] = [] car_names = [] brand_names = [] for brand in fingerprints: diff --git a/selfdrive/car/tests/test_fw_fingerprint.py b/selfdrive/car/tests/test_fw_fingerprint.py index 620fa25691..1a745b4447 100755 --- a/selfdrive/car/tests/test_fw_fingerprint.py +++ b/selfdrive/car/tests/test_fw_fingerprint.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -import pytest import random import time import unittest @@ -11,7 +10,7 @@ from cereal import car from openpilot.selfdrive.car.car_helpers import interfaces from openpilot.selfdrive.car.fingerprints import FW_VERSIONS from openpilot.selfdrive.car.fw_versions import FW_QUERY_CONFIGS, FUZZY_EXCLUDE_ECUS, VERSIONS, build_fw_dict, \ - match_fw_to_car, get_fw_versions, get_present_ecus + match_fw_to_car, get_brand_ecu_matches, get_fw_versions, get_present_ecus from openpilot.selfdrive.car.vin import get_vin CarFw = car.CarParams.CarFw @@ -34,18 +33,29 @@ class TestFwFingerprint(unittest.TestCase): self.assertEqual(len(candidates), 1, f"got more than one candidate: {candidates}") self.assertEqual(candidates[0], expected) - @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) - def test_exact_match(self, brand, car_model, ecus): + @parameterized.expand([(b, c, e[c], n) for b, e in VERSIONS.items() for c in e for n in (True, False)]) + def test_exact_match(self, brand, car_model, ecus, test_non_essential): + config = FW_QUERY_CONFIGS[brand] CP = car.CarParams.new_message() - for _ in range(200): + for _ in range(100): fw = [] for ecu, fw_versions in ecus.items(): + # Assume non-essential ECUs apply to all cars, so we catch cases where Car A with + # missing ECUs won't match to Car B where only Car B has labeled non-essential ECUs + if ecu[0] in config.non_essential_ecus and test_non_essential: + continue + ecu_name, addr, sub_addr = ecu fw.append({"ecu": ecu_name, "fwVersion": random.choice(fw_versions), 'brand': brand, "address": addr, "subAddress": 0 if sub_addr is None else sub_addr}) CP.carFw = fw _, matches = match_fw_to_car(CP.carFw, allow_fuzzy=False) - self.assertFingerprints(matches, car_model) + if not test_non_essential: + self.assertFingerprints(matches, car_model) + else: + # if we're removing ECUs we expect some match loss, but it shouldn't mismatch + if len(matches) != 0: + self.assertFingerprints(matches, car_model) @parameterized.expand([(b, c, e[c]) for b, e in VERSIONS.items() for c in e]) def test_custom_fuzzy_match(self, brand, car_model, ecus): @@ -150,7 +160,7 @@ class TestFwFingerprint(unittest.TestCase): # Ensure each brand has at least 1 ECU to query, and extra ECU retrieval for brand, config in FW_QUERY_CONFIGS.items(): self.assertEqual(len(config.get_all_ecus({}, include_extra_ecus=False)), 0) - self.assertEqual(config.get_all_ecus({}, include_ecu_type=True), set(config.extra_ecus)) + self.assertEqual(config.get_all_ecus({}), set(config.extra_ecus)) self.assertGreater(len(config.get_all_ecus(VERSIONS[brand])), 0) def test_fw_request_ecu_whitelist(self): @@ -178,6 +188,14 @@ class TestFwFingerprint(unittest.TestCase): self.assertFalse(request_obj.auxiliary and request_obj.bus == 1 and request_obj.obd_multiplexing, f"{brand.title()}: OBD multiplexed request is marked auxiliary: {request_obj}") + def test_brand_ecu_matches(self): + empty_response = {brand: set() for brand in FW_QUERY_CONFIGS} + self.assertEqual(get_brand_ecu_matches(set()), empty_response) + + # we ignore bus + expected_response = empty_response | {'toyota': {(0x750, 0xf)}} + self.assertEqual(get_brand_ecu_matches({(0x758, 0xf, 99)}), expected_response) + class TestFwFingerprintTiming(unittest.TestCase): N: int = 5 @@ -218,7 +236,7 @@ class TestFwFingerprintTiming(unittest.TestCase): def test_startup_timing(self): # Tests worse-case VIN query time and typical present ECU query time - vin_ref_times = {'worst': 1.5, 'best': 0.5} # best assumes we go through all queries to get a match + vin_ref_times = {'worst': 1.2, 'best': 0.6} # best assumes we go through all queries to get a match present_ecu_ref_time = 0.75 def fake_get_ecu_addrs(*_, timeout): @@ -244,48 +262,50 @@ class TestFwFingerprintTiming(unittest.TestCase): self._assert_timing(self.total_time / self.N, vin_ref_times[name]) print(f'get_vin {name} case, query time={self.total_time / self.N} seconds') - @pytest.mark.timeout(60) def test_fw_query_timing(self): - total_ref_time = 6.9 + total_ref_time = {1: 6.5, 2: 7.4} brand_ref_times = { 1: { 'gm': 0.5, 'body': 0.1, 'chrysler': 0.3, - 'ford': 0.1, + 'ford': 0.2, 'honda': 0.55, - 'hyundai': 0.65, - 'mazda': 0.2, + 'hyundai': 1.05, + 'mazda': 0.1, 'nissan': 0.8, 'subaru': 0.45, 'tesla': 0.2, 'toyota': 1.6, - 'volkswagen': 0.2, + 'volkswagen': 0.65, }, 2: { - 'ford': 0.2, - 'hyundai': 1.05, + 'ford': 0.3, + 'hyundai': 1.85, } } - total_time = 0 + total_times = {1: 0.0, 2: 0.0} for num_pandas in (1, 2): for brand, config in FW_QUERY_CONFIGS.items(): with self.subTest(brand=brand, num_pandas=num_pandas): - multi_panda_requests = [r for r in config.requests if r.bus > 3] - if not len(multi_panda_requests) and num_pandas > 1: - raise unittest.SkipTest("No multi-panda FW queries") - avg_time = self._benchmark_brand(brand, num_pandas) - total_time += avg_time + total_times[num_pandas] += avg_time avg_time = round(avg_time, 2) - self._assert_timing(avg_time, brand_ref_times[num_pandas][brand]) + + ref_time = brand_ref_times[num_pandas].get(brand) + if ref_time is None: + # ref time should be same as 1 panda if no aux queries + ref_time = brand_ref_times[num_pandas - 1][brand] + + self._assert_timing(avg_time, ref_time) print(f'{brand=}, {num_pandas=}, {len(config.requests)=}, avg FW query time={avg_time} seconds') - with self.subTest(brand='all_brands'): - total_time = round(total_time, 2) - self._assert_timing(total_time, total_ref_time) - print(f'all brands, total FW query time={total_time} seconds') + for num_pandas in (1, 2): + with self.subTest(brand='all_brands', num_pandas=num_pandas): + total_time = round(total_times[num_pandas], 2) + self._assert_timing(total_time, total_ref_time[num_pandas]) + print(f'all brands, total FW query time={total_time} seconds') if __name__ == "__main__": diff --git a/selfdrive/car/tests/test_lateral_limits.py b/selfdrive/car/tests/test_lateral_limits.py index 10e24ff4c5..083cdd5a5e 100755 --- a/selfdrive/car/tests/test_lateral_limits.py +++ b/selfdrive/car/tests/test_lateral_limits.py @@ -3,7 +3,6 @@ from collections import defaultdict import importlib from parameterized import parameterized_class import sys -from typing import DefaultDict, Dict import unittest from openpilot.common.realtime import DT_CTRL @@ -29,7 +28,7 @@ ABOVE_LIMITS_CARS = [ SUBARU.OUTBACK, ] -car_model_jerks: DefaultDict[str, Dict[str, float]] = defaultdict(dict) +car_model_jerks: defaultdict[str, dict[str, float]] = defaultdict(dict) @parameterized_class('car_model', [(c,) for c in sorted(CAR_MODELS)]) diff --git a/selfdrive/car/tests/test_models.py b/selfdrive/car/tests/test_models.py index 537214d14c..2b29c14f72 100755 --- a/selfdrive/car/tests/test_models.py +++ b/selfdrive/car/tests/test_models.py @@ -8,7 +8,6 @@ import unittest from collections import defaultdict, Counter import hypothesis.strategies as st from hypothesis import Phase, given, settings -from typing import List, Optional, Tuple from parameterized import parameterized_class from cereal import messaging, log, car @@ -23,9 +22,8 @@ from openpilot.selfdrive.car.tests.routes import non_tested_cars, routes, CarTes from openpilot.selfdrive.controls.controlsd import Controls from openpilot.selfdrive.test.helpers import read_segment_list from openpilot.system.hardware.hw import DEFAULT_DOWNLOAD_CACHE_ROOT -from openpilot.tools.lib.comma_car_segments import get_url -from openpilot.tools.lib.logreader import LogReader -from openpilot.tools.lib.route import Route, SegmentName, RouteName +from openpilot.tools.lib.logreader import LogReader, internal_source, openpilotci_source +from openpilot.tools.lib.route import SegmentName from panda.tests.libpanda import libpanda_py @@ -37,11 +35,11 @@ NUM_JOBS = int(os.environ.get("NUM_JOBS", "1")) JOB_ID = int(os.environ.get("JOB_ID", "0")) INTERNAL_SEG_LIST = os.environ.get("INTERNAL_SEG_LIST", "") INTERNAL_SEG_CNT = int(os.environ.get("INTERNAL_SEG_CNT", "0")) -MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "50")) +MAX_EXAMPLES = int(os.environ.get("MAX_EXAMPLES", "300")) CI = os.environ.get("CI", None) is not None -def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: +def get_test_cases() -> list[tuple[str, CarTestRoute | None]]: # build list of test cases test_cases = [] if not len(INTERNAL_SEG_LIST): @@ -51,7 +49,7 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: for i, c in enumerate(sorted(all_known_cars())): if i % NUM_JOBS == JOB_ID: - test_cases.extend(sorted((c.value, r) for r in routes_by_car.get(c, (None,)))) + test_cases.extend(sorted((c, r) for r in routes_by_car.get(c, (None,)))) else: segment_list = read_segment_list(os.path.join(BASEDIR, INTERNAL_SEG_LIST)) @@ -66,22 +64,14 @@ def get_test_cases() -> List[Tuple[str, Optional[CarTestRoute]]]: @pytest.mark.slow @pytest.mark.shared_download_cache class TestCarModelBase(unittest.TestCase): - car_model: Optional[str] = None - test_route: Optional[CarTestRoute] = None + car_model: str | None = None + test_route: CarTestRoute | None = None test_route_on_bucket: bool = True # whether the route is on the preserved CI bucket - can_msgs: List[capnp.lib.capnp._DynamicStructReader] + can_msgs: list[capnp.lib.capnp._DynamicStructReader] fingerprint: dict[int, dict[int, int]] - elm_frame: Optional[int] - car_safety_mode_frame: Optional[int] - - @classmethod - def get_logreader(cls, seg): - if len(INTERNAL_SEG_LIST): - route_name = RouteName(cls.test_route.route) - return LogReader(f"cd:/{route_name.dongle_id}/{route_name.time_str}/{seg}/rlog.bz2") - else: - return LogReader(get_url(cls.test_route.route, seg)) + elm_frame: int | None + car_safety_mode_frame: int | None @classmethod def get_testing_data_from_logreader(cls, lr): @@ -133,22 +123,26 @@ class TestCarModelBase(unittest.TestCase): if cls.test_route.segment is not None: test_segs = (cls.test_route.segment,) - # Try the primary method first (CI or internal) + is_internal = len(INTERNAL_SEG_LIST) + for seg in test_segs: + segment_range = f"{cls.test_route.route}/{seg}" + try: - lr = cls.get_logreader(seg) + lr = LogReader(segment_range, default_source=internal_source if is_internal else openpilotci_source) return cls.get_testing_data_from_logreader(lr) except Exception: pass # Route is not in CI bucket, assume either user has access (private), or it is public # test_route_on_ci_bucket will fail when running in CI - if not len(INTERNAL_SEG_LIST): + if not is_internal: cls.test_route_on_bucket = False for seg in test_segs: + segment_range = f"{cls.test_route.route}/{seg}" try: - lr = LogReader(Route(cls.test_route.route).log_paths()[seg]) + lr = LogReader(segment_range) return cls.get_testing_data_from_logreader(lr) except Exception: pass @@ -239,7 +233,6 @@ class TestCarModelBase(unittest.TestCase): self.assertEqual(can_invalid_cnt, 0) def test_radar_interface(self): - os.environ['NO_RADAR_SLEEP'] = "1" RadarInterface = importlib.import_module(f'selfdrive.car.{self.CP.carName}.radar_interface').RadarInterface RI = RadarInterface(self.CP) assert RI @@ -414,7 +407,7 @@ class TestCarModelBase(unittest.TestCase): controls_allowed_prev = False CS_prev = car.CarState.new_message() - checks = defaultdict(lambda: 0) + checks = defaultdict(int) controlsd = Controls(CI=self.CI) controlsd.initialized = True for idx, can in enumerate(self.can_msgs): diff --git a/selfdrive/car/torque_data/neural_ff_weights.json b/selfdrive/car/torque_data/neural_ff_weights.json new file mode 100644 index 0000000000..c526f07fa2 --- /dev/null +++ b/selfdrive/car/torque_data/neural_ff_weights.json @@ -0,0 +1 @@ +{"CHEVROLET BOLT EUV 2022": {"w_1": [[0.3452189564704895, -0.15614677965641022, -0.04062516987323761, -0.5960758328437805, 0.3211185932159424, 0.31732726097106934, -0.04430829733610153, -0.37327295541763306, -0.14118380844593048, 0.12712529301643372, 0.2641555070877075, -0.3451094627380371, -0.005127656273543835, 0.6185108423233032, 0.03725295141339302, 0.3763789236545563], [-0.0708412230014801, 0.3667356073856354, 0.031383827328681946, 0.1740853488445282, -0.04695861041545868, 0.018055908381938934, 0.009072160348296165, -0.23640218377113342, -0.10362917929887772, 0.022628149017691612, -0.224413201212883, 0.20718418061733246, -0.016947750002145767, -0.3872031271457672, -0.15500062704086304, -0.06375953555107117], [-0.0838046595454216, -0.0242826659232378, -0.07765661180019379, 0.028858814388513565, -0.09516210108995438, 0.008368706330657005, 0.1689300835132599, 0.015036891214549541, -0.15121428668498993, 0.1388195902109146, 0.11486363410949707, 0.0651545450091362, 0.13559958338737488, 0.04300367832183838, -0.13856294751167297, -0.058136988431215286], [-0.006249868310987949, 0.08809533715248108, -0.040690965950489044, 0.02359287068247795, -0.00766348373144865, 0.24816390872001648, -0.17360293865203857, -0.03676899895071983, -0.17564819753170013, 0.18998438119888306, -0.050583917647600174, -0.006488069426268339, 0.10649101436138153, -0.024557121098041534, -0.103276826441288, 0.18448011577129364]], "b_1": [0.2935388386249542, 0.10967712104320526, -0.014007942751049995, 0.211833655834198, 0.33605605363845825, 0.37722209095954895, -0.16615016758441925, 0.3134673535823822, 0.06695777177810669, 0.3425212800502777, 0.3769673705101013, 0.23186539113521576, 0.5770409107208252, -0.05929069593548775, 0.01839117519557476, 0.03828774020075798], "w_2": [[-0.06261160969734192, 0.010185074992477894, -0.06083013117313385, -0.04531499370932579, -0.08979734033346176, 0.3432150185108185, -0.019801849499344826, 0.3010321259498596], [0.19698476791381836, -0.009238275699317455, 0.08842222392559052, -0.09516377002000809, -0.05022778362035751, 0.13626104593276978, -0.052890390157699585, 0.15569131076335907], [0.0724768117070198, -0.09018408507108688, 0.06850195676088333, -0.025572121143341064, 0.0680626779794693, -0.07648195326328278, 0.07993496209383011, -0.059752143919467926], [1.267876386642456, -0.05755887180566788, -0.08429178595542908, 0.021366603672504425, -0.0006479775765910745, -1.4292563199996948, -0.08077696710824966, -1.414825439453125], [0.04535430669784546, 0.06555880606174469, -0.027145234867930412, -0.07661093026399612, -0.05702832341194153, 0.23650476336479187, 0.0024587824009358883, 0.20126521587371826], [0.006042032968252897, 0.042880818247795105, 0.002187949838116765, -0.017126334831118584, -0.08352015167474747, 0.19801731407642365, -0.029196614399552345, 0.23713473975658417], [-0.01644900068640709, -0.04358499124646187, 0.014584392309188843, 0.07155826687812805, -0.09354910999536514, -0.033351872116327286, 0.07138452678918839, -0.04755295440554619], [-1.1012420654296875, -0.03534531593322754, 0.02167935110628605, -0.01116552110761404, -0.08436500281095505, 1.1038788557052612, 0.027903547510504723, 1.0676132440567017], [0.03843916580080986, -0.0952216386795044, 0.039226632565259933, 0.002778085647150874, -0.020275786519050598, -0.07848760485649109, 0.04803166165947914, 0.015538203530013561], [0.018385495990514755, -0.025189843028783798, 0.0036680365446954966, -0.02105865254998207, 0.04808586835861206, 0.1575016975402832, 0.02703506126999855, 0.23039312660694122], [-0.0033881019335240126, -0.10210853815078735, -0.04877309128642082, 0.006989633198827505, 0.046798162162303925, 0.38676899671554565, -0.032304272055625916, 0.2345031052827835], [0.22092825174331665, -0.09642873704433441, 0.04499409720301628, 0.05108088254928589, -0.10191166400909424, 0.12818090617656708, -0.021021494641900063, 0.09440375864505768], [0.1212429478764534, -0.028194155544042587, -0.0981956496834755, 0.08226924389600754, 0.055346839129924774, 0.27067816257476807, -0.09064067900180817, 0.12580905854701996], [-1.6740131378173828, -0.02066155895590782, -0.05924689769744873, 0.06347910314798355, -0.07821853458881378, 1.2807466983795166, 0.04589352011680603, 1.310766577720642], [-0.09893272817134857, -0.04093599319458008, -0.02502273954451084, 0.09490344673395157, -0.0211324505507946, -0.09021010994911194, 0.07936318963766098, -0.03593116253614426], [-0.08490308374166489, -0.015558987855911255, -0.048692114651203156, -0.007421435788273811, -0.040531404316425323, 0.25889304280281067, 0.06012800335884094, 0.27946868538856506]], "b_2": [0.07973937690258026, -0.010446485131978989, -0.003066520905122161, -0.031895797699689865, 0.006032303906977177, 0.24106740951538086, -0.008969511836767197, 0.2872662842273712], "w_3": [[-1.364486813545227, -0.11682678014039993, 0.01764785870909691, 0.03926877677440643], [-0.05695437639951706, 0.05472218990325928, 0.1266128271818161, 0.09950875490903854], [0.11415273696184158, -0.10069356113672256, 0.0864749327301979, -0.043946366757154465], [-0.10138195008039474, -0.040128443390131, -0.08937158435583115, -0.0048376512713730335], [-0.0028251828625798225, -0.04743027314543724, 0.06340016424655914, 0.07277824729681015], [0.49482327699661255, -0.06410001963376999, -0.0999293103814125, -0.14250673353672028], [0.042802367359399796, 0.0015462725423276424, -0.05991362780332565, 0.1022040992975235], [0.3523194193840027, 0.07343732565641403, 0.04157765582203865, -0.12358107417821884]], "b_3": [0.2653026282787323, -0.058485131710767746, -0.0744510293006897, 0.012550175189971924], "w_4": [[0.5988775491714478, 0.09668736904859543], [-0.04360569268465042, 0.06491032242774963], [-0.11868984252214432, -0.09601487964391708], [-0.06554870307445526, -0.14189276099205017]], "b_4": [-0.08148707449436188, -2.8251802921295166], "input_norm_mat": [[-3.0, 3.0], [-3.0, 3.0], [0.0, 40.0], [-3.0, 3.0]], "output_norm_mat": [-1.0, 1.0], "temperature": 100.0}} \ No newline at end of file diff --git a/selfdrive/car/torque_data/override.toml b/selfdrive/car/torque_data/override.toml index 86723efb7b..339fc533e5 100644 --- a/selfdrive/car/torque_data/override.toml +++ b/selfdrive/car/torque_data/override.toml @@ -42,7 +42,7 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "CHEVROLET BOLT EUV 2022" = [2.0, 2.0, 0.05] "CHEVROLET SILVERADO 1500 2020" = [1.9, 1.9, 0.112] "CHEVROLET TRAILBLAZER 2021" = [1.33, 1.9, 0.16] -"CHEVROLET EQUINOX 2019" = [2.0, 2.0, 0.05] +"CHEVROLET EQUINOX 2019" = [2.5, 2.5, 0.05] "VOLKSWAGEN PASSAT NMS" = [2.5, 2.5, 0.1] "VOLKSWAGEN SHARAN 2ND GEN" = [2.5, 2.5, 0.1] "HYUNDAI SANTA CRUZ 1ST GEN" = [2.7, 2.7, 0.1] diff --git a/selfdrive/car/torque_data/substitute.toml b/selfdrive/car/torque_data/substitute.toml index bab10bc062..6822ef437b 100644 --- a/selfdrive/car/torque_data/substitute.toml +++ b/selfdrive/car/torque_data/substitute.toml @@ -5,12 +5,15 @@ legend = ["LAT_ACCEL_FACTOR", "MAX_LAT_ACCEL_MEASURED", "FRICTION"] "MAZDA CX-5 2022" = "MAZDA CX-9 2021" "MAZDA CX-9" = "MAZDA CX-9 2021" +"DODGE DURANGO 2021" = "CHRYSLER PACIFICA 2020" + "TOYOTA ALPHARD 2020" = "TOYOTA SIENNA 2018" "TOYOTA PRIUS v 2017" = "TOYOTA PRIUS 2017" "LEXUS IS 2018" = "LEXUS NX 2018" "LEXUS CT HYBRID 2018" = "LEXUS NX 2018" "LEXUS ES 2018" = "TOYOTA CAMRY 2018" "LEXUS RC 2020" = "LEXUS NX 2020" +"LEXUS LC 2024" = "LEXUS NX 2020" "KIA OPTIMA 4TH GEN" = "HYUNDAI SONATA 2020" "KIA OPTIMA 4TH GEN FACELIFT" = "HYUNDAI SONATA 2020" diff --git a/selfdrive/car/toyota/carcontroller.py b/selfdrive/car/toyota/carcontroller.py index e8ed9cfcb2..343b1d3031 100644 --- a/selfdrive/car/toyota/carcontroller.py +++ b/selfdrive/car/toyota/carcontroller.py @@ -55,10 +55,10 @@ class CarController: apply_steer = apply_meas_steer_torque_limits(new_steer, self.last_steer, CS.out.steeringTorqueEps, self.params) # >100 degree/sec steering fault prevention - self.steer_rate_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, CC.latActive, + self.steer_rate_counter, apply_steer_req = common_fault_avoidance(abs(CS.out.steeringRateDeg) >= MAX_STEER_RATE, lat_active, self.steer_rate_counter, MAX_STEER_RATE_FRAMES) - if not CC.latActive: + if not lat_active: apply_steer = 0 # *** steer angle *** diff --git a/selfdrive/car/toyota/carstate.py b/selfdrive/car/toyota/carstate.py index 2461fa9a26..e4ea0d30f9 100644 --- a/selfdrive/car/toyota/carstate.py +++ b/selfdrive/car/toyota/carstate.py @@ -95,6 +95,9 @@ class CarState(CarStateBase): ret.leftBlinker = cp.vl["BLINKERS_STATE"]["TURN_SIGNALS"] == 1 ret.rightBlinker = cp.vl["BLINKERS_STATE"]["TURN_SIGNALS"] == 2 + if self.CP.carFingerprint != CAR.MIRAI: + ret.engineRpm = cp.vl["ENGINE_RPM"]["RPM"] + ret.steeringTorque = cp.vl["STEER_TORQUE_SENSOR"]["STEER_TORQUE_DRIVER"] 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 @@ -180,6 +183,9 @@ class CarState(CarStateBase): ("STEER_TORQUE_SENSOR", 50), ] + if CP.carFingerprint != CAR.MIRAI: + messages.append(("ENGINE_RPM", 42)) + if CP.carFingerprint in UNSUPPORTED_DSU_CAR: messages.append(("DSU_CRUISE", 5)) messages.append(("PCM_CRUISE_ALT", 1)) diff --git a/selfdrive/car/toyota/fingerprints.py b/selfdrive/car/toyota/fingerprints.py index fc24f0e72c..12a1d46aaf 100644 --- a/selfdrive/car/toyota/fingerprints.py +++ b/selfdrive/car/toyota/fingerprints.py @@ -38,6 +38,7 @@ FW_VERSIONS = { b'F152607180\x00\x00\x00\x00\x00\x00', b'F152641040\x00\x00\x00\x00\x00\x00', b'F152641050\x00\x00\x00\x00\x00\x00', + b'F152641060\x00\x00\x00\x00\x00\x00', b'F152641061\x00\x00\x00\x00\x00\x00', ], (Ecu.dsu, 0x791, None): [ @@ -55,10 +56,12 @@ FW_VERSIONS = { b'\x01896630725100\x00\x00\x00\x00', b'\x01896630725200\x00\x00\x00\x00', b'\x01896630725300\x00\x00\x00\x00', + b'\x01896630725400\x00\x00\x00\x00', b'\x01896630735100\x00\x00\x00\x00', b'\x01896630738000\x00\x00\x00\x00', b'\x02896630724000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', b'\x02896630728000\x00\x00\x00\x00897CF3302002\x00\x00\x00\x00', + b'\x02896630734000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', b'\x02896630737000\x00\x00\x00\x00897CF3305001\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1361,16 +1364,19 @@ FW_VERSIONS = { b'\x018966378G3000\x00\x00\x00\x00', ], (Ecu.engine, 0x7e0, None): [ + b'\x0237881000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', 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', b'\x02378F4000\x00\x00\x00\x00\x00\x00\x00\x00A4701000\x00\x00\x00\x00\x00\x00\x00\x00', ], (Ecu.abs, 0x7b0, None): [ b'\x01F152678221\x00\x00\x00\x00\x00\x00', + b'F152678200\x00\x00\x00\x00\x00\x00', b'F152678210\x00\x00\x00\x00\x00\x00', b'F152678211\x00\x00\x00\x00\x00\x00', ], (Ecu.eps, 0x7a1, None): [ + b'8965B78110\x00\x00\x00\x00\x00\x00', b'8965B78120\x00\x00\x00\x00\x00\x00', ], (Ecu.fwdRadar, 0x750, 0xf): [ @@ -1383,6 +1389,23 @@ FW_VERSIONS = { b'\x028646F7803100\x00\x00\x00\x008646G2601400\x00\x00\x00\x00', ], }, + CAR.LEXUS_LC_TSS2: { + (Ecu.engine, 0x7e0, None): [ + b'\x0131130000\x00\x00\x00\x00\x00\x00\x00\x00', + ], + (Ecu.abs, 0x7b0, None): [ + b'F152611390\x00\x00\x00\x00\x00\x00', + ], + (Ecu.eps, 0x7a1, None): [ + b'8965B11091\x00\x00\x00\x00\x00\x00', + ], + (Ecu.fwdRadar, 0x750, 0xf): [ + b'\x018821F6201400\x00\x00\x00\x00', + ], + (Ecu.fwdCamera, 0x750, 0x6d): [ + b'\x028646F1105200\x00\x00\x00\x008646G3304000\x00\x00\x00\x00', + ], + }, CAR.LEXUS_RC: { (Ecu.engine, 0x700, None): [ b'\x01896632461100\x00\x00\x00\x00', @@ -1417,6 +1440,7 @@ FW_VERSIONS = { }, CAR.LEXUS_RX: { (Ecu.engine, 0x700, None): [ + b'\x01896630E36100\x00\x00\x00\x00', b'\x01896630E36200\x00\x00\x00\x00', b'\x01896630E36300\x00\x00\x00\x00', b'\x01896630E37100\x00\x00\x00\x00', diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index 90b2b760fe..988b1b4547 100644 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -181,6 +181,12 @@ class CarInterface(CarInterfaceBase): ret.tireStiffnessFactor = 0.444 # not optimized yet ret.mass = 4070 * CV.LB_TO_KG + elif candidate == CAR.LEXUS_LC_TSS2: + ret.wheelbase = 2.87 + ret.steerRatio = 13.0 + ret.tireStiffnessFactor = 0.444 # not optimized yet + ret.mass = 4500 * CV.LB_TO_KG + elif candidate == CAR.PRIUS_TSS2: ret.wheelbase = 2.70002 # from toyota online sepc. ret.steerRatio = 13.4 # True steerRatio from older prius @@ -219,20 +225,16 @@ class CarInterface(CarInterfaceBase): 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 | UNSUPPORTED_DSU_CAR) \ and not (ret.flags & ToyotaFlags.SMART_DSU) - ret.enableGasInterceptor = 0x201 in fingerprint[0] - - if ret.enableGasInterceptor: - ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_GAS_INTERCEPTOR # if the smartDSU is detected, openpilot can send ACC_CONTROL and the smartDSU will block it from the DSU or radar. # since we don't yet parse radar on TSS2/TSS-P radar-based ACC cars, gate longitudinal behind experimental toggle use_sdsu = bool(ret.flags & ToyotaFlags.SMART_DSU) if candidate in (RADAR_ACC_CAR | NO_DSU_CAR): - ret.experimentalLongitudinalAvailable = use_sdsu + ret.experimentalLongitudinalAvailable = use_sdsu or candidate in RADAR_ACC_CAR if not use_sdsu: # Disabling radar is only supported on TSS2 radar-ACC cars - if experimental_long and candidate in RADAR_ACC_CAR and False: # TODO: disabling radar isn't supported yet + if experimental_long and candidate in RADAR_ACC_CAR: ret.flags |= ToyotaFlags.DISABLE_RADAR.value else: use_sdsu = use_sdsu and experimental_long @@ -247,10 +249,14 @@ class CarInterface(CarInterfaceBase): # - TSS-P DSU-less cars w/ CAN filter installed (no radar parser yet) ret.openpilotLongitudinalControl = use_sdsu or ret.enableDsu or candidate in (TSS2_CAR - RADAR_ACC_CAR) or bool(ret.flags & ToyotaFlags.DISABLE_RADAR.value) ret.autoResumeSng = ret.openpilotLongitudinalControl and candidate in NO_STOP_TIMER_CAR + ret.enableGasInterceptor = 0x201 in fingerprint[0] and ret.openpilotLongitudinalControl if not ret.openpilotLongitudinalControl: ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_STOCK_LONGITUDINAL + if ret.enableGasInterceptor: + ret.safetyConfigs[0].safetyParam |= Panda.FLAG_TOYOTA_GAS_INTERCEPTOR + # min speed to enable ACC. if car can do stop and go, then set enabling speed # to a negative value, so it won't matter. ret.minEnableSpeed = -1. if (stop_and_go or ret.enableGasInterceptor) else MIN_ACC_SPEED diff --git a/selfdrive/car/toyota/values.py b/selfdrive/car/toyota/values.py index 02a9e142d2..011d153f70 100644 --- a/selfdrive/car/toyota/values.py +++ b/selfdrive/car/toyota/values.py @@ -2,7 +2,6 @@ import re from collections import defaultdict from dataclasses import dataclass, field from enum import Enum, IntFlag, StrEnum -from typing import Dict, List, Set, Union from cereal import car from openpilot.common.conversions import Conversions as CV @@ -81,6 +80,7 @@ class CAR(StrEnum): LEXUS_IS_TSS2 = "LEXUS IS 2023" LEXUS_NX = "LEXUS NX 2018" LEXUS_NX_TSS2 = "LEXUS NX 2020" + LEXUS_LC_TSS2 = "LEXUS LC 2024" LEXUS_RC = "LEXUS RC 2020" LEXUS_RX = "LEXUS RX 2016" LEXUS_RX_TSS2 = "LEXUS RX 2020" @@ -99,7 +99,7 @@ class ToyotaCarInfo(CarInfo): car_parts: CarParts = field(default_factory=CarParts.common([CarHarness.toyota_a])) -CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { +CAR_INFO: dict[str, ToyotaCarInfo | list[ToyotaCarInfo]] = { # Toyota CAR.ALPHARD_TSS2: [ ToyotaCarInfo("Toyota Alphard 2019-20"), @@ -206,6 +206,7 @@ CAR_INFO: Dict[str, Union[ToyotaCarInfo, List[ToyotaCarInfo]]] = { ToyotaCarInfo("Lexus NX 2020-21"), ToyotaCarInfo("Lexus NX Hybrid 2020-21"), ], + CAR.LEXUS_LC_TSS2: ToyotaCarInfo("Lexus LC 2024"), CAR.LEXUS_RC: ToyotaCarInfo("Lexus RC 2018-20"), CAR.LEXUS_RX: [ ToyotaCarInfo("Lexus RX 2016", "Lexus Safety System+"), @@ -251,7 +252,7 @@ STATIC_DSU_MSGS = [ ] -def get_platform_codes(fw_versions: List[bytes]) -> Dict[bytes, Set[bytes]]: +def get_platform_codes(fw_versions: list[bytes]) -> dict[bytes, set[bytes]]: # Returns sub versions in a dict so comparisons can be made within part-platform-major_version combos codes = defaultdict(set) # Optional[part]-platform-major_version: set of sub_version for fw in fw_versions: @@ -295,7 +296,7 @@ def get_platform_codes(fw_versions: List[bytes]) -> Dict[bytes, Set[bytes]]: return dict(codes) -def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> Set[str]: +def match_fw_to_car_fuzzy(live_fw_versions, offline_fw_versions) -> set[str]: candidates = set() for candidate, fws in offline_fw_versions.items(): @@ -444,6 +445,7 @@ DBC = { CAR.PRIUS: dbc_dict('toyota_nodsu_pt_generated', 'toyota_adas'), CAR.PRIUS_V: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), CAR.COROLLA: dbc_dict('toyota_new_mc_pt_generated', 'toyota_adas'), + CAR.LEXUS_LC_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), CAR.LEXUS_RC: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_RX: dbc_dict('toyota_tnga_k_pt_generated', 'toyota_adas'), CAR.LEXUS_RX_TSS2: dbc_dict('toyota_nodsu_pt_generated', 'toyota_tss2_adas'), @@ -480,7 +482,8 @@ EPS_SCALE = defaultdict(lambda: 73, {CAR.PRIUS: 66, CAR.COROLLA: 88, CAR.LEXUS_I # Toyota/Lexus Safety Sense 2.0 and 2.5 TSS2_CAR = {CAR.RAV4_TSS2, CAR.RAV4_TSS2_2022, CAR.RAV4_TSS2_2023, CAR.COROLLA_TSS2, CAR.LEXUS_ES_TSS2, CAR.LEXUS_RX_TSS2, CAR.HIGHLANDER_TSS2, CAR.PRIUS_TSS2, CAR.CAMRY_TSS2, CAR.LEXUS_IS_TSS2, - CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, CAR.CHR_TSS2} + CAR.MIRAI, CAR.LEXUS_NX_TSS2, CAR.LEXUS_LC_TSS2, CAR.ALPHARD_TSS2, CAR.AVALON_TSS2, + CAR.CHR_TSS2} NO_DSU_CAR = TSS2_CAR | {CAR.CHR, CAR.CAMRY} diff --git a/selfdrive/car/values.py b/selfdrive/car/values.py new file mode 100644 index 0000000000..3e27870915 --- /dev/null +++ b/selfdrive/car/values.py @@ -0,0 +1,18 @@ +from typing import cast +from openpilot.selfdrive.car.body.values import CAR as BODY +from openpilot.selfdrive.car.chrysler.values import CAR as CHRYSLER +from openpilot.selfdrive.car.ford.values import CAR as FORD +from openpilot.selfdrive.car.gm.values import CAR as GM +from openpilot.selfdrive.car.honda.values import CAR as HONDA +from openpilot.selfdrive.car.hyundai.values import CAR as HYUNDAI +from openpilot.selfdrive.car.mazda.values import CAR as MAZDA +from openpilot.selfdrive.car.nissan.values import CAR as NISSAN +from openpilot.selfdrive.car.subaru.values import CAR as SUBARU +from openpilot.selfdrive.car.tesla.values import CAR as TESLA +from openpilot.selfdrive.car.toyota.values import CAR as TOYOTA +from openpilot.selfdrive.car.volkswagen.values import CAR as VOLKSWAGEN + +Platform = BODY | CHRYSLER | FORD | GM | HONDA | HYUNDAI | MAZDA | NISSAN | SUBARU | TESLA | TOYOTA | VOLKSWAGEN +BRANDS = [BODY, CHRYSLER, FORD, GM, HONDA, HYUNDAI, MAZDA, NISSAN, SUBARU, TESLA, TOYOTA, VOLKSWAGEN] + +PLATFORMS: dict[str, Platform] = {str(platform): platform for brand in BRANDS for platform in cast(list[Platform], brand)} diff --git a/selfdrive/car/vin.py b/selfdrive/car/vin.py index f69771546f..e668c35f7d 100755 --- a/selfdrive/car/vin.py +++ b/selfdrive/car/vin.py @@ -15,35 +15,42 @@ def is_valid_vin(vin: str): return re.fullmatch(VIN_RE, vin) is not None -def get_vin(logcan, sendcan, buses, timeout=0.1, retry=3, debug=False): +def get_vin(logcan, sendcan, buses, timeout=0.1, retry=2, debug=False): for i in range(retry): for bus in buses: for request, response, valid_buses, vin_addrs, functional_addrs, rx_offset in ( (StdQueries.UDS_VIN_REQUEST, StdQueries.UDS_VIN_RESPONSE, (0, 1), STANDARD_VIN_ADDRS, FUNCTIONAL_ADDRS, 0x8), (StdQueries.OBD_VIN_REQUEST, StdQueries.OBD_VIN_RESPONSE, (0, 1), STANDARD_VIN_ADDRS, FUNCTIONAL_ADDRS, 0x8), (StdQueries.GM_VIN_REQUEST, StdQueries.GM_VIN_RESPONSE, (0,), [0x24b], None, 0x400), # Bolt fwdCamera + (StdQueries.KWP_VIN_REQUEST, StdQueries.KWP_VIN_RESPONSE, (0,), [0x797], None, 0x3), # Nissan Leaf VCM ): if bus not in valid_buses: continue + # When querying functional addresses, ideally we respond to everything that sends a first frame to avoid leaving the + # ECU in a temporary bad state. Note that we may not cover all ECUs and response offsets. TODO: query physical addrs + tx_addrs = vin_addrs + if functional_addrs is not None: + tx_addrs = [a for a in range(0x700, 0x800) if a != 0x7DF] + list(range(0x18DA00F1, 0x18DB00F1, 0x100)) + try: - query = IsoTpParallelQuery(sendcan, logcan, bus, vin_addrs, [request, ], [response, ], response_offset=rx_offset, + query = IsoTpParallelQuery(sendcan, logcan, bus, tx_addrs, [request, ], [response, ], response_offset=rx_offset, functional_addrs=functional_addrs, debug=debug) results = query.get_data(timeout) for addr in vin_addrs: vin = results.get((addr, None)) if vin is not None: - # Ford pads with null bytes - if len(vin) == 24: + # Ford and Nissan pads with null bytes + if len(vin) in (19, 24): vin = re.sub(b'\x00*$', b'', vin) # Honda Bosch response starts with a length, trim to correct length if vin.startswith(b'\x11'): vin = vin[1:18] - cloudlog.warning(f"got vin with {request=}") - return get_rx_addr_for_tx_addr(addr), bus, vin.decode() + cloudlog.error(f"got vin with {request=}") + return get_rx_addr_for_tx_addr(addr, rx_offset=rx_offset), bus, vin.decode() except Exception: cloudlog.exception("VIN query exception") diff --git a/selfdrive/car/volkswagen/fingerprints.py b/selfdrive/car/volkswagen/fingerprints.py index dcdb2e62bf..eab2bc0090 100644 --- a/selfdrive/car/volkswagen/fingerprints.py +++ b/selfdrive/car/volkswagen/fingerprints.py @@ -320,6 +320,7 @@ FW_VERSIONS = { b'\xf1\x8704E906024L \xf1\x895595', b'\xf1\x8704E906024L \xf1\x899970', b'\xf1\x8704E906027MS\xf1\x896223', + b'\xf1\x8705E906013DB\xf1\x893361', b'\xf1\x875G0906259T \xf1\x890003', ], (Ecu.transmission, 0x7e1, None): [ @@ -327,6 +328,7 @@ FW_VERSIONS = { b'\xf1\x8709S927158BS\xf1\x893642', b'\xf1\x8709S927158BS\xf1\x893694', b'\xf1\x8709S927158CK\xf1\x893770', + b'\xf1\x8709S927158JC\xf1\x894113', b'\xf1\x8709S927158R \xf1\x893552', b'\xf1\x8709S927158R \xf1\x893587', b'\xf1\x870GC300020N \xf1\x892803', @@ -340,9 +342,11 @@ FW_VERSIONS = { b'\xf1\x875Q0959655BR\xf1\x890403\xf1\x82\x1319170031313300314240011550159333463100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314642021650169333613100', b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1314171231313500314643021650169333613100', + b'\xf1\x875Q0959655CB\xf1\x890421\xf1\x82\x1317171231313500314642023050309333613100', ], (Ecu.eps, 0x712, None): [ b'\xf1\x873Q0909144M \xf1\x895082\xf1\x82\x0571A10A11A1', + b'\xf1\x875QM907144D \xf1\x891063\xf1\x82\x000_A1080_OM', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521A10A01A1', b'\xf1\x875QM909144B \xf1\x891081\xf1\x82\x0521B00404A1', b'\xf1\x875QM909144C \xf1\x891082\xf1\x82\x0521A00642A1', @@ -350,6 +354,7 @@ FW_VERSIONS = { b'\xf1\x875QN909144B \xf1\x895082\xf1\x82\x0571A10A11A1', ], (Ecu.fwdRadar, 0x757, None): [ + b'\xf1\x872Q0907572AA\xf1\x890396', b'\xf1\x875Q0907572N \xf1\x890681', b'\xf1\x875Q0907572P \xf1\x890682', b'\xf1\x875Q0907572R \xf1\x890771', @@ -1038,6 +1043,7 @@ FW_VERSIONS = { ], (Ecu.srs, 0x715, None): [ b'\xf1\x873Q0959655AC\xf1\x890200\xf1\x82\r11120011100010022212110200', + b'\xf1\x873Q0959655AK\xf1\x890306\xf1\x82\r31210031210021033733310331', b'\xf1\x873Q0959655AP\xf1\x890305\xf1\x82\r11110011110011213331312131', b'\xf1\x873Q0959655AQ\xf1\x890200\xf1\x82\r11120011100010312212113100', b'\xf1\x873Q0959655AS\xf1\x890200\xf1\x82\r11120011100010022212110200', @@ -1057,6 +1063,7 @@ FW_VERSIONS = { (Ecu.fwdRadar, 0x757, None): [ b'\xf1\x875Q0907572D \xf1\x890304\xf1\x82\x0101', b'\xf1\x875Q0907572F \xf1\x890400\xf1\x82\x0101', + b'\xf1\x875Q0907572H \xf1\x890620', b'\xf1\x875Q0907572J \xf1\x890654', b'\xf1\x875Q0907572K \xf1\x890402\xf1\x82\x0101', b'\xf1\x875Q0907572P \xf1\x890682', diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index 710e779d0a..fa68e81d0d 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,9 +1,8 @@ from cereal import car from panda import Panda -from openpilot.common.conversions import Conversions as CV from openpilot.selfdrive.car import get_safety_config from openpilot.selfdrive.car.interfaces import CarInterfaceBase -from openpilot.selfdrive.car.volkswagen.values import CAR, PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags +from openpilot.selfdrive.car.volkswagen.values import PQ_CARS, CANBUS, NetworkLocation, TransmissionType, GearShifter, VolkswagenFlags ButtonType = car.CarState.ButtonEvent.Type EventName = car.CarEvent.EventName @@ -72,14 +71,17 @@ class CarInterface(CarInterfaceBase): # Global lateral tuning defaults, can be overridden per-vehicle - ret.steerActuatorDelay = 0.1 ret.steerLimitTimer = 0.4 - ret.steerRatio = 15.6 # Let the params learner figure this out - ret.lateralTuning.pid.kpBP = [0.] - ret.lateralTuning.pid.kiBP = [0.] - ret.lateralTuning.pid.kf = 0.00006 - ret.lateralTuning.pid.kpV = [0.6] - ret.lateralTuning.pid.kiV = [0.2] + if candidate in PQ_CARS: + ret.steerActuatorDelay = 0.2 + CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) + else: + ret.steerActuatorDelay = 0.1 + ret.lateralTuning.pid.kpBP = [0.] + ret.lateralTuning.pid.kiBP = [0.] + ret.lateralTuning.pid.kf = 0.00006 + ret.lateralTuning.pid.kpV = [0.6] + ret.lateralTuning.pid.kiV = [0.2] # Global longitudinal tuning defaults, can be overridden per-vehicle @@ -101,125 +103,6 @@ class CarInterface(CarInterfaceBase): # Per-chassis tuning values, override tuning defaults here if desired - if candidate == CAR.ARTEON_MK1: - ret.mass = 1733 - ret.wheelbase = 2.84 - - elif candidate == CAR.ATLAS_MK1: - ret.mass = 2011 - ret.wheelbase = 2.98 - - elif candidate == CAR.CRAFTER_MK2: - ret.mass = 2100 - 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 - ret.wheelbase = 2.62 - - elif candidate == CAR.JETTA_MK7: - ret.mass = 1328 - ret.wheelbase = 2.71 - - elif candidate == CAR.PASSAT_MK8: - ret.mass = 1551 - ret.wheelbase = 2.79 - - elif candidate == CAR.PASSAT_NMS: - ret.mass = 1503 - ret.wheelbase = 2.80 - ret.minEnableSpeed = 20 * CV.KPH_TO_MS # ACC "basic", no FtS - ret.minSteerSpeed = 50 * CV.KPH_TO_MS - ret.steerActuatorDelay = 0.2 - CarInterfaceBase.configure_torque_tune(candidate, ret.lateralTuning) - - elif candidate == CAR.POLO_MK6: - ret.mass = 1230 - ret.wheelbase = 2.55 - - elif candidate == CAR.SHARAN_MK2: - ret.mass = 1639 - ret.wheelbase = 2.92 - ret.minSteerSpeed = 50 * CV.KPH_TO_MS - ret.steerActuatorDelay = 0.2 - - elif candidate == CAR.TAOS_MK1: - ret.mass = 1498 - ret.wheelbase = 2.69 - - elif candidate == CAR.TCROSS_MK1: - ret.mass = 1150 - ret.wheelbase = 2.60 - - elif candidate == CAR.TIGUAN_MK2: - ret.mass = 1715 - ret.wheelbase = 2.74 - - elif candidate == CAR.TOURAN_MK2: - ret.mass = 1516 - ret.wheelbase = 2.79 - - elif candidate == CAR.TRANSPORTER_T61: - ret.mass = 1926 - ret.wheelbase = 3.00 # SWB, LWB is 3.40, TBD how to detect difference - ret.minSteerSpeed = 14.0 - - elif candidate == CAR.TROC_MK1: - ret.mass = 1413 - ret.wheelbase = 2.63 - - elif candidate == CAR.AUDI_A3_MK3: - ret.mass = 1335 - ret.wheelbase = 2.61 - - elif candidate == CAR.AUDI_Q2_MK1: - ret.mass = 1205 - ret.wheelbase = 2.61 - - elif candidate == CAR.AUDI_Q3_MK2: - ret.mass = 1623 - ret.wheelbase = 2.68 - - elif candidate == CAR.SEAT_ATECA_MK1: - ret.mass = 1900 - ret.wheelbase = 2.64 - - elif candidate == CAR.SEAT_LEON_MK3: - ret.mass = 1227 - ret.wheelbase = 2.64 - - elif candidate == CAR.SKODA_FABIA_MK4: - ret.mass = 1266 - ret.wheelbase = 2.56 - - elif candidate == CAR.SKODA_KAMIQ_MK1: - ret.mass = 1265 - ret.wheelbase = 2.66 - - elif candidate == CAR.SKODA_KAROQ_MK1: - ret.mass = 1278 - ret.wheelbase = 2.66 - - elif candidate == CAR.SKODA_KODIAQ_MK1: - ret.mass = 1569 - ret.wheelbase = 2.79 - - elif candidate == CAR.SKODA_OCTAVIA_MK3: - ret.mass = 1388 - ret.wheelbase = 2.68 - - elif candidate == CAR.SKODA_SCALA_MK1: - ret.mass = 1192 - ret.wheelbase = 2.65 - - elif candidate == CAR.SKODA_SUPERB_MK3: - ret.mass = 1505 - ret.wheelbase = 2.84 - - else: - raise ValueError(f"unsupported car {candidate}") - ret.autoResumeSng = ret.minEnableSpeed == -1 ret.centerToFront = ret.wheelbase * 0.45 return ret diff --git a/selfdrive/car/volkswagen/values.py b/selfdrive/car/volkswagen/values.py index f4809e4523..f029740284 100644 --- a/selfdrive/car/volkswagen/values.py +++ b/selfdrive/car/volkswagen/values.py @@ -1,12 +1,12 @@ -from collections import defaultdict, namedtuple +from collections import namedtuple from dataclasses import dataclass, field -from enum import Enum, IntFlag, StrEnum -from typing import Dict, List, Union +from enum import Enum, IntFlag from cereal import car from panda.python import uds from opendbc.can.can_define import CANDefine -from openpilot.selfdrive.car import dbc_dict +from openpilot.common.conversions import Conversions as CV +from openpilot.selfdrive.car import dbc_dict, CarSpecs, DbcDict, PlatformConfig, Platforms from openpilot.selfdrive.car.docs_definitions import CarFootnote, CarHarness, CarInfo, CarParts, Column, \ Device from openpilot.selfdrive.car.fw_query_definitions import FwQueryConfig, Request, p16 @@ -112,49 +112,17 @@ class CANBUS: class VolkswagenFlags(IntFlag): STOCK_HCA_PRESENT = 1 +@dataclass +class VolkswagenMQBPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_mqb_2010', None)) -# Check the 7th and 8th characters of the VIN before adding a new CAR. If the -# chassis code is already listed below, don't add a new CAR, just add to the -# FW_VERSIONS for that existing CAR. -# Exception: SEAT Leon and SEAT Ateca share a chassis code - -class CAR(StrEnum): - 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 - PASSAT_NMS = "VOLKSWAGEN PASSAT NMS" # Chassis A3, North America/China/Mideast NMS Passat, incl. facelift - POLO_MK6 = "VOLKSWAGEN POLO 6TH GEN" # Chassis AW, Mk6 VW Polo - SHARAN_MK2 = "VOLKSWAGEN SHARAN 2ND GEN" # Chassis 7N, Mk2 Volkswagen Sharan and SEAT Alhambra - TAOS_MK1 = "VOLKSWAGEN TAOS 1ST GEN" # Chassis B2, Mk1 VW Taos and Tharu - TCROSS_MK1 = "VOLKSWAGEN T-CROSS 1ST GEN" # Chassis C1, Mk1 VW T-Cross SWB and LWB variants - 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 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 - SKODA_SCALA_MK1 = "SKODA SCALA 1ST GEN" # Chassis NW, Mk1 Skoda Scala and Skoda Kamiq - SKODA_SUPERB_MK3 = "SKODA SUPERB 3RD GEN" # Chassis 3V/NP, Mk3 Skoda Superb and variants - SKODA_OCTAVIA_MK3 = "SKODA OCTAVIA 3RD GEN" # Chassis NE, Mk3 Skoda Octavia and variants - - -PQ_CARS = {CAR.PASSAT_NMS, CAR.SHARAN_MK2} - - -DBC: Dict[str, Dict[str, str]] = defaultdict(lambda: dbc_dict("vw_mqb_2010", None)) -for car_type in PQ_CARS: - DBC[car_type] = dbc_dict("vw_golf_mk4", None) +@dataclass +class VolkswagenPQPlatformConfig(PlatformConfig): + dbc_dict: DbcDict = field(default_factory=lambda: dbc_dict('vw_golf_mk4', None)) +@dataclass(kw_only=True) +class VolkswagenCarSpecs(CarSpecs): + steerRatio: float = field(default=15.6) class Footnote(Enum): KAMIQ = CarFootnote( @@ -190,90 +158,202 @@ class VWCarInfo(CarInfo): if CP.carFingerprint in (CAR.CRAFTER_MK2, CAR.TRANSPORTER_T61): self.car_parts = CarParts([Device.threex_angled_mount, CarHarness.j533]) +# Check the 7th and 8th characters of the VIN before adding a new CAR. If the +# chassis code is already listed below, don't add a new CAR, just add to the +# FW_VERSIONS for that existing CAR. +# Exception: SEAT Leon and SEAT Ateca share a chassis code -CAR_INFO: Dict[str, Union[VWCarInfo, List[VWCarInfo]]] = { - CAR.ARTEON_MK1: [ - VWCarInfo("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), - VWCarInfo("Volkswagen Arteon eHybrid 2020-23", 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"), - VWCarInfo("Volkswagen Atlas Cross Sport 2020-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", auto_resume=False), - VWCarInfo("Volkswagen Golf Alltrack 2015-19", auto_resume=False), - VWCarInfo("Volkswagen Golf GTD 2015-20"), - VWCarInfo("Volkswagen Golf GTE 2015-20"), - VWCarInfo("Volkswagen Golf GTI 2015-21", auto_resume=False), - VWCarInfo("Volkswagen Golf R 2015-19"), - VWCarInfo("Volkswagen Golf SportsVan 2015-20"), - ], - CAR.JETTA_MK7: [ - VWCarInfo("Volkswagen Jetta 2018-22"), - VWCarInfo("Volkswagen Jetta GLI 2021-22"), - ], - CAR.PASSAT_MK8: [ - 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"), - CAR.POLO_MK6: [ - VWCarInfo("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), - VWCarInfo("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), - ], - CAR.SHARAN_MK2: [ - VWCarInfo("Volkswagen Sharan 2018-22"), - VWCarInfo("SEAT Alhambra 2018-20"), - ], - CAR.TAOS_MK1: VWCarInfo("Volkswagen Taos 2022-23"), - CAR.TCROSS_MK1: VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), - CAR.TIGUAN_MK2: [ - VWCarInfo("Volkswagen Tiguan 2018-24"), - VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"), - ], - CAR.TOURAN_MK2: VWCarInfo("Volkswagen Touran 2016-23"), - CAR.TRANSPORTER_T61: [ - VWCarInfo("Volkswagen Caravelle 2020"), - VWCarInfo("Volkswagen California 2021-23"), - ], - CAR.TROC_MK1: VWCarInfo("Volkswagen T-Roc 2018-22", footnotes=[Footnote.VW_MQB_A0]), - CAR.AUDI_A3_MK3: [ - VWCarInfo("Audi A3 2014-19"), - VWCarInfo("Audi A3 Sportback e-tron 2017-18"), - VWCarInfo("Audi RS3 2018"), - VWCarInfo("Audi S3 2015-17"), - ], - CAR.AUDI_Q2_MK1: VWCarInfo("Audi Q2 2018"), - 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_FABIA_MK4: VWCarInfo("Å koda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0]), - CAR.SKODA_KAMIQ_MK1: VWCarInfo("Å koda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), - CAR.SKODA_KAROQ_MK1: VWCarInfo("Å koda Karoq 2019-23"), - CAR.SKODA_KODIAQ_MK1: VWCarInfo("Å koda Kodiaq 2017-23"), - CAR.SKODA_SCALA_MK1: VWCarInfo("Å koda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), - CAR.SKODA_SUPERB_MK3: VWCarInfo("Å koda Superb 2015-22"), - CAR.SKODA_OCTAVIA_MK3: [ - VWCarInfo("Å koda Octavia 2015-19"), - VWCarInfo("Å koda Octavia RS 2016"), - ], -} +class CAR(Platforms): + ARTEON_MK1 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN ARTEON 1ST GEN", # Chassis AN + [ + VWCarInfo("Volkswagen Arteon 2018-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon R 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen Arteon eHybrid 2020-23", video_link="https://youtu.be/FAomFKPFlDA"), + VWCarInfo("Volkswagen CC 2018-22", video_link="https://youtu.be/FAomFKPFlDA"), + ], + specs=VolkswagenCarSpecs(mass=1733, wheelbase=2.84), + ) + ATLAS_MK1 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN ATLAS 1ST GEN", # Chassis CA + [ + VWCarInfo("Volkswagen Atlas 2018-23"), + VWCarInfo("Volkswagen Atlas Cross Sport 2020-22"), + VWCarInfo("Volkswagen Teramont 2018-22"), + VWCarInfo("Volkswagen Teramont Cross Sport 2021-22"), + VWCarInfo("Volkswagen Teramont X 2021-22"), + ], + specs=VolkswagenCarSpecs(mass=2011, wheelbase=2.98), + ) + CRAFTER_MK2 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN CRAFTER 2ND GEN", # Chassis SY/SZ + [ + 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"), + ], + specs=VolkswagenCarSpecs(mass=2100, wheelbase=3.64, minSteerSpeed=50 * CV.KPH_TO_MS), + ) + GOLF_MK7 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN GOLF 7TH GEN", # Chassis 5G/AU/BA/BE + [ + VWCarInfo("Volkswagen e-Golf 2014-20"), + VWCarInfo("Volkswagen Golf 2015-20", auto_resume=False), + VWCarInfo("Volkswagen Golf Alltrack 2015-19", auto_resume=False), + VWCarInfo("Volkswagen Golf GTD 2015-20"), + VWCarInfo("Volkswagen Golf GTE 2015-20"), + VWCarInfo("Volkswagen Golf GTI 2015-21", auto_resume=False), + VWCarInfo("Volkswagen Golf R 2015-19"), + VWCarInfo("Volkswagen Golf SportsVan 2015-20"), + ], + specs=VolkswagenCarSpecs(mass=1397, wheelbase=2.62), + ) + JETTA_MK7 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN JETTA 7TH GEN", # Chassis BU + [ + VWCarInfo("Volkswagen Jetta 2018-24"), + VWCarInfo("Volkswagen Jetta GLI 2021-24"), + ], + specs=VolkswagenCarSpecs(mass=1328, wheelbase=2.71), + ) + PASSAT_MK8 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN PASSAT 8TH GEN", # Chassis 3G + [ + VWCarInfo("Volkswagen Passat 2015-22", footnotes=[Footnote.PASSAT]), + VWCarInfo("Volkswagen Passat Alltrack 2015-22"), + VWCarInfo("Volkswagen Passat GTE 2015-22"), + ], + specs=VolkswagenCarSpecs(mass=1551, wheelbase=2.79), + ) + PASSAT_NMS = VolkswagenPQPlatformConfig( + "VOLKSWAGEN PASSAT NMS", # Chassis A3 + VWCarInfo("Volkswagen Passat NMS 2017-22"), + specs=VolkswagenCarSpecs(mass=1503, wheelbase=2.80, minSteerSpeed=50*CV.KPH_TO_MS, minEnableSpeed=20*CV.KPH_TO_MS), + ) + POLO_MK6 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN POLO 6TH GEN", # Chassis AW + [ + VWCarInfo("Volkswagen Polo 2018-23", footnotes=[Footnote.VW_MQB_A0]), + VWCarInfo("Volkswagen Polo GTI 2018-23", footnotes=[Footnote.VW_MQB_A0]), + ], + specs=VolkswagenCarSpecs(mass=1230, wheelbase=2.55), + ) + SHARAN_MK2 = VolkswagenPQPlatformConfig( + "VOLKSWAGEN SHARAN 2ND GEN", # Chassis 7N + [ + VWCarInfo("Volkswagen Sharan 2018-22"), + VWCarInfo("SEAT Alhambra 2018-20"), + ], + specs=VolkswagenCarSpecs(mass=1639, wheelbase=2.92, minSteerSpeed=50*CV.KPH_TO_MS), + ) + TAOS_MK1 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN TAOS 1ST GEN", # Chassis B2 + VWCarInfo("Volkswagen Taos 2022-23"), + specs=VolkswagenCarSpecs(mass=1498, wheelbase=2.69), + ) + TCROSS_MK1 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN T-CROSS 1ST GEN", # Chassis C1 + car_info=VWCarInfo("Volkswagen T-Cross 2021", footnotes=[Footnote.VW_MQB_A0]), + specs=VolkswagenCarSpecs(mass=1150, wheelbase=2.60), + ) + TIGUAN_MK2 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN TIGUAN 2ND GEN", # Chassis AD/BW + [ + VWCarInfo("Volkswagen Tiguan 2018-24"), + VWCarInfo("Volkswagen Tiguan eHybrid 2021-23"), + ], + specs=VolkswagenCarSpecs(mass=1715, wheelbase=2.74), + ) + TOURAN_MK2 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN TOURAN 2ND GEN", # Chassis 1T + VWCarInfo("Volkswagen Touran 2016-23"), + specs=VolkswagenCarSpecs(mass=1516, wheelbase=2.79), + ) + TRANSPORTER_T61 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN TRANSPORTER T6.1", # Chassis 7H/7L + [ + VWCarInfo("Volkswagen Caravelle 2020"), + VWCarInfo("Volkswagen California 2021-23"), + ], + specs=VolkswagenCarSpecs(mass=1926, wheelbase=3.00, minSteerSpeed=14.0), + ) + TROC_MK1 = VolkswagenMQBPlatformConfig( + "VOLKSWAGEN T-ROC 1ST GEN", # Chassis A1 + VWCarInfo("Volkswagen T-Roc 2018-22", footnotes=[Footnote.VW_MQB_A0]), + specs=VolkswagenCarSpecs(mass=1413, wheelbase=2.63), + ) + AUDI_A3_MK3 = VolkswagenMQBPlatformConfig( + "AUDI A3 3RD GEN", # Chassis 8V/FF + [ + VWCarInfo("Audi A3 2014-19"), + VWCarInfo("Audi A3 Sportback e-tron 2017-18"), + VWCarInfo("Audi RS3 2018"), + VWCarInfo("Audi S3 2015-17"), + ], + specs=VolkswagenCarSpecs(mass=1335, wheelbase=2.61), + ) + AUDI_Q2_MK1 = VolkswagenMQBPlatformConfig( + "AUDI Q2 1ST GEN", # Chassis GA + VWCarInfo("Audi Q2 2018"), + specs=VolkswagenCarSpecs(mass=1205, wheelbase=2.61), + ) + AUDI_Q3_MK2 = VolkswagenMQBPlatformConfig( + "AUDI Q3 2ND GEN", # Chassis 8U/F3/FS + VWCarInfo("Audi Q3 2019-23"), + specs=VolkswagenCarSpecs(mass=1623, wheelbase=2.68), + ) + SEAT_ATECA_MK1 = VolkswagenMQBPlatformConfig( + "SEAT ATECA 1ST GEN", # Chassis 5F + VWCarInfo("SEAT Ateca 2018"), + specs=VolkswagenCarSpecs(mass=1900, wheelbase=2.64), + ) + SEAT_LEON_MK3 = VolkswagenMQBPlatformConfig( + "SEAT LEON 3RD GEN", # Chassis 5F + VWCarInfo("SEAT Leon 2014-20"), + specs=VolkswagenCarSpecs(mass=1227, wheelbase=2.64), + ) + SKODA_FABIA_MK4 = VolkswagenMQBPlatformConfig( + "SKODA FABIA 4TH GEN", # Chassis PJ + VWCarInfo("Å koda Fabia 2022-23", footnotes=[Footnote.VW_MQB_A0]), + specs=VolkswagenCarSpecs(mass=1266, wheelbase=2.56), + ) + SKODA_KAMIQ_MK1 = VolkswagenMQBPlatformConfig( + "SKODA KAMIQ 1ST GEN", # Chassis NW + VWCarInfo("Å koda Kamiq 2021-23", footnotes=[Footnote.VW_MQB_A0, Footnote.KAMIQ]), + specs=VolkswagenCarSpecs(mass=1265, wheelbase=2.66), + ) + SKODA_KAROQ_MK1 = VolkswagenMQBPlatformConfig( + "SKODA KAROQ 1ST GEN", # Chassis NU + VWCarInfo("Å koda Karoq 2019-23"), + specs=VolkswagenCarSpecs(mass=1278, wheelbase=2.66), + ) + SKODA_KODIAQ_MK1 = VolkswagenMQBPlatformConfig( + "SKODA KODIAQ 1ST GEN", # Chassis NS + VWCarInfo("Å koda Kodiaq 2017-23"), + specs=VolkswagenCarSpecs(mass=1569, wheelbase=2.79), + ) + SKODA_OCTAVIA_MK3 = VolkswagenMQBPlatformConfig( + "SKODA OCTAVIA 3RD GEN", # Chassis NE + [ + VWCarInfo("Å koda Octavia 2015-19"), + VWCarInfo("Å koda Octavia RS 2016"), + ], + specs=VolkswagenCarSpecs(mass=1388, wheelbase=2.68), + ) + SKODA_SCALA_MK1 = VolkswagenMQBPlatformConfig( + "SKODA SCALA 1ST GEN", # Chassis NW + VWCarInfo("Å koda Scala 2020-23", footnotes=[Footnote.VW_MQB_A0]), + specs=VolkswagenCarSpecs(mass=1192, wheelbase=2.65), + ) + SKODA_SUPERB_MK3 = VolkswagenMQBPlatformConfig( + "SKODA SUPERB 3RD GEN", # Chassis 3V/NP + VWCarInfo("Å koda Superb 2015-22"), + specs=VolkswagenCarSpecs(mass=1505, wheelbase=2.84), + ) +PQ_CARS = {CAR.PASSAT_NMS, CAR.SHARAN_MK2} # All supported cars should return FW from the engine, srs, eps, and fwdRadar. Cars # with a manual trans won't return transmission firmware, but all other cars will. @@ -293,17 +373,27 @@ VOLKSWAGEN_VERSION_RESPONSE = bytes([uds.SERVICE_TYPE.READ_DATA_BY_IDENTIFIER + VOLKSWAGEN_RX_OFFSET = 0x6a FW_QUERY_CONFIG = FwQueryConfig( - requests=[ + # TODO: add back whitelists after we gather enough data + requests=[request for bus, obd_multiplexing in [(1, True), (1, False), (0, False)] for request in [ Request( [VOLKSWAGEN_VERSION_REQUEST_MULTI], [VOLKSWAGEN_VERSION_RESPONSE], - whitelist_ecus=[Ecu.srs, Ecu.eps, Ecu.fwdRadar], + # whitelist_ecus=[Ecu.srs, Ecu.eps, Ecu.fwdRadar], rx_offset=VOLKSWAGEN_RX_OFFSET, + bus=bus, + logging=(bus != 1 or not obd_multiplexing), + obd_multiplexing=obd_multiplexing, ), Request( [VOLKSWAGEN_VERSION_REQUEST_MULTI], [VOLKSWAGEN_VERSION_RESPONSE], - whitelist_ecus=[Ecu.engine, Ecu.transmission], + # whitelist_ecus=[Ecu.engine, Ecu.transmission], + bus=bus, + logging=(bus != 1 or not obd_multiplexing), + obd_multiplexing=obd_multiplexing, ), - ], + ]], ) + +CAR_INFO = CAR.create_carinfo_map() +DBC = CAR.create_dbc_map() diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 2aae956090..bd3dd08179 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -5,28 +5,34 @@ import time import threading from typing import SupportsFloat -from cereal import car, log -from openpilot.common.numpy_fast import clip -from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL -from openpilot.common.params import Params import cereal.messaging as messaging + +from cereal import car, log from cereal.visionipc import VisionIpcClient, VisionStreamType -from openpilot.common.conversions import Conversions as CV + from panda import ALTERNATIVE_EXPERIENCE + +from openpilot.common.conversions import Conversions as CV +from openpilot.common.numpy_fast import clip +from openpilot.common.params import Params +from openpilot.common.realtime import config_realtime_process, Priority, Ratekeeper, DT_CTRL from openpilot.common.swaglog import cloudlog -from openpilot.system.version import get_short_branch + from openpilot.selfdrive.boardd.boardd import can_list_to_can_capnp from openpilot.selfdrive.car.car_helpers import get_car, get_startup_event, get_one_can +from openpilot.selfdrive.car.interfaces import CarInterfaceBase +from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert from openpilot.selfdrive.controls.lib.drive_helpers import VCruiseHelper, clip_curvature +from openpilot.selfdrive.controls.lib.events import Events, ET from openpilot.selfdrive.controls.lib.latcontrol import LatControl, MIN_LATERAL_CONTROL_SPEED -from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle, STEER_ANGLE_SATURATION_THRESHOLD from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque -from openpilot.selfdrive.controls.lib.events import Events, ET -from openpilot.selfdrive.controls.lib.alertmanager import AlertManager, set_offroad_alert +from openpilot.selfdrive.controls.lib.longcontrol import LongControl from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel + from openpilot.system.hardware import HARDWARE +from openpilot.system.version import get_short_branch SOFT_DISABLE_TIME = 3 # seconds LDW_MIN_SPEED = 31 * CV.MPH_TO_MS @@ -55,32 +61,19 @@ ACTIVE_STATES = (State.enabled, State.softDisabling, State.overriding) ENABLED_STATES = (State.preEnabled, *ACTIVE_STATES) -class Controls: - def __init__(self, CI=None): - config_realtime_process(4, Priority.CTRL_HIGH) - - # Ensure the current branch is cached, otherwise the first iteration of controlsd lags - self.branch = get_short_branch("") - - # Setup sockets - self.pm = messaging.PubMaster(['sendcan', 'controlsState', 'carState', - 'carControl', 'onroadEvents', 'carParams']) - - self.sensor_packets = ["accelerometer", "gyroscope"] - self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] +class CarD: + CI: CarInterfaceBase + CS: car.CarState - self.log_sock = messaging.sub_sock('androidLog') + def __init__(self, CI=None): self.can_sock = messaging.sub_sock('can', timeout=20) + self.sm = messaging.SubMaster(['pandaStates']) + self.pm = messaging.PubMaster(['sendcan', 'carState', 'carParams']) + + self.can_rcv_timeout_counter = 0 # conseuctive timeout count + self.can_rcv_cum_timeout_counter = 0 # cumulative timeout count self.params = Params() - ignore = self.sensor_packets + ['testJoystick'] - if SIMULATION: - ignore += ['driverCameraState', 'managerState'] - self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', - 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', - 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', - 'testJoystick'] + self.camera_packets + self.sensor_packets, - ignore_alive=ignore, ignore_avg_freq=['radarState', 'testJoystick'], ignore_valid=['testJoystick', ]) if CI is None: # wait for one pandaState and one CAN packet @@ -93,25 +86,17 @@ class Controls: else: self.CI, self.CP = CI, CI.CP - self.joystick_mode = self.params.get_bool("JoystickDebugMode") - # set alternative experiences from parameters - self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") + disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") self.CP.alternativeExperience = 0 - if not self.disengage_on_accelerator: + if not disengage_on_accelerator: self.CP.alternativeExperience |= ALTERNATIVE_EXPERIENCE.DISABLE_DISENGAGE_ON_GAS - # read params - 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") - - # detect sound card presence and ensure successful init - sounds_available = HARDWARE.get_sound_card_online() - car_recognized = self.CP.carName != 'mock' + openpilot_enabled_toggle = self.params.get_bool("OpenpilotEnabledToggle") controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly + self.CP.passive = not car_recognized or not controller_available or self.CP.dashcamOnly if self.CP.passive: safety_config = car.CarParams.SafetyConfig.new_message() @@ -129,6 +114,113 @@ class Controls: self.params.put_nonblocking("CarParamsCache", cp_bytes) self.params.put_nonblocking("CarParamsPersistent", cp_bytes) + def initialize(self): + """Initialize CarInterface, once controls are ready""" + self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) + + def state_update(self, CC: car.CarControl): + """carState update loop, driven by can""" + + # TODO: This should not depend on carControl + + # Update carState from CAN + can_strs = messaging.drain_sock_raw(self.can_sock, wait_for_one=True) + self.CS = self.CI.update(CC, can_strs) + + self.sm.update(0) + + can_rcv_valid = len(can_strs) > 0 + + # Check for CAN timeout + if not can_rcv_valid: + self.can_rcv_timeout_counter += 1 + self.can_rcv_cum_timeout_counter += 1 + else: + self.can_rcv_timeout_counter = 0 + + self.can_rcv_timeout = self.can_rcv_timeout_counter >= 5 + + if can_rcv_valid and REPLAY: + self.can_log_mono_time = messaging.log_from_bytes(can_strs[0]).logMonoTime + + return self.CS + + def state_publish(self, car_events): + """carState and carParams publish loop""" + + # TODO: carState should be independent of the event loop + + # carState + cs_send = messaging.new_message('carState') + cs_send.valid = self.CS.canValid + cs_send.carState = self.CS + cs_send.carState.events = car_events + self.pm.send('carState', cs_send) + + # carParams - logged every 50 seconds (> 1 per segment) + if (self.sm.frame % int(50. / DT_CTRL) == 0): + cp_send = messaging.new_message('carParams') + cp_send.valid = True + cp_send.carParams = self.CP + self.pm.send('carParams', cp_send) + + def controls_update(self, CC: car.CarControl): + """control update loop, driven by carControl""" + + # send car controls over can + now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) + actuators_output, can_sends = self.CI.apply(CC, now_nanos) + self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=self.CS.canValid)) + + return actuators_output + + +class Controls: + def __init__(self, CI=None): + self.card = CarD(CI) + + self.CP = self.card.CP + self.CI = self.card.CI + + config_realtime_process(4, Priority.CTRL_HIGH) + + # Ensure the current branch is cached, otherwise the first iteration of controlsd lags + self.branch = get_short_branch() + + # Setup sockets + self.pm = messaging.PubMaster(['controlsState', 'carControl', 'onroadEvents']) + + self.sensor_packets = ["accelerometer", "gyroscope"] + self.camera_packets = ["roadCameraState", "driverCameraState", "wideRoadCameraState"] + + self.log_sock = messaging.sub_sock('androidLog') + + self.params = Params() + ignore = self.sensor_packets + ['testJoystick'] + if SIMULATION: + ignore += ['driverCameraState', 'managerState'] + self.sm = messaging.SubMaster(['deviceState', 'pandaStates', 'peripheralState', 'modelV2', 'liveCalibration', + 'driverMonitoringState', 'longitudinalPlan', 'liveLocationKalman', + 'managerState', 'liveParameters', 'radarState', 'liveTorqueParameters', + 'testJoystick'] + self.camera_packets + self.sensor_packets, + ignore_alive=ignore, ignore_avg_freq=ignore+['radarState', 'testJoystick'], ignore_valid=['testJoystick', ], + frequency=int(1/DT_CTRL)) + + self.joystick_mode = self.params.get_bool("JoystickDebugMode") + + # read params + self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator") + 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") + + # detect sound card presence and ensure successful init + sounds_available = HARDWARE.get_sound_card_online() + + car_recognized = self.CP.carName != 'mock' + + controller_available = self.CI.CC is not None and openpilot_enabled_toggle and not self.CP.dashcamOnly + # cleanup old params if not self.CP.experimentalLongitudinalAvailable: self.params.remove("ExperimentalLongitudinalEnabled") @@ -158,8 +250,6 @@ class Controls: self.soft_disable_timer = 0 self.mismatch_counter = 0 self.cruise_mismatch_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 @@ -311,7 +401,8 @@ class Controls: else: safety_mismatch = pandaState.safetyModel not in IGNORED_SAFETY_MODES - if safety_mismatch or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200: + # safety mismatch allows some time for boardd to set the safety mode and publish it back from panda + if (safety_mismatch and self.sm.frame*DT_CTRL > 10.) or pandaState.safetyRxChecksInvalid or self.mismatch_counter >= 200: self.events.add(EventName.controlsMismatch) if log.PandaState.FaultType.relayMalfunction in pandaState.faults: @@ -323,7 +414,7 @@ class Controls: num_events = len(self.events) not_running = {p.name for p in self.sm['managerState'].processes if not p.running and p.shouldBeRunning} - if self.sm.rcv_frame['managerState'] and (not_running - IGNORE_PROCESSES): + if self.sm.recv_frame['managerState'] and (not_running - IGNORE_PROCESSES): self.events.add(EventName.processNotRunning) if not_running != self.not_running_prev: cloudlog.event("process_not_running", not_running=not_running, error=True) @@ -346,10 +437,9 @@ 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.contains(ET.NO_ENTRY) and (self.events.contains(ET.SOFT_DISABLE) or self.events.contains(ET.IMMEDIATE_DISABLE)) no_system_errors = (not has_disable_events) or (len(self.events) == num_events) - if (not self.sm.all_checks() or can_rcv_timeout) and no_system_errors: + if (not self.sm.all_checks() or self.card.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(): @@ -361,7 +451,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': can_rcv_timeout, + 'can_rcv_timeout': self.card.can_rcv_timeout, } if logs != self.logged_comm_issue: cloudlog.event("commIssue", error=True, **logs) @@ -380,7 +470,7 @@ class Controls: self.events.add(EventName.paramsdTemporaryError) # conservative HW alert. if the data or frequency are off, locationd will throw an error - if any((self.sm.frame - self.sm.rcv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): + if any((self.sm.frame - self.sm.recv_frame[s])*DT_CTRL > 10. for s in self.sensor_packets): self.events.add(EventName.sensorDataInvalid) if not REPLAY: @@ -410,9 +500,12 @@ class Controls: # TODO: fix simulator if not SIMULATION or REPLAY: + # Not show in first 1 km to allow for driving out of garage. This event shows after 5 minutes if not self.sm['liveLocationKalman'].gpsOK and self.sm['liveLocationKalman'].inputsOK and (self.distance_traveled > 1000): - # Not show in first 1 km to allow for driving out of garage. This event shows after 5 minutes self.events.add(EventName.noGps) + if self.sm['liveLocationKalman'].gpsOK: + self.distance_traveled = 0 + self.distance_traveled += CS.vEgo * DT_CTRL if self.sm['modelV2'].frameDropPerc > 20: self.events.add(EventName.modeldLagging) @@ -420,17 +513,13 @@ class Controls: 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 + CS = self.card.state_update(self.CC) self.sm.update(0) if not self.initialized: all_valid = CS.canValid and self.sm.all_checks() - timed_out = self.sm.frame * DT_CTRL > (6. if REPLAY else 3.5) + timed_out = self.sm.frame * DT_CTRL > 6. if all_valid or timed_out or (SIMULATION and not REPLAY): available_streams = VisionIpcClient.available_streams("camerad", block=False) if VisionStreamType.VISION_STREAM_ROAD not in available_streams: @@ -439,18 +528,22 @@ class Controls: self.sm.ignore_alive.append('wideRoadCameraState') if not self.CP.passive: - self.CI.init(self.CP, self.can_sock, self.pm.sock['sendcan']) + self.card.initialize() self.initialized = True self.set_initial_state() self.params.put_bool_nonblocking("ControlsReady", True) - # Check for CAN timeout - if not can_strs: - self.can_rcv_timeout_counter += 1 - self.can_rcv_cum_timeout_counter += 1 - else: - self.can_rcv_timeout_counter = 0 + cloudlog.event( + "controlsd.initialized", + dt=self.sm.frame*DT_CTRL, + timeout=timed_out, + canValid=CS.canValid, + 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], + error=True, + ) # When the panda and controlsd do not agree on controls_allowed # we want to disengage openpilot. However the status from the panda goes through @@ -464,8 +557,6 @@ class Controls: if ps.safetyModel not in IGNORED_SAFETY_MODES): self.mismatch_counter += 1 - self.distance_traveled += CS.vEgo * DT_CTRL - return CS def state_transition(self, CS): @@ -603,7 +694,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_helper.v_cruise_kph * CV.KPH_TO_MS) - t_since_plan = (self.sm.frame - self.sm.rcv_frame['longitudinalPlan']) * DT_CTRL + t_since_plan = (self.sm.frame - self.sm.recv_frame['longitudinalPlan']) * DT_CTRL actuators.accel = self.LoC.update(CC.longActive, CS, long_plan, pid_accel_limits, t_since_plan) # Steering PID loop and lateral MPC @@ -614,9 +705,9 @@ class Controls: self.sm['liveLocationKalman']) else: lac_log = log.ControlsState.LateralDebugState.new_message() - if self.sm.rcv_frame['testJoystick'] > 0: + if self.sm.recv_frame['testJoystick'] > 0: # reset joystick if it hasn't been received in a while - should_reset_joystick = (self.sm.frame - self.sm.rcv_frame['testJoystick'])*DT_CTRL > 0.2 + should_reset_joystick = (self.sm.frame - self.sm.recv_frame['testJoystick'])*DT_CTRL > 0.2 if not should_reset_joystick: joystick_axes = self.sm['testJoystick'].axes else: @@ -691,7 +782,7 @@ class Controls: CC.cruiseControl.override = self.enabled and not CC.longActive and self.CP.openpilotLongitudinalControl CC.cruiseControl.cancel = CS.cruiseState.enabled and (not self.enabled or not self.CP.pcmCruise) - if self.joystick_mode and self.sm.rcv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: + if self.joystick_mode and self.sm.recv_frame['testJoystick'] > 0 and self.sm['testJoystick'].buttons[0]: CC.cruiseControl.cancel = True speeds = self.sm['longitudinalPlan'].speeds @@ -742,10 +833,7 @@ class Controls: hudControl.visualAlert = current_alert.visual_alert if not self.CP.passive and self.initialized: - # send car controls over can - now_nanos = self.can_log_mono_time if REPLAY else int(time.monotonic() * 1e9) - self.last_actuators, can_sends = self.CI.apply(CC, now_nanos) - self.pm.send('sendcan', can_list_to_can_capnp(can_sends, msgtype='sendcan', valid=CS.canValid)) + self.last_actuators = self.card.controls_update(CC) CC.actuatorsOutput = self.last_actuators if self.CP.steerControlType == car.CarParams.SteerControlType.angle: self.steer_limited = abs(CC.actuators.steeringAngleDeg - CC.actuatorsOutput.steeringAngleDeg) > \ @@ -793,7 +881,7 @@ class Controls: controlsState.cumLagMs = -self.rk.remaining * 1000. controlsState.startMonoTime = int(start_time * 1e9) controlsState.forceDecel = bool(force_decel) - controlsState.canErrorCounter = self.can_rcv_cum_timeout_counter + controlsState.canErrorCounter = self.card.can_rcv_cum_timeout_counter controlsState.experimentalMode = self.experimental_mode lat_tuning = self.CP.lateralTuning.which() @@ -808,13 +896,9 @@ class Controls: self.pm.send('controlsState', dat) - # carState car_events = self.events.to_msg() - cs_send = messaging.new_message('carState') - cs_send.valid = CS.canValid - cs_send.carState = CS - cs_send.carState.events = car_events - self.pm.send('carState', cs_send) + + self.card.state_publish(car_events) # onroadEvents - logged every second or on change if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): @@ -824,13 +908,6 @@ class Controls: self.pm.send('onroadEvents', ce_send) self.events_prev = self.events.names.copy() - # carParams - logged every 50 seconds (> 1 per segment) - if (self.sm.frame % int(50. / DT_CTRL) == 0): - cp_send = messaging.new_message('carParams') - cp_send.valid = True - cp_send.carParams = self.CP - self.pm.send('carParams', cp_send) - # carControl cc_send = messaging.new_message('carControl') cc_send.valid = CS.canValid diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index 6abcf4cbba..f67e269fa9 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -3,7 +3,6 @@ import os import json from collections import defaultdict from dataclasses import dataclass -from typing import List, Dict, Optional from openpilot.common.basedir import BASEDIR from openpilot.common.params import Params @@ -14,7 +13,7 @@ with open(os.path.join(BASEDIR, "selfdrive/controls/lib/alerts_offroad.json")) a OFFROAD_ALERTS = json.load(f) -def set_offroad_alert(alert: str, show_alert: bool, extra_text: Optional[str] = None) -> None: +def set_offroad_alert(alert: str, show_alert: bool, extra_text: str = None) -> None: if show_alert: a = copy.copy(OFFROAD_ALERTS[alert]) a['extra'] = extra_text or '' @@ -25,7 +24,7 @@ def set_offroad_alert(alert: str, show_alert: bool, extra_text: Optional[str] = @dataclass class AlertEntry: - alert: Optional[Alert] = None + alert: Alert | None = None start_frame: int = -1 end_frame: int = -1 @@ -34,9 +33,9 @@ class AlertEntry: class AlertManager: def __init__(self): - self.alerts: Dict[str, AlertEntry] = defaultdict(AlertEntry) + self.alerts: dict[str, AlertEntry] = defaultdict(AlertEntry) - def add_many(self, frame: int, alerts: List[Alert]) -> None: + def add_many(self, frame: int, alerts: list[Alert]) -> None: for alert in alerts: entry = self.alerts[alert.alert_type] entry.alert = alert @@ -45,7 +44,7 @@ class AlertManager: min_end_frame = entry.start_frame + alert.duration entry.end_frame = max(frame + 1, min_end_frame) - def process_alerts(self, frame: int, clear_event_types: set) -> Optional[Alert]: + def process_alerts(self, frame: int, clear_event_types: set) -> Alert | None: current_alert = AlertEntry() for v in self.alerts.values(): if not v.alert: diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py index 7df41927cf..c5228ef7f2 100755 --- a/selfdrive/controls/lib/events.py +++ b/selfdrive/controls/lib/events.py @@ -2,7 +2,7 @@ import math import os from enum import IntEnum -from typing import Dict, Union, Callable, List, Optional +from collections.abc import Callable from cereal import log, car import cereal.messaging as messaging @@ -48,12 +48,12 @@ EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} class Events: def __init__(self): - self.events: List[int] = [] - self.static_events: List[int] = [] + self.events: list[int] = [] + self.static_events: list[int] = [] self.events_prev = dict.fromkeys(EVENTS.keys(), 0) @property - def names(self) -> List[int]: + def names(self) -> list[int]: return self.events def __len__(self) -> int: @@ -71,7 +71,7 @@ class Events: def contains(self, event_type: str) -> bool: return any(event_type in EVENTS.get(e, {}) for e in self.events) - def create_alerts(self, event_types: List[str], callback_args=None): + def create_alerts(self, event_types: list[str], callback_args=None): if callback_args is None: callback_args = [] @@ -132,7 +132,7 @@ class Alert: self.creation_delay = creation_delay self.alert_type = "" - self.event_type: Optional[str] = None + self.event_type: str | None = None def __str__(self) -> str: return f"{self.alert_text_1}/{self.alert_text_2} {self.priority} {self.visual_alert} {self.audible_alert}" @@ -224,7 +224,7 @@ def user_soft_disable_alert(alert_text_2: str) -> AlertCallbackType: return func def startup_master_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int) -> Alert: - branch = get_short_branch("") # Ensure get_short_branch is cached to avoid lags on startup + branch = get_short_branch() # Ensure get_short_branch is cached to avoid lags on startup if "REPLAY" in os.environ: branch = "replay" @@ -333,7 +333,7 @@ def joystick_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, -EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { +EVENTS: dict[int, dict[str, Alert | AlertCallbackType]] = { # ********** events with no alerts ********** EventName.stockFcw: {}, @@ -767,12 +767,12 @@ EVENTS: Dict[int, Dict[str, Union[Alert, AlertCallbackType]]] = { # is thrown. This can mean a service crashed, did not broadcast a message for # ten times the regular interval, or the average interval is more than 10% too high. EventName.commIssue: { - ET.SOFT_DISABLE: soft_disable_alert("Communication Issue between Processes"), + ET.SOFT_DISABLE: soft_disable_alert("Communication Issue Between Processes"), ET.NO_ENTRY: comm_issue_alert, }, EventName.commIssueAvgFreq: { - ET.SOFT_DISABLE: soft_disable_alert("Low Communication Rate between Processes"), - ET.NO_ENTRY: NoEntryAlert("Low Communication Rate between Processes"), + ET.SOFT_DISABLE: soft_disable_alert("Low Communication Rate Between Processes"), + ET.NO_ENTRY: NoEntryAlert("Low Communication Rate Between Processes"), }, EventName.controlsdLagging: { @@ -965,7 +965,7 @@ if __name__ == '__main__': from collections import defaultdict event_names = {v: k for k, v in EventName.schema.enumerants.items()} - alerts_by_type: Dict[str, Dict[Priority, List[str]]] = defaultdict(lambda: defaultdict(list)) + alerts_by_type: dict[str, dict[Priority, list[str]]] = defaultdict(lambda: defaultdict(list)) CP = car.CarParams.new_message() CS = car.CarState.new_message() @@ -977,7 +977,7 @@ if __name__ == '__main__': alert = alert(CP, CS, sm, False, 1) alerts_by_type[et][alert.priority].append(event_names[i]) - all_alerts: Dict[str, List[tuple[Priority, List[str]]]] = {} + all_alerts: dict[str, list[tuple[Priority, list[str]]]] = {} for et, priority_alerts in alerts_by_type.items(): all_alerts[et] = sorted(priority_alerts.items(), key=lambda x: x[0], reverse=True) diff --git a/selfdrive/controls/lib/latcontrol_torque.py b/selfdrive/controls/lib/latcontrol_torque.py index 65fd1b51c5..34b0d47124 100644 --- a/selfdrive/controls/lib/latcontrol_torque.py +++ b/selfdrive/controls/lib/latcontrol_torque.py @@ -2,6 +2,7 @@ import math from cereal import log from openpilot.common.numpy_fast import interp +from openpilot.selfdrive.car.interfaces import LatControlInputs from openpilot.selfdrive.controls.lib.latcontrol import LatControl from openpilot.selfdrive.controls.lib.pid import PIDController from openpilot.selfdrive.controls.lib.vehicle_model import ACCELERATION_DUE_TO_GRAVITY @@ -38,16 +39,16 @@ class LatControlTorque(LatControl): def update(self, active, CS, VM, params, steer_limited, desired_curvature, llk): pid_log = log.ControlsState.LateralTorqueState.new_message() - if not active: output_torque = 0.0 pid_log.active = False else: + actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + roll_compensation = params.roll * ACCELERATION_DUE_TO_GRAVITY if self.use_steering_angle: - actual_curvature = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) + actual_curvature = actual_curvature_vm curvature_deadzone = abs(VM.calc_curvature(math.radians(self.steering_angle_deadzone_deg), CS.vEgo, 0.0)) else: - actual_curvature_vm = -VM.calc_curvature(math.radians(CS.steeringAngleDeg - params.angleOffsetDeg), CS.vEgo, params.roll) actual_curvature_llk = llk.angularVelocityCalibrated.value[2] / CS.vEgo actual_curvature = interp(CS.vEgo, [2.0, 5.0], [actual_curvature_vm, actual_curvature_llk]) curvature_deadzone = 0.0 @@ -61,15 +62,15 @@ class LatControlTorque(LatControl): 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 - gravity_adjusted_lateral_accel = desired_lateral_accel - params.roll * ACCELERATION_DUE_TO_GRAVITY - torque_from_setpoint = self.torque_from_lateral_accel(setpoint, self.torque_params, setpoint, - lateral_accel_deadzone, friction_compensation=False) - torque_from_measurement = self.torque_from_lateral_accel(measurement, self.torque_params, measurement, - lateral_accel_deadzone, friction_compensation=False) + gravity_adjusted_lateral_accel = desired_lateral_accel - roll_compensation + torque_from_setpoint = self.torque_from_lateral_accel(LatControlInputs(setpoint, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + setpoint, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False) + torque_from_measurement = self.torque_from_lateral_accel(LatControlInputs(measurement, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + measurement, lateral_accel_deadzone, friction_compensation=False, gravity_adjusted=False) pid_log.error = torque_from_setpoint - torque_from_measurement - ff = self.torque_from_lateral_accel(gravity_adjusted_lateral_accel, self.torque_params, - desired_lateral_accel - actual_lateral_accel, - lateral_accel_deadzone, friction_compensation=True) + ff = self.torque_from_lateral_accel(LatControlInputs(gravity_adjusted_lateral_accel, roll_compensation, CS.vEgo, CS.aEgo), self.torque_params, + desired_lateral_accel - actual_lateral_accel, lateral_accel_deadzone, friction_compensation=True, + gravity_adjusted=True) freeze_integrator = steer_limited or CS.steeringPressed or CS.vEgo < 5 output_torque = self.pid.update(pid_log.error, diff --git a/selfdrive/controls/lib/tests/test_latcontrol.py b/selfdrive/controls/lib/tests/test_latcontrol.py index 248abf1d7b..866270ca60 100755 --- a/selfdrive/controls/lib/tests/test_latcontrol.py +++ b/selfdrive/controls/lib/tests/test_latcontrol.py @@ -12,7 +12,7 @@ from openpilot.selfdrive.controls.lib.latcontrol_pid import LatControlPID from openpilot.selfdrive.controls.lib.latcontrol_torque import LatControlTorque from openpilot.selfdrive.controls.lib.latcontrol_angle import LatControlAngle from openpilot.selfdrive.controls.lib.vehicle_model import VehicleModel -from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk +from openpilot.common.mock.generators import generate_liveLocationKalman class TestLatControl(unittest.TestCase): @@ -32,7 +32,7 @@ class TestLatControl(unittest.TestCase): params = log.LiveParametersData.new_message() - llk = gen_llk() + llk = generate_liveLocationKalman() for _ in range(1000): _, _, lac_log = controller.update(True, CS, VM, params, False, 1, llk) diff --git a/selfdrive/controls/lib/vehicle_model.py b/selfdrive/controls/lib/vehicle_model.py index 0750384918..b6f50b4ba8 100755 --- a/selfdrive/controls/lib/vehicle_model.py +++ b/selfdrive/controls/lib/vehicle_model.py @@ -12,7 +12,6 @@ x_dot = A*x + B*u A depends on longitudinal speed, u [m/s], and vehicle parameters CP """ -from typing import Tuple import numpy as np from numpy.linalg import solve @@ -169,7 +168,7 @@ def kin_ss_sol(sa: float, u: float, VM: VehicleModel) -> np.ndarray: return K * sa -def create_dyn_state_matrices(u: float, VM: VehicleModel) -> Tuple[np.ndarray, np.ndarray]: +def create_dyn_state_matrices(u: float, VM: VehicleModel) -> tuple[np.ndarray, np.ndarray]: """Returns the A and B matrix for the dynamics system Args: diff --git a/selfdrive/controls/plannerd.py b/selfdrive/controls/plannerd.py index 5ab4894424..eeeeda050e 100755 --- a/selfdrive/controls/plannerd.py +++ b/selfdrive/controls/plannerd.py @@ -29,11 +29,10 @@ def plannerd_thread(): longitudinal_planner = LongitudinalPlanner(CP) pm = messaging.PubMaster(['longitudinalPlan', 'uiPlan']) sm = messaging.SubMaster(['carControl', 'carState', 'controlsState', 'radarState', 'modelV2'], - poll=['radarState', 'modelV2'], ignore_avg_freq=['radarState']) + poll='modelV2', ignore_avg_freq=['radarState']) while True: sm.update() - if sm.updated['modelV2']: longitudinal_planner.update(sm) longitudinal_planner.publish(sm, pm) diff --git a/selfdrive/controls/radard.py b/selfdrive/controls/radard.py index 4acadee7a7..4de4208e9d 100755 --- a/selfdrive/controls/radard.py +++ b/selfdrive/controls/radard.py @@ -2,13 +2,13 @@ import importlib import math from collections import deque -from typing import Optional, Dict, Any +from typing import Any, Optional import capnp from cereal import messaging, log, car from openpilot.common.numpy_fast import interp from openpilot.common.params import Params -from openpilot.common.realtime import Ratekeeper, Priority, config_realtime_process +from openpilot.common.realtime import DT_CTRL, Ratekeeper, Priority, config_realtime_process from openpilot.common.swaglog import cloudlog from openpilot.common.simple_kalman import KF1D @@ -125,7 +125,7 @@ def laplacian_pdf(x: float, mu: float, b: float): return math.exp(-abs(x-mu)/b) -def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: Dict[int, Track]): +def match_vision_to_track(v_ego: float, lead: capnp._DynamicStructReader, tracks: dict[int, Track]): offset_vision_dist = lead.x[0] - RADAR_TO_CAMERA def prob(c): @@ -166,8 +166,8 @@ def get_RadarState_from_vision(lead_msg: capnp._DynamicStructReader, v_ego: floa } -def get_lead(v_ego: float, ready: bool, tracks: Dict[int, Track], lead_msg: capnp._DynamicStructReader, - model_v_ego: float, low_speed_override: bool = True) -> Dict[str, Any]: +def get_lead(v_ego: float, ready: bool, tracks: dict[int, Track], lead_msg: capnp._DynamicStructReader, + model_v_ego: float, low_speed_override: bool = True) -> dict[str, Any]: # Determine leads, this is where the essential logic happens if len(tracks) > 0 and ready and lead_msg.prob > .5: track = match_vision_to_track(v_ego, lead_msg, tracks) @@ -196,18 +196,20 @@ class RadarD: def __init__(self, radar_ts: float, delay: int = 0): self.current_time = 0.0 - self.tracks: Dict[int, Track] = {} + self.tracks: dict[int, Track] = {} self.kalman_params = KalmanParams(radar_ts) self.v_ego = 0.0 self.v_ego_hist = deque([0.0], maxlen=delay+1) + self.last_v_ego_frame = -1 - self.radar_state: Optional[capnp._DynamicStructBuilder] = None + self.radar_state: capnp._DynamicStructBuilder | None = None self.radar_state_valid = False self.ready = False def update(self, sm: messaging.SubMaster, rr: Optional[car.RadarData]): + self.ready = sm.seen['modelV2'] self.current_time = 1e-9*max(sm.logMonoTime.values()) radar_points = [] @@ -216,11 +218,10 @@ class RadarD: radar_points = rr.points radar_errors = rr.errors - if sm.updated['carState']: + if sm.recv_frame['carState'] != self.last_v_ego_frame: self.v_ego = sm['carState'].vEgo self.v_ego_hist.append(self.v_ego) - if sm.updated['modelV2']: - self.ready = True + self.last_v_ego_frame = sm.recv_frame['carState'] ar_pts = {} for pt in radar_points: @@ -282,7 +283,7 @@ class RadarD: # fuses camera and radar data for best lead detection -def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: Optional[messaging.SubSocket] = None): +def main(): config_realtime_process(5, Priority.CTRL_LOW) # wait for stats about the car to come in from controls @@ -296,12 +297,9 @@ def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messagi RadarInterface = importlib.import_module(f'selfdrive.car.{CP.carName}.radar_interface').RadarInterface # *** setup messaging - if can_sock is None: - can_sock = messaging.sub_sock('can') - if sm is None: - sm = messaging.SubMaster(['modelV2', 'carState'], ignore_avg_freq=['modelV2', 'carState']) # Can't check average frequency, since radar determines timing - if pm is None: - pm = messaging.PubMaster(['radarState', 'liveTracks']) + can_sock = messaging.sub_sock('can') + sm = messaging.SubMaster(['modelV2', 'carState'], frequency=int(1./DT_CTRL)) + pm = messaging.PubMaster(['radarState', 'liveTracks']) RI = RadarInterface(CP) @@ -311,21 +309,15 @@ def radard_thread(sm: Optional[messaging.SubMaster] = None, pm: Optional[messagi while 1: can_strings = messaging.drain_sock_raw(can_sock, wait_for_one=True) rr = RI.update(can_strings) - + sm.update(0) if rr is None: continue - sm.update(0) - RD.update(sm, rr) RD.publish(pm, -rk.remaining*1000.0) rk.monitor_time() -def main(sm: Optional[messaging.SubMaster] = None, pm: Optional[messaging.PubMaster] = None, can_sock: messaging.SubSocket = None): - radard_thread(sm, pm, can_sock) - - if __name__ == "__main__": main() diff --git a/selfdrive/debug/can_print_changes.py b/selfdrive/debug/can_print_changes.py index 810e45cfc6..97d60b2b05 100755 --- a/selfdrive/debug/can_print_changes.py +++ b/selfdrive/debug/can_print_changes.py @@ -3,7 +3,6 @@ import argparse import binascii import time from collections import defaultdict -from typing import Optional import cereal.messaging as messaging from openpilot.selfdrive.debug.can_table import can_table @@ -96,8 +95,8 @@ if __name__ == "__main__": args = parser.parse_args() - init_lr: Optional[LogIterable] = None - new_lr: Optional[LogIterable] = None + init_lr: LogIterable | None = None + new_lr: LogIterable | None = None if args.init: if args.init == '': diff --git a/selfdrive/debug/check_freq.py b/selfdrive/debug/check_freq.py index 7e7b05e950..1765aeb86b 100755 --- a/selfdrive/debug/check_freq.py +++ b/selfdrive/debug/check_freq.py @@ -3,7 +3,7 @@ import argparse import numpy as np import time from collections import defaultdict, deque -from typing import DefaultDict, Deque, MutableSequence +from collections.abc import MutableSequence import cereal.messaging as messaging @@ -19,8 +19,8 @@ if __name__ == "__main__": socket_names = args.socket sockets = {} - rcv_times: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) - valids: DefaultDict[str, Deque[bool]] = defaultdict(lambda: deque(maxlen=100)) + rcv_times: defaultdict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) + valids: defaultdict[str, deque[bool]] = defaultdict(lambda: deque(maxlen=100)) t = time.monotonic() for name in socket_names: diff --git a/selfdrive/debug/check_lag.py b/selfdrive/debug/check_lag.py index 72d11d6eda..341ae79c8b 100755 --- a/selfdrive/debug/check_lag.py +++ b/selfdrive/debug/check_lag.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -from typing import Dict import cereal.messaging as messaging from cereal.services import SERVICE_LIST @@ -10,7 +9,7 @@ TO_CHECK = ['carState'] if __name__ == "__main__": sm = messaging.SubMaster(TO_CHECK) - prev_t: Dict[str, float] = {} + prev_t: dict[str, float] = {} while True: sm.update() diff --git a/selfdrive/debug/check_timings.py b/selfdrive/debug/check_timings.py index fb8467a3c4..3de6b8eb66 100755 --- a/selfdrive/debug/check_timings.py +++ b/selfdrive/debug/check_timings.py @@ -3,13 +3,13 @@ import sys import time import numpy as np -from typing import DefaultDict, MutableSequence +from collections.abc import MutableSequence from collections import defaultdict, deque import cereal.messaging as messaging socks = {s: messaging.sub_sock(s, conflate=False) for s in sys.argv[1:]} -ts: DefaultDict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) +ts: defaultdict[str, MutableSequence[float]] = defaultdict(lambda: deque(maxlen=100)) if __name__ == "__main__": while True: diff --git a/selfdrive/debug/count_events.py b/selfdrive/debug/count_events.py index 0d545b3153..5942054757 100755 --- a/selfdrive/debug/count_events.py +++ b/selfdrive/debug/count_events.py @@ -4,54 +4,58 @@ import math import datetime from collections import Counter from pprint import pprint -from typing import List, Tuple, cast +from typing import cast from cereal.services import SERVICE_LIST from openpilot.tools.lib.logreader import LogReader, ReadMode if __name__ == "__main__": - cnt_valid: Counter = Counter() cnt_events: Counter = Counter() cams = [s for s in SERVICE_LIST if s.endswith('CameraState')] cnt_cameras = dict.fromkeys(cams, 0) - alerts: List[Tuple[float, str]] = [] + events: list[tuple[float, set[str]]] = [] + alerts: list[tuple[float, str]] = [] start_time = math.inf end_time = -math.inf ignition_off = None for msg in LogReader(sys.argv[1], ReadMode.QLOG): + t = (msg.logMonoTime - start_time) / 1e9 end_time = max(end_time, msg.logMonoTime) start_time = min(start_time, msg.logMonoTime) if msg.which() == 'onroadEvents': for e in msg.onroadEvents: cnt_events[e.name] += 1 + + ae = {str(e.name) for e in msg.onroadEvents if e.name not in ('pedalPressed', 'steerOverride', 'gasPressedOverride')} + if len(events) == 0 or ae != events[-1][1]: + events.append((t, ae)) + elif msg.which() == 'controlsState': at = msg.controlsState.alertType if "/override" not in at or "lanechange" in at.lower(): if len(alerts) == 0 or alerts[-1][1] != at: - t = (msg.logMonoTime - start_time) / 1e9 alerts.append((t, at)) elif msg.which() == 'pandaStates': if ignition_off is None: ign = any(ps.ignitionLine or ps.ignitionCan for ps in msg.pandaStates) if not ign: ignition_off = msg.logMonoTime + break elif msg.which() in cams: cnt_cameras[msg.which()] += 1 - if not msg.valid: - cnt_valid[msg.which()] += 1 - duration = (end_time - start_time) / 1e9 print("Events") pprint(cnt_events) print("\n") - print("Not valid") - pprint(cnt_valid) + print("Events") + for t, evt in events: + print(f"{t:8.2f} {evt}") print("\n") print("Cameras") @@ -64,9 +68,9 @@ if __name__ == "__main__": print("Alerts") for t, a in alerts: print(f"{t:8.2f} {a}") + + print("\n") if ignition_off is not None: ignition_off = round((ignition_off - start_time) / 1e9, 2) print("Ignition off at", ignition_off) - - print("\n") print("Route duration", datetime.timedelta(seconds=duration)) diff --git a/selfdrive/debug/cpu_usage_stat.py b/selfdrive/debug/cpu_usage_stat.py index 9050fbb064..ec9d02e8f4 100755 --- a/selfdrive/debug/cpu_usage_stat.py +++ b/selfdrive/debug/cpu_usage_stat.py @@ -66,12 +66,12 @@ if __name__ == "__main__": for p in psutil.process_iter(): if p == psutil.Process(): continue - matched = any(l for l in p.cmdline() if any(pn for pn in monitored_proc_names if re.match(r'.*{}.*'.format(pn), l, re.M | re.I))) + matched = any(l for l in p.cmdline() if any(pn for pn in monitored_proc_names if re.match(fr'.*{pn}.*', l, re.M | re.I))) if matched: k = ' '.join(p.cmdline()) print('Add monitored proc:', k) stats[k] = {'cpu_samples': defaultdict(list), 'min': defaultdict(lambda: None), 'max': defaultdict(lambda: None), - 'avg': defaultdict(lambda: 0.0), 'last_cpu_times': None, 'last_sys_time': None} + 'avg': defaultdict(float), 'last_cpu_times': None, 'last_sys_time': None} stats[k]['last_sys_time'] = timer() stats[k]['last_cpu_times'] = p.cpu_times() monitored_procs.append(p) diff --git a/selfdrive/debug/dump.py b/selfdrive/debug/dump.py index db18f4c622..787e9bc738 100755 --- a/selfdrive/debug/dump.py +++ b/selfdrive/debug/dump.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 -import os import sys import argparse import json import codecs -import cereal.messaging as messaging from hexdump import hexdump from cereal import log from cereal.services import SERVICE_LIST +from openpilot.tools.lib.live_logreader import raw_live_logreader + codecs.register_error("strict", codecs.backslashreplace_errors) @@ -22,32 +22,20 @@ if __name__ == "__main__": parser.add_argument('--no-print', action='store_true') parser.add_argument('--addr', default='127.0.0.1') parser.add_argument('--values', help='values to monitor (instead of entire event)') - parser.add_argument("socket", type=str, nargs='*', help="socket names to dump. defaults to all services defined in cereal") + parser.add_argument("socket", type=str, nargs='*', default=list(SERVICE_LIST.keys()), help="socket names to dump. defaults to all services defined in cereal") args = parser.parse_args() - if args.addr != "127.0.0.1": - os.environ["ZMQ"] = "1" - messaging.context = messaging.Context() - - poller = messaging.Poller() - - for m in args.socket if len(args.socket) > 0 else SERVICE_LIST: - messaging.sub_sock(m, poller, addr=args.addr) + lr = raw_live_logreader(args.socket, args.addr) values = None if args.values: values = [s.strip().split(".") for s in args.values.split(",")] - while 1: - polld = poller.poll(100) - for sock in polld: - msg = sock.receive() - with log.Event.from_bytes(msg) as log_evt: - evt = log_evt - + for msg in lr: + with log.Event.from_bytes(msg) as evt: if not args.no_print: if args.pipe: - sys.stdout.write(msg) + sys.stdout.write(str(msg)) sys.stdout.flush() elif args.raw: hexdump(msg) diff --git a/selfdrive/debug/filter_log_message.py b/selfdrive/debug/filter_log_message.py index 20028f8fd2..9cbab0b41f 100755 --- a/selfdrive/debug/filter_log_message.py +++ b/selfdrive/debug/filter_log_message.py @@ -46,6 +46,7 @@ def print_androidlog(t, msg): if __name__ == "__main__": parser = argparse.ArgumentParser() + parser.add_argument('--absolute', action='store_true') parser.add_argument('--level', default='DEBUG') parser.add_argument('--addr', default='127.0.0.1') parser.add_argument("route", type=str, nargs='*', help="route name + segment number for offline usage") @@ -54,15 +55,18 @@ if __name__ == "__main__": min_level = LEVELS[args.level] if args.route: + st = None if not args.absolute else 0 for route in args.route: - lr = LogReader(route) + lr = LogReader(route, sort_by_time=True) for m in lr: + if st is None: + st = m.logMonoTime if m.which() == 'logMessage': - print_logmessage(m.logMonoTime, m.logMessage, min_level) + print_logmessage(m.logMonoTime-st, m.logMessage, min_level) elif m.which() == 'errorLogMessage': - print_logmessage(m.logMonoTime, m.errorLogMessage, min_level) + print_logmessage(m.logMonoTime-st, m.errorLogMessage, min_level) elif m.which() == 'androidLog': - print_androidlog(m.logMonoTime, m.androidLog) + print_androidlog(m.logMonoTime-st, m.androidLog) else: sm = messaging.SubMaster(['logMessage', 'androidLog'], addr=args.addr) while True: diff --git a/selfdrive/debug/format_fingerprints.py b/selfdrive/debug/format_fingerprints.py index bd5822729a..2a5e4e6080 100755 --- a/selfdrive/debug/format_fingerprints.py +++ b/selfdrive/debug/format_fingerprints.py @@ -67,7 +67,7 @@ def format_brand_fw_versions(brand, extra_fw_versions: None | dict[str, dict[tup extra_fw_versions = extra_fw_versions or {} fingerprints_file = os.path.join(BASEDIR, f"selfdrive/car/{brand}/fingerprints.py") - with open(fingerprints_file, "r") as f: + with open(fingerprints_file) as f: comments = [line for line in f.readlines() if line.startswith("#") and "noqa" not in line] with open(fingerprints_file, "w") as f: diff --git a/selfdrive/debug/internal/measure_modeld_packet_drop.py b/selfdrive/debug/internal/measure_modeld_packet_drop.py index 6b7f7dbd13..9814942ce2 100755 --- a/selfdrive/debug/internal/measure_modeld_packet_drop.py +++ b/selfdrive/debug/internal/measure_modeld_packet_drop.py @@ -1,12 +1,11 @@ #!/usr/bin/env python3 import cereal.messaging as messaging -from typing import Optional if __name__ == "__main__": modeld_sock = messaging.sub_sock("modelV2") last_frame_id = None - start_t: Optional[int] = None + start_t: int | None = None frame_cnt = 0 dropped = 0 diff --git a/selfdrive/debug/live_cpu_and_temp.py b/selfdrive/debug/live_cpu_and_temp.py index 06f1be0b00..8549b92665 100755 --- a/selfdrive/debug/live_cpu_and_temp.py +++ b/selfdrive/debug/live_cpu_and_temp.py @@ -5,7 +5,6 @@ from collections import defaultdict from cereal.messaging import SubMaster from openpilot.common.numpy_fast import mean -from typing import Optional, Dict def cputime_total(ct): return ct.user + ct.nice + ct.system + ct.idle + ct.iowait + ct.irq + ct.softirq @@ -42,8 +41,8 @@ if __name__ == "__main__": total_times = [0.]*8 busy_times = [0.]*8 - prev_proclog: Optional[capnp._DynamicStructReader] = None - prev_proclog_t: Optional[int] = None + prev_proclog: capnp._DynamicStructReader | None = None + prev_proclog_t: int | None = None while True: sm.update() @@ -76,7 +75,7 @@ if __name__ == "__main__": print(f"CPU {100.0 * mean(cores):.2f}% - RAM: {last_mem:.2f}% - Temp {last_temp:.2f}C") if args.cpu and prev_proclog is not None and prev_proclog_t is not None: - procs: Dict[str, float] = defaultdict(float) + procs: dict[str, float] = defaultdict(float) dt = (sm.logMonoTime['procLog'] - prev_proclog_t) / 1e9 for proc in m.procs: try: diff --git a/selfdrive/debug/test_fw_query_on_routes.py b/selfdrive/debug/test_fw_query_on_routes.py index dd6243a44c..cc6fc2ae17 100755 --- a/selfdrive/debug/test_fw_query_on_routes.py +++ b/selfdrive/debug/test_fw_query_on_routes.py @@ -44,11 +44,15 @@ if __name__ == "__main__": dongles = [] for route in tqdm(routes): - dongle_id = SegmentRange(route).dongle_id + sr = SegmentRange(route) + dongle_id = sr.dongle_id if dongle_id in dongles: continue + if sr.slice == '' and sr.selector is None: + route += '/0' + lr = LogReader(route, default_mode=ReadMode.QLOG) try: diff --git a/selfdrive/locationd/calibrationd.py b/selfdrive/locationd/calibrationd.py index 06be9f031a..6e154bf07c 100755 --- a/selfdrive/locationd/calibrationd.py +++ b/selfdrive/locationd/calibrationd.py @@ -10,7 +10,7 @@ import gc import os import capnp import numpy as np -from typing import List, NoReturn, Optional +from typing import NoReturn from cereal import log import cereal.messaging as messaging @@ -89,7 +89,7 @@ class Calibrator: valid_blocks: int = 0, wide_from_device_euler_init: np.ndarray = WIDE_FROM_DEVICE_EULER_INIT, height_init: np.ndarray = HEIGHT_INIT, - smooth_from: Optional[np.ndarray] = None) -> None: + smooth_from: np.ndarray = None) -> None: if not np.isfinite(rpy_init).all(): self.rpy = RPY_INIT.copy() else: @@ -125,7 +125,7 @@ class Calibrator: self.old_rpy = smooth_from self.old_rpy_weight = 1.0 - def get_valid_idxs(self) -> List[int]: + def get_valid_idxs(self) -> list[int]: # exclude current block_idx from validity window before_current = list(range(self.block_idx)) after_current = list(range(min(self.valid_blocks, self.block_idx + 1), self.valid_blocks)) @@ -175,12 +175,12 @@ class Calibrator: else: return self.rpy - def handle_cam_odom(self, trans: List[float], - rot: List[float], - wide_from_device_euler: List[float], - trans_std: List[float], - road_transform_trans: List[float], - road_transform_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], + road_transform_trans: list[float], + road_transform_trans_std: list[float]) -> np.ndarray | None: self.old_rpy_weight = max(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)) @@ -260,7 +260,7 @@ def main() -> NoReturn: set_realtime_priority(1) pm = messaging.PubMaster(['liveCalibration']) - sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll=['cameraOdometry']) + sm = messaging.SubMaster(['cameraOdometry', 'carState', 'carParams'], poll='cameraOdometry') calibrator = Calibrator(param_put=True) diff --git a/selfdrive/locationd/helpers.py b/selfdrive/locationd/helpers.py index 93e2929139..786bdbbfec 100644 --- a/selfdrive/locationd/helpers.py +++ b/selfdrive/locationd/helpers.py @@ -1,5 +1,5 @@ import numpy as np -from typing import List, Optional, Tuple, Any +from typing import Any from cereal import log @@ -12,7 +12,7 @@ class NPQueue: def __len__(self) -> int: return len(self.arr) - def append(self, pt: List[float]) -> None: + def append(self, pt: list[float]) -> None: if len(self.arr) < self.maxlen: self.arr = np.append(self.arr, [pt], axis=0) else: @@ -21,33 +21,33 @@ class NPQueue: class PointBuckets: - def __init__(self, x_bounds: List[Tuple[float, float]], min_points: List[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None: + def __init__(self, x_bounds: list[tuple[float, float]], min_points: list[float], min_points_total: int, points_per_bucket: int, rowsize: int) -> None: self.x_bounds = x_bounds self.buckets = {bounds: NPQueue(maxlen=points_per_bucket, rowsize=rowsize) for bounds in x_bounds} self.buckets_min_points = dict(zip(x_bounds, min_points, strict=True)) self.min_points_total = min_points_total - def bucket_lengths(self) -> List[int]: - return [len(v) for v in self.buckets.values()] - def __len__(self) -> int: - return sum(self.bucket_lengths()) + return sum([len(v) for v in self.buckets.values()]) def is_valid(self) -> bool: individual_buckets_valid = all(len(v) >= min_pts for v, min_pts in zip(self.buckets.values(), self.buckets_min_points.values(), strict=True)) total_points_valid = self.__len__() >= self.min_points_total return individual_buckets_valid and total_points_valid + def is_calculable(self) -> bool: + return all(len(v) > 0 for v in self.buckets.values()) + def add_point(self, x: float, y: float, bucket_val: float) -> None: raise NotImplementedError - def get_points(self, num_points: Optional[int] = None) -> Any: + def get_points(self, num_points: int = None) -> Any: points = np.vstack([x.arr for x in self.buckets.values()]) if num_points is None: return points return points[np.random.choice(np.arange(len(points)), min(len(points), num_points), replace=False)] - def load_points(self, points: List[List[float]]) -> None: + def load_points(self, points: list[list[float]]) -> None: for point in points: self.add_point(*point) diff --git a/selfdrive/locationd/models/car_kf.py b/selfdrive/locationd/models/car_kf.py index 9230cb48f0..1f3e447a19 100755 --- a/selfdrive/locationd/models/car_kf.py +++ b/selfdrive/locationd/models/car_kf.py @@ -1,7 +1,7 @@ #!/usr/bin/env python3 import math import sys -from typing import Any, Dict +from typing import Any import numpy as np @@ -70,7 +70,7 @@ class CarKalman(KalmanFilter): ]) P_initial = Q.copy() - obs_noise: Dict[int, Any] = { + obs_noise: dict[int, Any] = { ObservationKind.STEER_ANGLE: np.atleast_2d(math.radians(0.05)**2), ObservationKind.ANGLE_OFFSET_FAST: np.atleast_2d(math.radians(10.0)**2), ObservationKind.ROAD_ROLL: np.atleast_2d(math.radians(1.0)**2), diff --git a/selfdrive/locationd/paramsd.py b/selfdrive/locationd/paramsd.py index 183a8666e8..d124eb5f05 100755 --- a/selfdrive/locationd/paramsd.py +++ b/selfdrive/locationd/paramsd.py @@ -124,7 +124,7 @@ def main(): REPLAY = bool(int(os.getenv("REPLAY", "0"))) pm = messaging.PubMaster(['liveParameters']) - sm = messaging.SubMaster(['liveLocationKalman', 'carState'], poll=['liveLocationKalman']) + sm = messaging.SubMaster(['liveLocationKalman', 'carState'], poll='liveLocationKalman') params_reader = Params() # wait for stats about the car to come in from controls diff --git a/selfdrive/locationd/torqued.py b/selfdrive/locationd/torqued.py index f2c0248afa..69bab8d1fa 100755 --- a/selfdrive/locationd/torqued.py +++ b/selfdrive/locationd/torqued.py @@ -184,23 +184,23 @@ class TorqueEstimator(ParameterEstimator): liveTorqueParameters.version = VERSION liveTorqueParameters.useParams = self.use_params - if self.filtered_points.is_valid(): + # Calculate raw estimates when possible, only update filters when enough points are gathered + if self.filtered_points.is_calculable(): latAccelFactor, latAccelOffset, frictionCoeff = self.estimate_params() liveTorqueParameters.latAccelFactorRaw = float(latAccelFactor) liveTorqueParameters.latAccelOffsetRaw = float(latAccelOffset) liveTorqueParameters.frictionCoefficientRaw = float(frictionCoeff) - 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.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 + if self.filtered_points.is_valid(): + 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.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}) if with_points: liveTorqueParameters.points = self.filtered_points.get_points()[:, [0, 2]].tolist() @@ -218,7 +218,7 @@ def main(demo=False): config_realtime_process([0, 1, 2, 3], 5) pm = messaging.PubMaster(['liveTorqueParameters']) - sm = messaging.SubMaster(['carControl', 'carState', 'liveLocationKalman'], poll=['liveLocationKalman']) + sm = messaging.SubMaster(['carControl', 'carState', 'liveLocationKalman'], poll='liveLocationKalman') params = Params() with car.CarParams.from_bytes(params.get("CarParams", block=True)) as CP: diff --git a/selfdrive/manager/build.py b/selfdrive/manager/build.py index f2758cf32b..067e1b5a1e 100755 --- a/selfdrive/manager/build.py +++ b/selfdrive/manager/build.py @@ -2,7 +2,6 @@ import os import subprocess from pathlib import Path -from typing import List # NOTE: Do NOT import anything here that needs be built (e.g. params) from openpilot.common.basedir import BASEDIR @@ -29,7 +28,7 @@ def build(spinner: Spinner, dirty: bool = False, minimal: bool = False) -> None: # building with all cores can result in using too # much memory, so retry with less parallelism - compile_output: List[bytes] = [] + compile_output: list[bytes] = [] for n in (nproc, nproc/2, 1): compile_output.clear() scons: subprocess.Popen = subprocess.Popen(["scons", f"-j{int(n)}", "--cache-populate", *extra_args], cwd=BASEDIR, env=env, stderr=subprocess.PIPE) diff --git a/selfdrive/manager/helpers.py b/selfdrive/manager/helpers.py index 1b6f8df1ef..047d0ac2d6 100644 --- a/selfdrive/manager/helpers.py +++ b/selfdrive/manager/helpers.py @@ -1,9 +1,10 @@ +import errno +import fcntl import os import sys -import fcntl -import errno -import signal +import pathlib import shutil +import signal import subprocess import tempfile import threading @@ -52,11 +53,13 @@ def write_onroad_params(started, params): def save_bootlog(): # copy current params tmp = tempfile.mkdtemp() - shutil.copytree(Params().get_param_path() + "/..", tmp, dirs_exist_ok=True) + params_dirname = pathlib.Path(Params().get_param_path()).name + params_dir = os.path.join(tmp, params_dirname) + shutil.copytree(Params().get_param_path(), params_dir, dirs_exist_ok=True) def fn(tmpdir): env = os.environ.copy() - env['PARAMS_ROOT'] = tmpdir + env['PARAMS_COPY_PATH'] = tmpdir subprocess.call("./bootlog", cwd=os.path.join(BASEDIR, "system/loggerd"), env=env) shutil.rmtree(tmpdir) t = threading.Thread(target=fn, args=(tmp, )) diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 5c0d5518be..24dceaaf08 100755 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -4,14 +4,12 @@ import os import signal import sys import traceback -from typing import List, Tuple, Union from cereal import log import cereal.messaging as messaging import openpilot.selfdrive.sentry as sentry from openpilot.common.params import Params, ParamKeyType from openpilot.common.text_window import TextWindow -from openpilot.selfdrive.boardd.set_time import set_time from openpilot.system.hardware import HARDWARE, PC from openpilot.selfdrive.manager.helpers import unblock_stdout, write_onroad_params, save_bootlog from openpilot.selfdrive.manager.process import ensure_running @@ -20,15 +18,11 @@ from openpilot.selfdrive.athena.registration import register, UNREGISTERED_DONGL from openpilot.common.swaglog import cloudlog, add_file_handler from openpilot.system.version import is_dirty, get_commit, get_version, get_origin, get_short_branch, \ get_normalized_origin, terms_version, training_version, \ - is_tested_branch, is_release_branch + is_tested_branch, is_release_branch, get_commit_date def manager_init() -> None: - # update system time from panda - set_time(cloudlog) - - # save boot log save_bootlog() params = Params() @@ -38,7 +32,7 @@ def manager_init() -> None: if is_release_branch(): params.clear_all(ParamKeyType.DEVELOPMENT_ONLY) - default_params: List[Tuple[str, Union[str, bytes]]] = [ + default_params: list[tuple[str, str | bytes]] = [ ("CompletedTrainingVersion", "0"), ("DisengageOnAccelerator", "0"), ("GsmMetered", "1"), @@ -70,9 +64,10 @@ def manager_init() -> None: params.put("Version", get_version()) params.put("TermsVersion", terms_version) params.put("TrainingVersion", training_version) - params.put("GitCommit", get_commit(default="")) - params.put("GitBranch", get_short_branch(default="")) - params.put("GitRemote", get_origin(default="")) + params.put("GitCommit", get_commit()) + params.put("GitCommitDate", get_commit_date()) + params.put("GitBranch", get_short_branch()) + params.put("GitRemote", get_origin()) params.put_bool("IsTestedBranch", is_tested_branch()) params.put_bool("IsReleaseBranch", is_release_branch()) @@ -84,6 +79,9 @@ def manager_init() -> None: serial = params.get("HardwareSerial") raise Exception(f"Registration failed for device {serial}") os.environ['DONGLE_ID'] = dongle_id # Needed for swaglog + os.environ['GIT_ORIGIN'] = get_normalized_origin() # Needed for swaglog + os.environ['GIT_BRANCH'] = get_short_branch() # Needed for swaglog + os.environ['GIT_COMMIT'] = get_commit() # Needed for swaglog if not is_dirty(): os.environ['CLEAN'] = '1' @@ -122,14 +120,14 @@ def manager_thread() -> None: params = Params() - ignore: List[str] = [] + ignore: list[str] = [] if params.get("DongleId", encoding='utf8') in (None, UNREGISTERED_DONGLE_ID): ignore += ["manage_athenad", "uploader"] if os.getenv("NOBOARD") is not None: ignore.append("pandad") ignore += [x for x in os.getenv("BLOCK", "").split(",") if len(x) > 0] - sm = messaging.SubMaster(['deviceState', 'carParams'], poll=['deviceState']) + sm = messaging.SubMaster(['deviceState', 'carParams'], poll='deviceState') pm = messaging.PubMaster(['managerState']) write_onroad_params(False, params) @@ -138,7 +136,7 @@ def manager_thread() -> None: started_prev = False while True: - sm.update() + sm.update(1000) started = sm['deviceState'].started @@ -155,7 +153,7 @@ def manager_thread() -> None: ensure_running(managed_processes.values(), started, params=params, CP=sm['carParams'], not_run=ignore) - running = ' '.join("%s%s\u001b[0m" % ("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) + running = ' '.join("{}{}\u001b[0m".format("\u001b[32m" if p.proc.is_alive() else "\u001b[31m", p.name) for p in managed_processes.values() if p.proc) print(running) cloudlog.debug(running) diff --git a/selfdrive/manager/process.py b/selfdrive/manager/process.py index 523e1fd209..7964f5229d 100644 --- a/selfdrive/manager/process.py +++ b/selfdrive/manager/process.py @@ -4,7 +4,7 @@ import signal import struct import time import subprocess -from typing import Optional, Callable, List, ValuesView +from collections.abc import Callable, ValuesView from abc import ABC, abstractmethod from multiprocessing import Process @@ -47,7 +47,7 @@ def launcher(proc: str, name: str) -> None: raise -def nativelauncher(pargs: List[str], cwd: str, name: str) -> None: +def nativelauncher(pargs: list[str], cwd: str, name: str) -> None: os.environ['MANAGER_DAEMON'] = name # exec the process @@ -67,12 +67,12 @@ class ManagerProcess(ABC): daemon = False sigkill = False should_run: Callable[[bool, Params, car.CarParams], bool] - proc: Optional[Process] = None + proc: Process | None = None enabled = True name = "" last_watchdog_time = 0 - watchdog_max_dt: Optional[int] = None + watchdog_max_dt: int | None = None watchdog_seen = False shutting_down = False @@ -109,7 +109,7 @@ class ManagerProcess(ABC): else: self.watchdog_seen = True - def stop(self, retry: bool = True, block: bool = True, sig: Optional[signal.Signals] = None) -> Optional[int]: + def stop(self, retry: bool = True, block: bool = True, sig: signal.Signals = None) -> int | None: if self.proc is None: return None @@ -274,18 +274,20 @@ class DaemonProcess(ManagerProcess): def ensure_running(procs: ValuesView[ManagerProcess], started: bool, params=None, CP: car.CarParams=None, - not_run: Optional[List[str]]=None) -> List[ManagerProcess]: + not_run: list[str] | None=None) -> list[ManagerProcess]: if not_run is None: not_run = [] running = [] for p in procs: if p.enabled and p.name not in not_run and p.should_run(started, params, CP): - p.start() running.append(p) else: p.stop(block=False) p.check_watchdog(started) + for p in running: + p.start() + return running diff --git a/selfdrive/manager/test/test_manager.py b/selfdrive/manager/test/test_manager.py index fbeb932a86..1ae94b26a1 100755 --- a/selfdrive/manager/test/test_manager.py +++ b/selfdrive/manager/test/test_manager.py @@ -5,6 +5,8 @@ import signal import time import unittest +from parameterized import parameterized + from cereal import car from openpilot.common.params import Params import openpilot.selfdrive.manager.manager as manager @@ -38,13 +40,13 @@ class TestManager(unittest.TestCase): # TODO: ensure there are blacklisted procs until we have a dedicated test self.assertTrue(len(BLACKLIST_PROCS), "No blacklisted procs to test not_run") - def test_startup_time(self): - for _ in range(10): - start = time.monotonic() - os.environ['PREPAREONLY'] = '1' - manager.main() - t = time.monotonic() - start - assert t < MAX_STARTUP_TIME, f"startup took {t}s, expected <{MAX_STARTUP_TIME}s" + @parameterized.expand([(i,) for i in range(10)]) + def test_startup_time(self, index): + start = time.monotonic() + os.environ['PREPAREONLY'] = '1' + manager.main() + t = time.monotonic() - start + assert t < MAX_STARTUP_TIME, f"startup took {t}s, expected <{MAX_STARTUP_TIME}s" @unittest.skip("this test is flaky the way it's currently written, should be moved to test_onroad") def test_clean_exit(self): diff --git a/selfdrive/modeld/dmonitoringmodeld.py b/selfdrive/modeld/dmonitoringmodeld.py index 1e25964702..ef403b44fc 100755 --- a/selfdrive/modeld/dmonitoringmodeld.py +++ b/selfdrive/modeld/dmonitoringmodeld.py @@ -6,7 +6,6 @@ import time import ctypes import numpy as np from pathlib import Path -from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -53,7 +52,7 @@ class DMonitoringModelResult(ctypes.Structure): ("wheel_on_right_prob", ctypes.c_float)] class ModelState: - inputs: Dict[str, np.ndarray] + inputs: dict[str, np.ndarray] output: np.ndarray model: ModelRunner @@ -68,7 +67,7 @@ class ModelState: self.model.addInput("input_img", None) self.model.addInput("calib", self.inputs['calib']) - def run(self, buf:VisionBuf, calib:np.ndarray) -> Tuple[np.ndarray, float]: + def run(self, buf:VisionBuf, calib:np.ndarray) -> tuple[np.ndarray, float]: self.inputs['calib'][:] = calib v_offset = buf.height - MODEL_HEIGHT diff --git a/selfdrive/modeld/fill_model_msg.py b/selfdrive/modeld/fill_model_msg.py index 93d1c7e77b..c39ec2da3d 100644 --- a/selfdrive/modeld/fill_model_msg.py +++ b/selfdrive/modeld/fill_model_msg.py @@ -1,7 +1,6 @@ import os import capnp import numpy as np -from typing import Dict from cereal import log from openpilot.selfdrive.modeld.constants import ModelConstants, Plan, Meta @@ -42,10 +41,10 @@ def fill_xyvat(builder, t, x, y, v, a, x_std=None, y_std=None, v_std=None, a_std if a_std is not None: builder.aStd = a_std.tolist() -def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray], publish_state: PublishState, +def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, np.ndarray], publish_state: PublishState, vipc_frame_id: int, vipc_frame_id_extra: int, frame_id: int, frame_drop: float, timestamp_eof: int, timestamp_llk: int, model_execution_time: float, - nav_enabled: bool, v_ego: float, steer_delay: float, valid: bool) -> None: + nav_enabled: bool, valid: bool) -> None: frame_age = frame_id - vipc_frame_id if frame_id > vipc_frame_id else 0 msg.valid = valid @@ -174,7 +173,7 @@ def fill_model_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, if SEND_RAW_PRED: modelV2.rawPredictions = net_output_data['raw_pred'].tobytes() -def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: Dict[str, np.ndarray], +def fill_pose_msg(msg: capnp._DynamicStructBuilder, net_output_data: dict[str, np.ndarray], vipc_frame_id: int, vipc_dropped_frames: int, timestamp_eof: int, live_calib_seen: bool) -> None: msg.valid = live_calib_seen & (vipc_dropped_frames < 1) cameraOdometry = msg.cameraOdometry diff --git a/selfdrive/modeld/get_model_metadata.py b/selfdrive/modeld/get_model_metadata.py index 187f83399b..144860204f 100755 --- a/selfdrive/modeld/get_model_metadata.py +++ b/selfdrive/modeld/get_model_metadata.py @@ -4,9 +4,8 @@ import pathlib import onnx import codecs import pickle -from typing import Tuple -def get_name_and_shape(value_info:onnx.ValueInfoProto) -> Tuple[str, Tuple[int,...]]: +def get_name_and_shape(value_info:onnx.ValueInfoProto) -> tuple[str, tuple[int,...]]: shape = tuple([int(dim.dim_value) for dim in value_info.type.tensor_type.shape.dim]) name = value_info.name return name, shape diff --git a/selfdrive/modeld/modeld.py b/selfdrive/modeld/modeld.py index 5b227d08e9..e086b8aaf8 100755 --- a/selfdrive/modeld/modeld.py +++ b/selfdrive/modeld/modeld.py @@ -6,7 +6,6 @@ import numpy as np import cereal.messaging as messaging from cereal import car, log from pathlib import Path -from typing import Dict, Optional from setproctitle import setproctitle from cereal.messaging import PubMaster, SubMaster from cereal.visionipc import VisionIpcClient, VisionStreamType, VisionBuf @@ -45,7 +44,7 @@ class FrameMeta: class ModelState: frame: ModelFrame wide_frame: ModelFrame - inputs: Dict[str, np.ndarray] + inputs: dict[str, np.ndarray] output: np.ndarray prev_desire: np.ndarray # for tracking the rising edge of the pulse model: ModelRunner @@ -78,14 +77,14 @@ class ModelState: for k,v in self.inputs.items(): self.model.addInput(k, v) - def slice_outputs(self, model_outputs: np.ndarray) -> Dict[str, np.ndarray]: + def slice_outputs(self, model_outputs: np.ndarray) -> dict[str, np.ndarray]: parsed_model_outputs = {k: model_outputs[np.newaxis, v] for k,v in self.output_slices.items()} if SEND_RAW_PRED: parsed_model_outputs['raw_pred'] = model_outputs.copy() return parsed_model_outputs def run(self, buf: VisionBuf, wbuf: VisionBuf, transform: np.ndarray, transform_wide: np.ndarray, - inputs: Dict[str, np.ndarray], prepare_only: bool) -> Optional[Dict[str, np.ndarray]]: + inputs: dict[str, np.ndarray], prepare_only: bool) -> dict[str, np.ndarray] | None: # Model decides when action is completed, so desire input is just a pulse triggered on rising edge inputs['desire'][0] = 0 self.inputs['desire'][:-ModelConstants.DESIRE_LEN] = self.inputs['desire'][ModelConstants.DESIRE_LEN:] @@ -116,12 +115,16 @@ class ModelState: def main(demo=False): + cloudlog.warning("modeld init") + sentry.set_tag("daemon", PROCESS_NAME) cloudlog.bind(daemon=PROCESS_NAME) setproctitle(PROCESS_NAME) config_realtime_process(7, 54) + cloudlog.warning("setting up CL context") cl_context = CLContext() + cloudlog.warning("CL context ready; loading model") model = ModelState(cl_context) cloudlog.warning("models loaded, modeld starting") @@ -152,12 +155,8 @@ def main(demo=False): pm = PubMaster(["modelV2", "cameraOdometry"]) sm = SubMaster(["carState", "roadCameraState", "liveCalibration", "driverMonitoringState", "navModel", "navInstruction", "carControl"]) - publish_state = PublishState() params = Params() - with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: - steer_delay = msg.steerActuatorDelay + .2 - #steer_delay = 0.4 # setup filter to track dropped frames frame_dropped_filter = FirstOrderFilter(0., 10., 1. / ModelConstants.MODEL_FREQ) @@ -177,13 +176,15 @@ def main(demo=False): if demo: CP = get_demo_car_params() - with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: - CP = msg - cloudlog.info("plannerd got CarParams: %s", CP.carName) + else: + with car.CarParams.from_bytes(params.get("CarParams", block=True)) as msg: + CP = msg + cloudlog.info("modeld got CarParams: %s", CP.carName) + # TODO this needs more thought, use .2s extra for now to estimate other delays steer_delay = CP.steerActuatorDelay + .2 - DH = DesireHelper() + DH = DesireHelper() while True: # Keep receiving frames until we are at least 1 frame ahead of previous extra frame @@ -219,13 +220,10 @@ def main(demo=False): buf_extra = buf_main meta_extra = meta_main - # TODO: path planner timeout? sm.update(0) desire = DH.desire - v_ego = sm["carState"].vEgo is_rhd = sm["driverMonitoringState"].isRHD frame_id = sm["roadCameraState"].frameId - # TODO add lag lateral_control_params = np.array([sm["carState"].vEgo, steer_delay], dtype=np.float32) if sm.updated["liveCalibration"]: device_from_calib_euler = np.array(sm["liveCalibration"].rpyCalib, dtype=np.float32) @@ -277,7 +275,7 @@ def main(demo=False): if prepare_only: cloudlog.error(f"skipping model eval. Dropped {vipc_dropped_frames} frames") - inputs:Dict[str, np.ndarray] = { + inputs:dict[str, np.ndarray] = { 'desire': vec_desire, 'traffic_convention': traffic_convention, 'lateral_control_params': lateral_control_params, @@ -293,7 +291,7 @@ def main(demo=False): modelv2_send = messaging.new_message('modelV2') posenet_send = messaging.new_message('cameraOdometry') fill_model_msg(modelv2_send, model_output, publish_state, meta_main.frame_id, meta_extra.frame_id, frame_id, frame_drop_ratio, - meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, v_ego, steer_delay, live_calib_seen) + meta_main.timestamp_eof, timestamp_llk, model_execution_time, nav_enabled, live_calib_seen) desire_state = modelv2_send.modelV2.meta.desireState l_lane_change_prob = desire_state[log.Desire.laneChangeLeft] diff --git a/selfdrive/modeld/models/commonmodel_pyx.pxd b/selfdrive/modeld/models/commonmodel_pyx.pxd index 21c0716de4..97e3914588 100644 --- a/selfdrive/modeld/models/commonmodel_pyx.pxd +++ b/selfdrive/modeld/models/commonmodel_pyx.pxd @@ -7,7 +7,7 @@ cdef class CLContext(BaseCLContext): pass cdef class CLMem: - cdef cl_mem * mem; + cdef cl_mem * mem @staticmethod cdef create(void*) diff --git a/selfdrive/modeld/models/supercombo.onnx b/selfdrive/modeld/models/supercombo.onnx index dc93d84135..ce346a406a 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:763821410b35b06b598cacfa5bc3e312610b3f8de2729e0d5954d7571b6794be -size 48219112 +oid sha256:c11f99aab832242a604b1bb4c4cf391c9c3d5e90cbc07ab0d4c133473b56a3a4 +size 48193749 diff --git a/selfdrive/modeld/navmodeld.py b/selfdrive/modeld/navmodeld.py index ed0b597dfe..4672734681 100755 --- a/selfdrive/modeld/navmodeld.py +++ b/selfdrive/modeld/navmodeld.py @@ -5,7 +5,6 @@ import time import ctypes import numpy as np from pathlib import Path -from typing import Tuple, Dict from cereal import messaging from cereal.messaging import PubMaster, SubMaster @@ -41,7 +40,7 @@ class NavModelResult(ctypes.Structure): ("features", ctypes.c_float*NAV_FEATURE_LEN)] class ModelState: - inputs: Dict[str, np.ndarray] + inputs: dict[str, np.ndarray] output: np.ndarray model: ModelRunner @@ -52,7 +51,7 @@ class ModelState: self.model = ModelRunner(MODEL_PATHS, self.output, Runtime.DSP, True, None) self.model.addInput("input_img", None) - def run(self, buf:np.ndarray) -> Tuple[np.ndarray, float]: + def run(self, buf:np.ndarray) -> tuple[np.ndarray, float]: self.inputs['input_img'][:] = buf t1 = time.perf_counter() diff --git a/selfdrive/modeld/parse_model_outputs.py b/selfdrive/modeld/parse_model_outputs.py index 01cba29d1c..af57e11d03 100644 --- a/selfdrive/modeld/parse_model_outputs.py +++ b/selfdrive/modeld/parse_model_outputs.py @@ -1,5 +1,4 @@ import numpy as np -from typing import Dict from openpilot.selfdrive.modeld.constants import ModelConstants def sigmoid(x): @@ -82,7 +81,7 @@ class Parser: outs[name] = pred_mu_final.reshape(final_shape) outs[name + '_stds'] = pred_std_final.reshape(final_shape) - def parse_outputs(self, outs: Dict[str, np.ndarray]) -> Dict[str, np.ndarray]: + def parse_outputs(self, outs: dict[str, np.ndarray]) -> dict[str, np.ndarray]: self.parse_mdn('plan', outs, in_N=ModelConstants.PLAN_MHP_N, out_N=ModelConstants.PLAN_MHP_SELECTION, out_shape=(ModelConstants.IDX_N,ModelConstants.PLAN_WIDTH)) self.parse_mdn('lane_lines', outs, in_N=0, out_N=0, out_shape=(ModelConstants.NUM_LANE_LINES,ModelConstants.IDX_N,ModelConstants.LANE_LINES_WIDTH)) diff --git a/selfdrive/modeld/runners/onnxmodel.py b/selfdrive/modeld/runners/onnxmodel.py index f86bee3878..69b44a5a97 100644 --- a/selfdrive/modeld/runners/onnxmodel.py +++ b/selfdrive/modeld/runners/onnxmodel.py @@ -3,7 +3,7 @@ import itertools import os import sys import numpy as np -from typing import Tuple, Dict, Union, Any +from typing import Any from openpilot.selfdrive.modeld.runners.runmodel_pyx import RunModel @@ -38,7 +38,7 @@ def create_ort_session(path, fp16_to_fp32): options = ort.SessionOptions() options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_DISABLE_ALL - provider: Union[str, Tuple[str, Dict[Any, Any]]] + provider: str | tuple[str, dict[Any, Any]] if 'OpenVINOExecutionProvider' in ort.get_available_providers() and 'ONNXCPU' not in os.environ: provider = 'OpenVINOExecutionProvider' elif 'CUDAExecutionProvider' in ort.get_available_providers() and 'ONNXCPU' not in os.environ: diff --git a/selfdrive/modeld/runners/runmodel_pyx.pyx b/selfdrive/modeld/runners/runmodel_pyx.pyx index cdc62a79be..e1b201a6a9 100644 --- a/selfdrive/modeld/runners/runmodel_pyx.pyx +++ b/selfdrive/modeld/runners/runmodel_pyx.pyx @@ -2,7 +2,6 @@ # cython: c_string_encoding=ascii from libcpp.string cimport string -from libc.string cimport memcpy from .runmodel cimport USE_CPU_RUNTIME, USE_GPU_RUNTIME, USE_DSP_RUNTIME from selfdrive.modeld.models.commonmodel_pyx cimport CLMem diff --git a/selfdrive/modeld/thneed/serialize.cc b/selfdrive/modeld/thneed/serialize.cc index 6ed5c08e81..3dc2bef414 100644 --- a/selfdrive/modeld/thneed/serialize.cc +++ b/selfdrive/modeld/thneed/serialize.cc @@ -4,13 +4,14 @@ #include "third_party/json11/json11.hpp" #include "common/util.h" #include "common/clutil.h" +#include "common/swaglog.h" #include "selfdrive/modeld/thneed/thneed.h" using namespace json11; extern map g_program_source; void Thneed::load(const char *filename) { - printf("Thneed::load: loading from %s\n", filename); + LOGD("Thneed::load: loading from %s\n", filename); string buf = util::read_file(filename); int jsz = *(int *)buf.data(); @@ -74,8 +75,8 @@ void Thneed::load(const char *filename) { clbuf = clCreateImage(context, CL_MEM_READ_WRITE, &format, &desc, NULL, &errcode); #endif if (clbuf == NULL) { - printf("clError: %s create image %zux%zu rp %zu with buffer %p\n", cl_get_error_string(errcode), - desc.image_width, desc.image_height, desc.image_row_pitch, desc.buffer); + LOGE("clError: %s create image %zux%zu rp %zu with buffer %p\n", cl_get_error_string(errcode), + desc.image_width, desc.image_height, desc.image_row_pitch, desc.buffer); } assert(clbuf != NULL); } @@ -95,11 +96,11 @@ void Thneed::load(const char *filename) { cl_mem aa = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; input_clmem.push_back(aa); input_sizes.push_back(sz); - printf("Thneed::load: adding input %s with size %d\n", mobj["name"].string_value().data(), sz); + LOGD("Thneed::load: adding input %s with size %d\n", mobj["name"].string_value().data(), sz); cl_int cl_err; void *ret = clEnqueueMapBuffer(command_queue, aa, CL_TRUE, CL_MAP_WRITE, 0, sz, 0, NULL, NULL, &cl_err); - if (cl_err != CL_SUCCESS) printf("clError: %s map %p %d\n", cl_get_error_string(cl_err), aa, sz); + if (cl_err != CL_SUCCESS) LOGE("clError: %s map %p %d\n", cl_get_error_string(cl_err), aa, sz); assert(cl_err == CL_SUCCESS); inputs.push_back(ret); } @@ -107,7 +108,7 @@ void Thneed::load(const char *filename) { for (auto &obj : jdat["outputs"].array_items()) { auto mobj = obj.object_items(); int sz = mobj["size"].int_value(); - printf("Thneed::save: adding output with size %d\n", sz); + LOGD("Thneed::save: adding output with size %d\n", sz); // TODO: support multiple outputs output = real_mem[*(cl_mem*)(mobj["buffer_id"].string_value().data())]; assert(output != NULL); diff --git a/selfdrive/modeld/transforms/transform.cl b/selfdrive/modeld/transforms/transform.cl index 357ef87321..2ca25920cd 100644 --- a/selfdrive/modeld/transforms/transform.cl +++ b/selfdrive/modeld/transforms/transform.cl @@ -22,20 +22,20 @@ __kernel void warpPerspective(__global const uchar * src, W = W != 0.0f ? INTER_TAB_SIZE / W : 0.0f; int X = rint(X0 * W), Y = rint(Y0 * W); - short sx = convert_short_sat(X >> INTER_BITS); - short sy = convert_short_sat(Y >> INTER_BITS); + int sx = convert_short_sat(X >> INTER_BITS); + int sy = convert_short_sat(Y >> INTER_BITS); + + short sx_clamp = clamp(sx, 0, src_cols - 1); + short sx_p1_clamp = clamp(sx + 1, 0, src_cols - 1); + short sy_clamp = clamp(sy, 0, src_rows - 1); + short sy_p1_clamp = clamp(sy + 1, 0, src_rows - 1); + int v0 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v1 = convert_int(src[mad24(sy_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + int v2 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_clamp*src_px_stride)]); + int v3 = convert_int(src[mad24(sy_p1_clamp, src_row_stride, src_offset + sx_p1_clamp*src_px_stride)]); + short ay = (short)(Y & (INTER_TAB_SIZE - 1)); short ax = (short)(X & (INTER_TAB_SIZE - 1)); - - int v0 = (sx >= 0 && sx < src_cols && sy >= 0 && sy < src_rows) ? - convert_int(src[mad24(sy, src_row_stride, src_offset + sx*src_px_stride)]) : 0; - int v1 = (sx+1 >= 0 && sx+1 < src_cols && sy >= 0 && sy < src_rows) ? - convert_int(src[mad24(sy, src_row_stride, src_offset + (sx+1)*src_px_stride)]) : 0; - int v2 = (sx >= 0 && sx < src_cols && sy+1 >= 0 && sy+1 < src_rows) ? - convert_int(src[mad24(sy+1, src_row_stride, src_offset + sx*src_px_stride)]) : 0; - int v3 = (sx+1 >= 0 && sx+1 < src_cols && sy+1 >= 0 && sy+1 < src_rows) ? - convert_int(src[mad24(sy+1, src_row_stride, src_offset + (sx+1)*src_px_stride)]) : 0; - float taby = 1.f/INTER_TAB_SIZE*ay; float tabx = 1.f/INTER_TAB_SIZE*ax; diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 7a24c0107e..579e79093f 100755 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -15,7 +15,7 @@ def dmonitoringd_thread(): params = Params() pm = messaging.PubMaster(['driverMonitoringState']) - sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll=['driverStateV2']) + sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'controlsState', 'modelV2'], poll='driverStateV2') driver_status = DriverStatus(rhd_saved=params.get_bool("IsRhdDetected")) @@ -43,7 +43,7 @@ def dmonitoringd_thread(): # Get data from dmonitoringmodeld events = Events() - if sm.all_checks(): + if sm.all_checks() and len(sm['liveCalibration'].rpyCalib): driver_status.update_states(sm['driverStateV2'], sm['liveCalibration'].rpyCalib, sm['carState'].vEgo, sm['controlsState'].enabled) # Block engaging after max number of distrations diff --git a/selfdrive/navd/SConscript b/selfdrive/navd/SConscript index c116ef1535..5a173c0351 100644 --- a/selfdrive/navd/SConscript +++ b/selfdrive/navd/SConscript @@ -1,14 +1,14 @@ Import('qt_env', 'arch', 'common', 'messaging', 'visionipc', 'cereal', 'transformations') map_env = qt_env.Clone() -libs = ['qt_widgets', 'qt_util', 'qmapboxgl', common, messaging, cereal, visionipc, transformations, +libs = ['qt_widgets', 'qt_util', 'QMapLibre', common, messaging, cereal, visionipc, transformations, 'zmq', 'capnp', 'kj', 'm', 'OpenCL', 'ssl', 'crypto', 'pthread', 'json11'] + map_env["LIBS"] if arch == 'larch64': libs.append(':libEGL_mesa.so.0') if arch in ['larch64', 'aarch64', 'x86_64']: if arch == 'x86_64': - rpath = Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath + rpath = Dir(f"#third_party/maplibre-native-qt/{arch}/lib").srcnode().abspath map_env["RPATH"] += [rpath, ] style_path = File("style.json").abspath diff --git a/selfdrive/navd/helpers.py b/selfdrive/navd/helpers.py index 55c3f88a9a..0f0410c2c7 100644 --- a/selfdrive/navd/helpers.py +++ b/selfdrive/navd/helpers.py @@ -2,7 +2,7 @@ from __future__ import annotations import json import math -from typing import Any, Dict, List, Optional, Tuple, Union, cast +from typing import Any, cast from openpilot.common.conversions import Conversions from openpilot.common.numpy_fast import clip @@ -22,13 +22,13 @@ class Coordinate: def __init__(self, latitude: float, longitude: float) -> None: self.latitude = latitude self.longitude = longitude - self.annotations: Dict[str, float] = {} + self.annotations: dict[str, float] = {} @classmethod - def from_mapbox_tuple(cls, t: Tuple[float, float]) -> Coordinate: + def from_mapbox_tuple(cls, t: tuple[float, float]) -> Coordinate: return cls(t[1], t[0]) - def as_dict(self) -> Dict[str, float]: + def as_dict(self) -> dict[str, float]: return {'latitude': self.latitude, 'longitude': self.longitude} def __str__(self) -> str: @@ -83,7 +83,7 @@ def minimum_distance(a: Coordinate, b: Coordinate, p: Coordinate): return projection.distance_to(p) -def distance_along_geometry(geometry: List[Coordinate], pos: Coordinate) -> float: +def distance_along_geometry(geometry: list[Coordinate], pos: Coordinate) -> float: if len(geometry) <= 2: return geometry[0].distance_to(pos) @@ -106,7 +106,7 @@ def distance_along_geometry(geometry: List[Coordinate], pos: Coordinate) -> floa return total_distance_closest -def coordinate_from_param(param: str, params: Optional[Params] = None) -> Optional[Coordinate]: +def coordinate_from_param(param: str, params: Params = None) -> Coordinate | None: if params is None: params = Params() @@ -130,7 +130,7 @@ def string_to_direction(direction: str) -> str: return 'none' -def maxspeed_to_ms(maxspeed: Dict[str, Union[str, float]]) -> float: +def maxspeed_to_ms(maxspeed: dict[str, str | float]) -> float: unit = cast(str, maxspeed['unit']) speed = cast(float, maxspeed['speed']) return SPEED_CONVERSIONS[unit] * speed @@ -140,7 +140,7 @@ def field_valid(dat: dict, field: str) -> bool: return field in dat and dat[field] is not None -def parse_banner_instructions(banners: Any, distance_to_maneuver: float = 0.0) -> Optional[Dict[str, Any]]: +def parse_banner_instructions(banners: Any, distance_to_maneuver: float = 0.0) -> dict[str, Any] | None: if not len(banners): return None diff --git a/selfdrive/navd/map_renderer.cc b/selfdrive/navd/map_renderer.cc index 543515c631..d52ee162bd 100644 --- a/selfdrive/navd/map_renderer.cc +++ b/selfdrive/navd/map_renderer.cc @@ -28,16 +28,16 @@ float get_zoom_level_for_scale(float lat, float meters_per_pixel) { return log2(num_tiles) - 1; } -QMapbox::Coordinate get_point_along_line(float lat, float lon, float bearing, float dist) { +QMapLibre::Coordinate get_point_along_line(float lat, float lon, float bearing, float dist) { float ang_dist = dist / EARTH_RADIUS_METERS; float lat1 = DEG2RAD(lat), lon1 = DEG2RAD(lon), bearing1 = DEG2RAD(bearing); float lat2 = asin(sin(lat1)*cos(ang_dist) + cos(lat1)*sin(ang_dist)*cos(bearing1)); float lon2 = lon1 + atan2(sin(bearing1)*sin(ang_dist)*cos(lat1), cos(ang_dist)-sin(lat1)*sin(lat2)); - return QMapbox::Coordinate(RAD2DEG(lat2), RAD2DEG(lon2)); + return QMapLibre::Coordinate(RAD2DEG(lat2), RAD2DEG(lon2)); } -MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_settings(settings) { +MapRenderer::MapRenderer(const QMapLibre::Settings &settings, bool online) : m_settings(settings) { QSurfaceFormat fmt; fmt.setRenderableType(QSurfaceFormat::OpenGLES); @@ -60,8 +60,8 @@ MapRenderer::MapRenderer(const QMapboxGLSettings &settings, bool online) : m_set fbo.reset(new QOpenGLFramebufferObject(WIDTH, HEIGHT, fbo_format)); std::string style = util::read_file(STYLE_PATH); - m_map.reset(new QMapboxGL(nullptr, m_settings, fbo->size(), 1)); - m_map->setCoordinateZoom(QMapbox::Coordinate(0, 0), DEFAULT_ZOOM); + m_map.reset(new QMapLibre::Map(nullptr, m_settings, fbo->size(), 1)); + m_map->setCoordinateZoom(QMapLibre::Coordinate(0, 0), DEFAULT_ZOOM); m_map->setStyleJson(style.c_str()); m_map->createRenderer(); ever_loaded = false; @@ -70,20 +70,20 @@ 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) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) { // Ignore expected signals // https://github.com/mapbox/mapbox-gl-native/blob/cf734a2fec960025350d8de0d01ad38aeae155a0/platform/qt/include/qmapboxgl.hpp#L116 if (ever_loaded) { - if (change != QMapboxGL::MapChange::MapChangeRegionWillChange && - change != QMapboxGL::MapChange::MapChangeRegionDidChange && - change != QMapboxGL::MapChange::MapChangeWillStartRenderingFrame && - change != QMapboxGL::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { + if (change != QMapLibre::Map::MapChange::MapChangeRegionWillChange && + change != QMapLibre::Map::MapChange::MapChangeRegionDidChange && + change != QMapLibre::Map::MapChange::MapChangeWillStartRenderingFrame && + change != QMapLibre::Map::MapChange::MapChangeDidFinishRenderingFrameFullyRendered) { LOGD("New map state: %d", change); } } }); - QObject::connect(m_map.data(), &QMapboxGL::mapLoadingFailed, [=](QMapboxGL::MapLoadingFailure err_code, const QString &reason) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapLoadingFailed, [=](QMapLibre::Map::MapLoadingFailure err_code, const QString &reason) { LOGE("Map loading failed with %d: '%s'\n", err_code, reason.toStdString().c_str()); }); @@ -145,7 +145,7 @@ void MapRenderer::msgUpdate() { timer->start(0); } -void MapRenderer::updatePosition(QMapbox::Coordinate position, float bearing) { +void MapRenderer::updatePosition(QMapLibre::Coordinate position, float bearing) { if (m_map.isNull()) { return; } @@ -261,10 +261,10 @@ void MapRenderer::updateRoute(QList coordinates) { initLayers(); auto route_points = coordinate_list_to_collection(coordinates); - QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); QVariantMap navSource; navSource["type"] = "geojson"; - navSource["data"] = QVariant::fromValue(feature); + navSource["data"] = QVariant::fromValue(feature); m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); } @@ -273,10 +273,9 @@ void MapRenderer::initLayers() { if (!m_map->layerExists("navLayer")) { LOGD("Initializing navLayer"); QVariantMap nav; - nav["id"] = "navLayer"; nav["type"] = "line"; nav["source"] = "navSource"; - m_map->addLayer(nav, "road-intersection"); + m_map->addLayer("navLayer", nav, "road-intersection"); m_map->setPaintProperty("navLayer", "line-color", QColor("grey")); m_map->setPaintProperty("navLayer", "line-width", 5); m_map->setLayoutProperty("navLayer", "line-cap", "round"); @@ -296,9 +295,10 @@ extern "C" { QApplication *app = new QApplication(argc, argv); assert(app); - QMapboxGLSettings settings; + QMapLibre::Settings settings; + settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider); settings.setApiBaseUrl(maps_host == nullptr ? MAPS_HOST : maps_host); - settings.setAccessToken(token == nullptr ? get_mapbox_token() : token); + settings.setApiKey(token == nullptr ? get_mapbox_token() : token); return new MapRenderer(settings, false); } diff --git a/selfdrive/navd/map_renderer.h b/selfdrive/navd/map_renderer.h index dc92c70b0f..fd5922b668 100644 --- a/selfdrive/navd/map_renderer.h +++ b/selfdrive/navd/map_renderer.h @@ -3,7 +3,8 @@ #include #include -#include +#include +#include #include #include #include @@ -19,7 +20,7 @@ class MapRenderer : public QObject { Q_OBJECT public: - MapRenderer(const QMapboxGLSettings &, bool online=true); + MapRenderer(const QMapLibre::Settings &, bool online=true); uint8_t* getImage(); void update(); bool loaded(); @@ -37,8 +38,8 @@ private: void publish(const double render_time, const bool loaded); void sendThumbnail(const uint64_t ts, const kj::Array &buf); - QMapboxGLSettings m_settings; - QScopedPointer m_map; + QMapLibre::Settings m_settings; + QScopedPointer m_map; void initLayers(); @@ -52,7 +53,7 @@ private: bool ever_loaded = false; public slots: - void updatePosition(QMapbox::Coordinate position, float bearing); + void updatePosition(QMapLibre::Coordinate position, float bearing); void updateRoute(QList coordinates); void msgUpdate(); }; diff --git a/selfdrive/navd/tests/test_map_renderer.py b/selfdrive/navd/tests/test_map_renderer.py index a7289d02d9..b5f186dbb0 100755 --- a/selfdrive/navd/tests/test_map_renderer.py +++ b/selfdrive/navd/tests/test_map_renderer.py @@ -11,30 +11,16 @@ import cereal.messaging as messaging from typing import Any from cereal.visionipc import VisionIpcClient, VisionStreamType +from openpilot.common.mock.generators import LLK_DECIMATION, LOCATION1, LOCATION2, generate_liveLocationKalman from openpilot.selfdrive.test.helpers import with_processes -LLK_DECIMATION = 10 CACHE_PATH = "/data/mbgl-cache-navd.db" -LOCATION1 = (32.7174, -117.16277) -LOCATION2 = (32.7558, -117.2037) - RENDER_FRAMES = 15 DEFAULT_ITERATIONS = RENDER_FRAMES * LLK_DECIMATION - LOCATION1_REPEATED = [LOCATION1] * DEFAULT_ITERATIONS LOCATION2_REPEATED = [LOCATION2] * DEFAULT_ITERATIONS -def gen_llk(location=LOCATION1): - msg = messaging.new_message('liveLocationKalman') - msg.liveLocationKalman.positionGeodetic = {'value': [*location, 0], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.positionECEF = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.calibratedOrientationNED = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.velocityCalibrated = {'value': [0., 0., 0.], 'std': [0., 0., 0.], 'valid': True} - msg.liveLocationKalman.status = 'valid' - msg.liveLocationKalman.gpsOK = True - return msg - class MapBoxInternetDisabledRequestHandler(http.server.BaseHTTPRequestHandler): INTERNET_ACTIVE = True @@ -134,7 +120,7 @@ class TestMapRenderer(unittest.TestCase): if starting_frame_id is None: starting_frame_id = prev_frame_id - llk = gen_llk(location) + llk = generate_liveLocationKalman(location) self.pm.send("liveLocationKalman", llk) self.pm.wait_for_readers_to_update("liveLocationKalman", 10) self.sm.update(1000 if frame_expected else 0) diff --git a/selfdrive/navd/tests/test_navd.py b/selfdrive/navd/tests/test_navd.py index e2a5944c2b..61be6cc387 100755 --- a/selfdrive/navd/tests/test_navd.py +++ b/selfdrive/navd/tests/test_navd.py @@ -4,6 +4,8 @@ import random import unittest import numpy as np +from parameterized import parameterized + import cereal.messaging as messaging from openpilot.common.params import Params from openpilot.selfdrive.manager.process_config import managed_processes @@ -24,7 +26,7 @@ class TestNavd(unittest.TestCase): managed_processes['navd'].start() for _ in range(30): self.sm.update(1000) - if all(f > 0 for f in self.sm.rcv_frame.values()): + if all(f > 0 for f in self.sm.recv_frame.values()): break else: raise Exception("didn't get a route") @@ -50,11 +52,11 @@ class TestNavd(unittest.TestCase): } self._check_route(start, end) - def test_random(self): - for _ in range(10): - start = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} - end = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} - self._check_route(start, end, check_coords=False) + @parameterized.expand([(i,) for i in range(10)]) + def test_random(self, index): + start = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} + end = {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} + self._check_route(start, end, check_coords=False) if __name__ == "__main__": diff --git a/selfdrive/sentry.py b/selfdrive/sentry.py index 6b14b6bbd6..5b63a9fe2d 100644 --- a/selfdrive/sentry.py +++ b/selfdrive/sentry.py @@ -44,7 +44,7 @@ def set_tag(key: str, value: str) -> None: def init(project: SentryProject) -> bool: # forks like to mess with this, so double check - comma_remote = is_comma_remote() and "commaai" in get_origin(default="") + comma_remote = is_comma_remote() and "commaai" in get_origin() if not comma_remote or not is_registered_device() or PC: return False diff --git a/selfdrive/statsd.py b/selfdrive/statsd.py index 94572b82c7..299aa295d7 100755 --- a/selfdrive/statsd.py +++ b/selfdrive/statsd.py @@ -5,7 +5,7 @@ import time from pathlib import Path from collections import defaultdict from datetime import datetime, timezone -from typing import NoReturn, Union, List, Dict +from typing import NoReturn from openpilot.common.params import Params from cereal.messaging import SubMaster @@ -61,7 +61,7 @@ class StatLog: def main() -> NoReturn: dongle_id = Params().get("DongleId", encoding='utf-8') - def get_influxdb_line(measurement: str, value: Union[float, Dict[str, float]], timestamp: datetime, tags: dict) -> str: + def get_influxdb_line(measurement: str, value: float | dict[str, float], timestamp: datetime, tags: dict) -> str: res = f"{measurement}" for k, v in tags.items(): res += f",{k}={str(v)}" @@ -102,7 +102,7 @@ def main() -> NoReturn: idx = 0 last_flush_time = time.monotonic() gauges = {} - samples: Dict[str, List[float]] = defaultdict(list) + samples: dict[str, list[float]] = defaultdict(list) try: while True: started_prev = sm['deviceState'].started diff --git a/selfdrive/test/ciui.py b/selfdrive/test/ciui.py index 3f33847b29..f3b0c1a98f 100755 --- a/selfdrive/test/ciui.py +++ b/selfdrive/test/ciui.py @@ -11,7 +11,7 @@ from openpilot.selfdrive.ui.qt.python_helpers import set_main_window class Window(QWidget): def __init__(self, parent=None): - super(Window, self).__init__(parent) + super().__init__(parent) layout = QVBoxLayout() self.setLayout(layout) @@ -47,7 +47,7 @@ class Window(QWidget): def update(self): for cmd, label in self.labels.items(): - out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, + out = subprocess.run(cmd, capture_output=True, shell=True, check=False, encoding='utf8').stdout label.setText(out.strip()) diff --git a/selfdrive/test/docker_common.sh b/selfdrive/test/docker_common.sh index 92da71ba66..f8a423762d 100644 --- a/selfdrive/test/docker_common.sh +++ b/selfdrive/test/docker_common.sh @@ -7,9 +7,6 @@ elif [ "$1" = "sim" ]; then elif [ "$1" = "prebuilt" ]; then export DOCKER_IMAGE=openpilot-prebuilt export DOCKER_FILE=Dockerfile.openpilot -elif [ "$1" = "cl" ]; then - export DOCKER_IMAGE=openpilot-base-cl - export DOCKER_FILE=Dockerfile.openpilot_base_cl else echo "Invalid docker build image: '$1'" exit 1 diff --git a/selfdrive/test/fuzzy_generation.py b/selfdrive/test/fuzzy_generation.py index 28c70a0ff4..26c35c0c18 100644 --- a/selfdrive/test/fuzzy_generation.py +++ b/selfdrive/test/fuzzy_generation.py @@ -1,6 +1,7 @@ import capnp import hypothesis.strategies as st -from typing import Any, Callable, Dict, List, Optional, Union +from typing import Any +from collections.abc import Callable from cereal import log @@ -12,7 +13,7 @@ class FuzzyGenerator: self.draw = draw self.real_floats = real_floats - def generate_native_type(self, field: str) -> st.SearchStrategy[Union[bool, int, float, str, bytes]]: + def generate_native_type(self, field: str) -> st.SearchStrategy[bool | int | float | str | bytes]: def floats(**kwargs) -> st.SearchStrategy[float]: allow_nan = not self.real_floats allow_infinity = not self.real_floats @@ -67,18 +68,18 @@ class FuzzyGenerator: else: return self.generate_struct(field.schema) - def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: Optional[str] = None) -> st.SearchStrategy[Dict[str, Any]]: - full_fill: List[str] = list(schema.non_union_fields) - single_fill: List[str] = [event] if event else [self.draw(st.sampled_from(schema.union_fields))] if schema.union_fields else [] + def generate_struct(self, schema: capnp.lib.capnp._StructSchema, event: str = None) -> st.SearchStrategy[dict[str, Any]]: + full_fill: list[str] = list(schema.non_union_fields) + single_fill: list[str] = [event] if event else [self.draw(st.sampled_from(schema.union_fields))] if schema.union_fields else [] return st.fixed_dictionaries({field: self.generate_field(schema.fields[field]) for field in full_fill + single_fill}) @classmethod - def get_random_msg(cls, draw: DrawType, struct: capnp.lib.capnp._StructModule, real_floats: bool = False) -> Dict[str, Any]: + def get_random_msg(cls, draw: DrawType, struct: capnp.lib.capnp._StructModule, real_floats: bool = False) -> dict[str, Any]: fg = cls(draw, real_floats=real_floats) - data: Dict[str, Any] = draw(fg.generate_struct(struct.schema)) + data: dict[str, Any] = draw(fg.generate_struct(struct.schema)) return data @classmethod - def get_random_event_msg(cls, draw: DrawType, events: List[str], real_floats: bool = False) -> List[Dict[str, Any]]: + def get_random_event_msg(cls, draw: DrawType, events: list[str], real_floats: bool = False) -> list[dict[str, Any]]: fg = cls(draw, real_floats=real_floats) return [draw(fg.generate_struct(log.Event.schema, e)) for e in sorted(events)] diff --git a/selfdrive/test/helpers.py b/selfdrive/test/helpers.py index a8b7ca0c4d..210a283699 100644 --- a/selfdrive/test/helpers.py +++ b/selfdrive/test/helpers.py @@ -1,4 +1,6 @@ +import http.server import os +import threading import time from functools import wraps @@ -11,7 +13,6 @@ from openpilot.system.version import training_version, terms_version def set_params_enabled(): - os.environ['REPLAY'] = "1" os.environ['FINGERPRINT'] = "TOYOTA COROLLA TSS2 2019" os.environ['LOGPRINT'] = "debug" @@ -73,7 +74,29 @@ def noop(*args, **kwargs): def read_segment_list(segment_list_path): - with open(segment_list_path, "r") as f: + with open(segment_list_path) as f: seg_list = f.read().splitlines() return [(platform[2:], segment) for platform, segment in zip(seg_list[::2], seg_list[1::2], strict=True)] + + +def with_http_server(func, handler=http.server.BaseHTTPRequestHandler, setup=None): + @wraps(func) + def inner(*args, **kwargs): + host = '127.0.0.1' + server = http.server.HTTPServer((host, 0), handler) + port = server.server_port + t = threading.Thread(target=server.serve_forever) + t.start() + + if setup is not None: + setup(host, port) + + try: + return func(*args, f'http://{host}:{port}', **kwargs) + finally: + server.shutdown() + server.server_close() + t.join() + + return inner diff --git a/selfdrive/test/loop_until_fail.sh b/selfdrive/test/loop_until_fail.sh new file mode 100755 index 0000000000..b73009dba6 --- /dev/null +++ b/selfdrive/test/loop_until_fail.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e + +# Loop something forever until it fails, for verifying new tests + +while true; do + $@ +done diff --git a/selfdrive/test/process_replay/capture.py b/selfdrive/test/process_replay/capture.py index 28206c6b91..90c279ef35 100644 --- a/selfdrive/test/process_replay/capture.py +++ b/selfdrive/test/process_replay/capture.py @@ -1,7 +1,7 @@ import os import sys -from typing import Tuple, no_type_check +from typing import no_type_check class FdRedirect: def __init__(self, file_prefix: str, fd: int): @@ -53,7 +53,7 @@ class ProcessOutputCapture: self.stdout_redirect.link() self.stderr_redirect.link() - def read_outerr(self) -> Tuple[str, str]: + def read_outerr(self) -> tuple[str, str]: out_str = self.stdout_redirect.read().decode() err_str = self.stderr_redirect.read().decode() return out_str, err_str diff --git a/selfdrive/test/process_replay/compare_logs.py b/selfdrive/test/process_replay/compare_logs.py index dbb7c223f5..673f3b484c 100755 --- a/selfdrive/test/process_replay/compare_logs.py +++ b/selfdrive/test/process_replay/compare_logs.py @@ -5,7 +5,6 @@ import capnp import numbers import dictdiffer from collections import Counter -from typing import Dict from openpilot.tools.lib.logreader import LogReader @@ -97,7 +96,7 @@ def format_process_diff(diff): diff_short += f" {diff}\n" diff_long += f"\t{diff}\n" else: - cnt: Dict[str, int] = {} + cnt: dict[str, int] = {} for d in diff: diff_long += f"\t{str(d)}\n" diff --git a/selfdrive/test/process_replay/model_replay_ref_commit b/selfdrive/test/process_replay/model_replay_ref_commit index 9ec78e1401..3b7b04df80 100644 --- a/selfdrive/test/process_replay/model_replay_ref_commit +++ b/selfdrive/test/process_replay/model_replay_ref_commit @@ -1 +1 @@ -4a01784a6b83a49301a68adf52bb7dcfcb7173b5 +fd6421f7551573c549480f9d29bb0dee4678344d diff --git a/selfdrive/test/process_replay/process_replay.py b/selfdrive/test/process_replay/process_replay.py index a6b2771668..4fb3e3c4de 100755 --- a/selfdrive/test/process_replay/process_replay.py +++ b/selfdrive/test/process_replay/process_replay.py @@ -8,7 +8,8 @@ import signal import platform from collections import OrderedDict from dataclasses import dataclass, field -from typing import Dict, List, Optional, Callable, Union, Any, Iterable, Tuple +from typing import Any +from collections.abc import Callable, Iterable from tqdm import tqdm import capnp @@ -36,9 +37,9 @@ FAKEDATA = os.path.join(PROC_REPLAY_DIR, "fakedata/") class DummySocket: def __init__(self): - self.data: List[bytes] = [] + self.data: list[bytes] = [] - def receive(self, non_blocking: bool = False) -> Optional[bytes]: + def receive(self, non_blocking: bool = False) -> bytes | None: if non_blocking: return None @@ -128,21 +129,21 @@ class ReplayContext: @dataclass class ProcessConfig: proc_name: str - pubs: List[str] - subs: List[str] - ignore: List[str] - config_callback: Optional[Callable] = None - init_callback: Optional[Callable] = None - should_recv_callback: Optional[Callable] = None - tolerance: Optional[float] = None + pubs: list[str] + subs: list[str] + ignore: list[str] + config_callback: Callable | None = None + init_callback: Callable | None = None + should_recv_callback: Callable | None = None + tolerance: float | None = None processing_time: float = 0.001 timeout: int = 30 simulation: bool = True - main_pub: Optional[str] = None + main_pub: str | None = None main_pub_drained: bool = True - vision_pubs: List[str] = field(default_factory=list) - ignore_alive_pubs: List[str] = field(default_factory=list) - unlocked_pubs: List[str] = field(default_factory=list) + vision_pubs: list[str] = field(default_factory=list) + ignore_alive_pubs: list[str] = field(default_factory=list) + unlocked_pubs: list[str] = field(default_factory=list) class ProcessContainer: @@ -150,25 +151,25 @@ class ProcessContainer: self.prefix = OpenpilotPrefix(clean_dirs_on_exit=False) self.cfg = copy.deepcopy(cfg) self.process = copy.deepcopy(managed_processes[cfg.proc_name]) - self.msg_queue: List[capnp._DynamicStructReader] = [] + self.msg_queue: list[capnp._DynamicStructReader] = [] self.cnt = 0 - self.pm: Optional[messaging.PubMaster] = None - self.sockets: Optional[List[messaging.SubSocket]] = None - self.rc: Optional[ReplayContext] = None - self.vipc_server: Optional[VisionIpcServer] = None - self.environ_config: Optional[Dict[str, Any]] = None - self.capture: Optional[ProcessOutputCapture] = None + self.pm: messaging.PubMaster | None = None + self.sockets: list[messaging.SubSocket] | None = None + self.rc: ReplayContext | None = None + self.vipc_server: VisionIpcServer | None = None + self.environ_config: dict[str, Any] | None = None + self.capture: ProcessOutputCapture | None = None @property def has_empty_queue(self) -> bool: return len(self.msg_queue) == 0 @property - def pubs(self) -> List[str]: + def pubs(self) -> list[str]: return self.cfg.pubs @property - def subs(self) -> List[str]: + def subs(self) -> list[str]: return self.cfg.subs def _clean_env(self): @@ -180,7 +181,7 @@ class ProcessContainer: if k in os.environ: del os.environ[k] - def _setup_env(self, params_config: Dict[str, Any], environ_config: Dict[str, Any]): + def _setup_env(self, params_config: dict[str, Any], environ_config: dict[str, Any]): for k, v in environ_config.items(): if len(v) != 0: os.environ[k] = v @@ -202,7 +203,7 @@ class ProcessContainer: self.environ_config = environ_config - def _setup_vision_ipc(self, all_msgs: LogIterable, frs: Dict[str, Any]): + def _setup_vision_ipc(self, all_msgs: LogIterable, frs: dict[str, Any]): assert len(self.cfg.vision_pubs) != 0 vipc_server = VisionIpcServer("camerad") @@ -223,9 +224,9 @@ class ProcessContainer: self.process.start() def start( - self, params_config: Dict[str, Any], environ_config: Dict[str, Any], - all_msgs: LogIterable, frs: Optional[Dict[str, BaseFrameReader]], - fingerprint: Optional[str], capture_output: bool + self, params_config: dict[str, Any], environ_config: dict[str, Any], + all_msgs: LogIterable, frs: dict[str, BaseFrameReader] | None, + fingerprint: str | None, capture_output: bool ): with self.prefix as p: self._setup_env(params_config, environ_config) @@ -266,7 +267,7 @@ class ProcessContainer: self.prefix.clean_dirs() self._clean_env() - def run_step(self, msg: capnp._DynamicStructReader, frs: Optional[Dict[str, BaseFrameReader]]) -> List[capnp._DynamicStructReader]: + def run_step(self, msg: capnp._DynamicStructReader, frs: dict[str, BaseFrameReader] | None) -> list[capnp._DynamicStructReader]: assert self.rc and self.pm and self.sockets and self.process.proc output_msgs = [] @@ -580,7 +581,7 @@ def get_process_config(name: str) -> ProcessConfig: raise Exception(f"Cannot find process config with name: {name}") from ex -def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") -> Dict[str, Any]: +def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") -> dict[str, Any]: """ Use this to get custom params dict based on provided logs. Useful when replaying following processes: calibrationd, paramsd, torqued @@ -614,7 +615,7 @@ def get_custom_params_from_lr(lr: LogIterable, initial_state: str = "first") -> return custom_params -def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, *args, **kwargs) -> List[capnp._DynamicStructReader]: +def replay_process_with_name(name: str | Iterable[str], lr: LogIterable, *args, **kwargs) -> list[capnp._DynamicStructReader]: if isinstance(name, str): cfgs = [get_process_config(name)] elif isinstance(name, Iterable): @@ -626,10 +627,10 @@ def replay_process_with_name(name: Union[str, Iterable[str]], lr: LogIterable, * def replay_process( - cfg: Union[ProcessConfig, Iterable[ProcessConfig]], lr: LogIterable, frs: Optional[Dict[str, BaseFrameReader]] = None, - fingerprint: Optional[str] = None, return_all_logs: bool = False, custom_params: Optional[Dict[str, Any]] = None, - captured_output_store: Optional[Dict[str, Dict[str, str]]] = None, disable_progress: bool = False -) -> List[capnp._DynamicStructReader]: + cfg: ProcessConfig | Iterable[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] = None, + fingerprint: str = None, return_all_logs: bool = False, custom_params: dict[str, Any] = None, + captured_output_store: dict[str, dict[str, str]] = None, disable_progress: bool = False +) -> list[capnp._DynamicStructReader]: if isinstance(cfg, Iterable): cfgs = list(cfg) else: @@ -654,9 +655,9 @@ def replay_process( def _replay_multi_process( - cfgs: List[ProcessConfig], lr: LogIterable, frs: Optional[Dict[str, BaseFrameReader]], fingerprint: Optional[str], - custom_params: Optional[Dict[str, Any]], captured_output_store: Optional[Dict[str, Dict[str, str]]], disable_progress: bool -) -> List[capnp._DynamicStructReader]: + cfgs: list[ProcessConfig], lr: LogIterable, frs: dict[str, BaseFrameReader] | None, fingerprint: str | None, + custom_params: dict[str, Any] | None, captured_output_store: dict[str, dict[str, str]] | None, disable_progress: bool +) -> list[capnp._DynamicStructReader]: if fingerprint is not None: params_config = generate_params_config(lr=lr, fingerprint=fingerprint, custom_params=custom_params) env_config = generate_environ_config(fingerprint=fingerprint) @@ -690,10 +691,10 @@ def _replay_multi_process( pub_msgs = [msg for msg in all_msgs if msg.which() in lr_pubs] # external queue for messages taken from logs; internal queue for messages generated by processes, which will be republished - external_pub_queue: List[capnp._DynamicStructReader] = pub_msgs.copy() - internal_pub_queue: List[capnp._DynamicStructReader] = [] + external_pub_queue: list[capnp._DynamicStructReader] = pub_msgs.copy() + internal_pub_queue: list[capnp._DynamicStructReader] = [] # heap for maintaining the order of messages generated by processes, where each element: (logMonoTime, index in internal_pub_queue) - internal_pub_index_heap: List[Tuple[int, int]] = [] + internal_pub_index_heap: list[tuple[int, int]] = [] pbar = tqdm(total=len(external_pub_queue), disable=disable_progress) while len(external_pub_queue) != 0 or (len(internal_pub_index_heap) != 0 and not all(c.has_empty_queue for c in containers)): @@ -723,7 +724,7 @@ def _replay_multi_process( return log_msgs -def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=None) -> Dict[str, Any]: +def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=None) -> dict[str, Any]: params_dict = { "OpenpilotEnabledToggle": True, "DisengageOnAccelerator": True, @@ -755,14 +756,13 @@ def generate_params_config(lr=None, CP=None, fingerprint=None, custom_params=Non return params_dict -def generate_environ_config(CP=None, fingerprint=None, log_dir=None) -> Dict[str, Any]: +def generate_environ_config(CP=None, fingerprint=None, log_dir=None) -> dict[str, Any]: environ_dict = {} if platform.system() != "Darwin": environ_dict["PARAMS_ROOT"] = "/dev/shm/params" if log_dir is not None: environ_dict["LOG_ROOT"] = log_dir - environ_dict["NO_RADAR_SLEEP"] = "1" environ_dict["REPLAY"] = "1" # Regen or python process diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index bde2296e6b..fc38f87310 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -eaab6bd55c5eab33fc9a0d8de8289b912e923887 +d0cdea7eb15f3cac8a921f7ace3eaa6baebb4fd5 diff --git a/selfdrive/test/process_replay/regen.py b/selfdrive/test/process_replay/regen.py index 245b5b2709..8e882207b5 100755 --- a/selfdrive/test/process_replay/regen.py +++ b/selfdrive/test/process_replay/regen.py @@ -5,7 +5,8 @@ import time import capnp import numpy as np -from typing import Union, Iterable, Optional, List, Any, Dict, Tuple +from typing import Any +from collections.abc import Iterable from openpilot.selfdrive.test.process_replay.process_replay import CONFIGS, FAKEDATA, ProcessConfig, replay_process, get_process_config, \ check_openpilot_enabled, get_custom_params_from_lr @@ -40,9 +41,9 @@ class DummyFrameReader(BaseFrameReader): def regen_segment( - lr: LogIterable, frs: Optional[Dict[str, Any]] = None, + lr: LogIterable, frs: dict[str, Any] = None, processes: Iterable[ProcessConfig] = CONFIGS, disable_tqdm: bool = False -) -> List[capnp._DynamicStructReader]: +) -> list[capnp._DynamicStructReader]: all_msgs = sorted(lr, key=lambda m: m.logMonoTime) custom_params = get_custom_params_from_lr(all_msgs) @@ -57,7 +58,7 @@ def regen_segment( def setup_data_readers( route: str, sidx: int, use_route_meta: bool, needs_driver_cam: bool = True, needs_road_cam: bool = True, dummy_driver_cam: bool = False -) -> Tuple[LogReader, Dict[str, Any]]: +) -> tuple[LogReader, dict[str, Any]]: if use_route_meta: r = Route(route) lr = LogReader(r.log_paths()[sidx]) @@ -92,7 +93,7 @@ def setup_data_readers( def regen_and_save( - route: str, sidx: int, processes: Union[str, Iterable[str]] = "all", outdir: str = FAKEDATA, + route: str, sidx: int, processes: str | Iterable[str] = "all", outdir: str = FAKEDATA, upload: bool = False, use_route_meta: bool = False, disable_tqdm: bool = False, dummy_driver_cam: bool = False ) -> str: if not isinstance(processes, str) and not hasattr(processes, "__iter__"): diff --git a/selfdrive/test/process_replay/test_debayer.py b/selfdrive/test/process_replay/test_debayer.py index bea1b1fb00..edf2cbd469 100755 --- a/selfdrive/test/process_replay/test_debayer.py +++ b/selfdrive/test/process_replay/test_debayer.py @@ -30,7 +30,7 @@ def get_frame_fn(ref_commit, test_route, tici=True): def bzip_frames(frames): - data = bytes() + data = b'' for y, u, v in frames: data += y.tobytes() data += u.tobytes() diff --git a/selfdrive/test/process_replay/test_processes.py b/selfdrive/test/process_replay/test_processes.py index 46fa93ba4d..2b917b0f61 100755 --- a/selfdrive/test/process_replay/test_processes.py +++ b/selfdrive/test/process_replay/test_processes.py @@ -5,7 +5,7 @@ import os import sys from collections import defaultdict from tqdm import tqdm -from typing import Any, DefaultDict, Dict +from typing import Any from openpilot.selfdrive.car.car_helpers import interface_names from openpilot.tools.lib.openpilotci import get_url, upload_file @@ -58,7 +58,7 @@ segments = [ ("VOLKSWAGEN", "regen8BDFE7307A0|2023-10-30--23-19-36--0"), ("MAZDA", "regen2E9F1A15FD5|2023-10-30--23-20-36--0"), ("FORD", "regen6D39E54606E|2023-10-30--23-20-54--0"), - ] +] # dashcamOnly makes don't need to be tested until a full port is done excluded_interfaces = ["mock", "tesla"] @@ -107,7 +107,9 @@ def test_process(cfg, lr, segment, ref_log_path, new_log_path, ignore_fields=Non # check to make sure openpilot is engaged in the route if cfg.proc_name == "controlsd": if not check_openpilot_enabled(log_msgs): - return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs + # FIXME: these segments should work, but the replay enabling logic is too brittle + if segment not in ("regen6CA24BC3035|2023-10-30--23-14-28--0", "regen7D2D3F82D5B|2023-10-30--23-15-55--0"): + return f"Route did not enable at all or for long enough: {new_log_path}", log_msgs try: return compare_logs(ref_log_msgs, log_msgs, ignore_fields + cfg.ignore, ignore_msgs, cfg.tolerance), log_msgs @@ -160,7 +162,7 @@ if __name__ == "__main__": sys.exit(1) cur_commit = get_commit() - if cur_commit is None: + if not cur_commit: raise Exception("Couldn't get current commit") print(f"***** testing against commit {ref_commit} *****") @@ -170,11 +172,11 @@ if __name__ == "__main__": untested = (set(interface_names) - set(excluded_interfaces)) - {c.lower() for c in tested_cars} assert len(untested) == 0, f"Cars missing routes: {str(untested)}" - log_paths: DefaultDict[str, Dict[str, Dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) + log_paths: defaultdict[str, dict[str, dict[str, str]]] = defaultdict(lambda: defaultdict(dict)) with concurrent.futures.ProcessPoolExecutor(max_workers=args.jobs) as pool: if not args.upload_only: download_segments = [seg for car, seg in segments if car in tested_cars] - log_data: Dict[str, LogReader] = {} + log_data: dict[str, LogReader] = {} p1 = pool.map(get_log_data, download_segments) for segment, lr in tqdm(p1, desc="Getting Logs", total=len(download_segments)): log_data[segment] = lr diff --git a/selfdrive/test/profiling/profiler.py b/selfdrive/test/profiling/profiler.py index 6d0cc204c5..6571825418 100755 --- a/selfdrive/test/profiling/profiler.py +++ b/selfdrive/test/profiling/profiler.py @@ -80,12 +80,10 @@ def profile(proc, func, car='toyota'): if __name__ == '__main__': from openpilot.selfdrive.controls.controlsd import main as controlsd_thread - from openpilot.selfdrive.controls.radard import radard_thread from openpilot.selfdrive.locationd.paramsd import main as paramsd_thread from openpilot.selfdrive.controls.plannerd import main as plannerd_thread procs = { - 'radard': radard_thread, 'controlsd': controlsd_thread, 'paramsd': paramsd_thread, 'plannerd': plannerd_thread, diff --git a/selfdrive/test/scons_build_test.sh b/selfdrive/test/scons_build_test.sh new file mode 100755 index 0000000000..a3b33f797a --- /dev/null +++ b/selfdrive/test/scons_build_test.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +SCRIPT_DIR=$(dirname "$0") +BASEDIR=$(realpath "$SCRIPT_DIR/../../") +cd $BASEDIR + +# tests that our build system's dependencies are configured properly, +# needs a machine with lots of cores +scons --clean +scons --no-cache --random -j$(nproc) \ No newline at end of file diff --git a/selfdrive/test/test_onroad.py b/selfdrive/test/test_onroad.py index 335da73232..de8a4420b3 100755 --- a/selfdrive/test/test_onroad.py +++ b/selfdrive/test/test_onroad.py @@ -29,7 +29,7 @@ from openpilot.tools.lib.logreader import LogReader # Baseline CPU usage by process PROCS = { - "selfdrive.controls.controlsd": 39.0, + "selfdrive.controls.controlsd": 41.0, "./loggerd": 14.0, "./encoderd": 17.0, "./camerad": 14.5, @@ -39,7 +39,7 @@ PROCS = { "./ui": 18.0, "selfdrive.locationd.paramsd": 9.0, "./sensord": 7.0, - "selfdrive.controls.radard": 4.5, + "selfdrive.controls.radard": 7.0, "selfdrive.modeld.modeld": 13.0, "selfdrive.modeld.dmonitoringmodeld": 8.0, "selfdrive.modeld.navmodeld": 1.0, @@ -57,7 +57,7 @@ PROCS = { "selfdrive.boardd.pandad": 0, "selfdrive.statsd": 0.4, "selfdrive.navd.navd": 0.4, - "system.loggerd.uploader": (0.5, 10.0), + "system.loggerd.uploader": (0.5, 15.0), "system.loggerd.deleter": 0.1, } @@ -112,17 +112,12 @@ class TestOnroad(unittest.TestCase): # setup env params = Params() - if "CI" in os.environ: - params.clear_all() params.remove("CurrentRoute") set_params_enabled() + os.environ['REPLAY'] = '1' os.environ['TESTING_CLOSET'] = '1' if os.path.exists(Paths.log_root()): shutil.rmtree(Paths.log_root()) - os.system("rm /dev/shm/*") - - # Make sure athena isn't running - os.system("pkill -9 -f athena") # start manager and run openpilot for a minute proc = None @@ -132,7 +127,7 @@ class TestOnroad(unittest.TestCase): sm = messaging.SubMaster(['carState']) with Timeout(150, "controls didn't start"): - while sm.rcv_frame['carState'] < 0: + while sm.recv_frame['carState'] < 0: sm.update(1000) # make sure we get at least two full segments @@ -429,4 +424,4 @@ class TestOnroad(unittest.TestCase): if __name__ == "__main__": - unittest.main() + pytest.main() diff --git a/selfdrive/test/test_time_to_onroad.py b/selfdrive/test/test_time_to_onroad.py index aec49cb13a..a3f803e221 100755 --- a/selfdrive/test/test_time_to_onroad.py +++ b/selfdrive/test/test_time_to_onroad.py @@ -18,30 +18,40 @@ def test_time_to_onroad(): proc = subprocess.Popen(["python", manager_path]) start_time = time.monotonic() - sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents']) + sm = messaging.SubMaster(['controlsState', 'deviceState', 'onroadEvents', 'sendcan']) try: - # wait for onroad - with Timeout(20, "timed out waiting to go onroad"): - while True: - sm.update(1000) - if sm['deviceState'].started: - break - time.sleep(1) + # wait for onroad. timeout assumes panda is up to date + with Timeout(10, "timed out waiting to go onroad"): + while not sm['deviceState'].started: + sm.update(100) # wait for engageability - with Timeout(10, "timed out waiting for engageable"): - while True: - sm.update(1000) - if sm['controlsState'].engageable: - break - time.sleep(1) + try: + with Timeout(10, "timed out waiting for engageable"): + sendcan_frame = None + while True: + sm.update(100) + + # sendcan is only sent once we're initialized + if sm.seen['controlsState'] and sendcan_frame is None: + sendcan_frame = sm.frame + + if sendcan_frame is not None and sm.recv_frame['sendcan'] > sendcan_frame: + sm.update(100) + assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}" + break + finally: + print(f"onroad events: {sm['onroadEvents']}") print(f"engageable after {time.monotonic() - start_time:.2f}s") - # once we're enageable, must be for the next few seconds - for _ in range(500): + # once we're enageable, must stay for the next few seconds + st = time.monotonic() + while (time.monotonic() - st) < 10.: sm.update(100) + assert sm.all_alive(), sm.alive assert sm['controlsState'].engageable, f"events: {sm['onroadEvents']}" + assert sm['controlsState'].cumLagMs < 10. finally: proc.terminate() - if proc.wait(60) is None: + if proc.wait(20) is None: proc.kill() diff --git a/selfdrive/test/update_ci_routes.py b/selfdrive/test/update_ci_routes.py index 5ab5042b2b..a9f4494ffd 100755 --- a/selfdrive/test/update_ci_routes.py +++ b/selfdrive/test/update_ci_routes.py @@ -3,7 +3,7 @@ import os import re import subprocess import sys -from typing import Iterable, List, Optional +from collections.abc import Iterable from tqdm import tqdm @@ -12,14 +12,14 @@ from openpilot.selfdrive.test.process_replay.test_processes import source_segmen from openpilot.tools.lib.azure_container import AzureContainer from openpilot.tools.lib.openpilotcontainers import DataCIContainer, DataProdContainer, OpenpilotCIContainer -SOURCES: List[AzureContainer] = [ +SOURCES: list[AzureContainer] = [ DataProdContainer, DataCIContainer ] DEST = OpenpilotCIContainer -def upload_route(path: str, exclude_patterns: Optional[Iterable[str]] = None) -> None: +def upload_route(path: str, exclude_patterns: Iterable[str] = None) -> None: if exclude_patterns is None: exclude_patterns = [r'dcamera\.hevc'] diff --git a/selfdrive/thermald/power_monitoring.py b/selfdrive/thermald/power_monitoring.py index 8802a82af4..073589edb7 100644 --- a/selfdrive/thermald/power_monitoring.py +++ b/selfdrive/thermald/power_monitoring.py @@ -1,6 +1,5 @@ import time import threading -from typing import Optional from openpilot.common.params import Params from openpilot.system.hardware import HARDWARE @@ -14,7 +13,6 @@ CAR_BATTERY_CAPACITY_uWh = 30e6 CAR_CHARGING_RATE_W = 45 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 DELAY_SHUTDOWN_TIME_S = 300 # Wait at least DELAY_SHUTDOWN_TIME_S seconds after offroad_time to shutdown. @@ -39,7 +37,7 @@ class PowerMonitoring: self.car_battery_capacity_uWh = max((CAR_BATTERY_CAPACITY_uWh / 10), int(car_battery_capacity_uWh)) # Calculation tick - def calculate(self, voltage: Optional[int], ignition: bool): + def calculate(self, voltage: int | None, ignition: bool): try: now = time.monotonic() @@ -109,7 +107,7 @@ class PowerMonitoring: return int(self.car_battery_capacity_uWh) # See if we need to shutdown - def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: Optional[float], started_seen: bool): + def should_shutdown(self, ignition: bool, in_car: bool, offroad_timestamp: float | None, started_seen: bool): if offroad_timestamp is None: return False @@ -117,7 +115,6 @@ class PowerMonitoring: should_shutdown = False 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 diff --git a/selfdrive/thermald/thermald.py b/selfdrive/thermald/thermald.py index ab30b1579f..93ebd3ab87 100755 --- a/selfdrive/thermald/thermald.py +++ b/selfdrive/thermald/thermald.py @@ -6,7 +6,6 @@ import threading import time from collections import OrderedDict, namedtuple from pathlib import Path -from typing import Dict, Optional, Tuple import psutil @@ -50,9 +49,9 @@ THERMAL_BANDS = OrderedDict({ # Override to highest thermal band when offroad and above this temp OFFROAD_DANGER_TEMP = 75 -prev_offroad_states: Dict[str, Tuple[bool, Optional[str]]] = {} +prev_offroad_states: dict[str, tuple[bool, str | None]] = {} -tz_by_type: Optional[Dict[str, int]] = None +tz_by_type: dict[str, int] | None = None def populate_tz_by_type(): global tz_by_type tz_by_type = {} @@ -83,12 +82,11 @@ def read_thermal(thermal_config): dat.deviceState.cpuTempC = [read_tz(z) / thermal_config.cpu[1] for z in thermal_config.cpu[0]] dat.deviceState.gpuTempC = [read_tz(z) / thermal_config.gpu[1] for z in thermal_config.gpu[0]] dat.deviceState.memoryTempC = read_tz(thermal_config.mem[0]) / thermal_config.mem[1] - dat.deviceState.ambientTempC = read_tz(thermal_config.ambient[0]) / thermal_config.ambient[1] dat.deviceState.pmicTempC = [read_tz(z) / thermal_config.pmic[1] for z in thermal_config.pmic[0]] return dat -def set_offroad_alert_if_changed(offroad_alert: str, show_alert: bool, extra_text: Optional[str]=None): +def set_offroad_alert_if_changed(offroad_alert: str, show_alert: bool, extra_text: str | None=None): if prev_offroad_states.get(offroad_alert, None) == (show_alert, extra_text): return prev_offroad_states[offroad_alert] = (show_alert, extra_text) @@ -166,20 +164,20 @@ def hw_state_thread(end_event, hw_queue): def thermald_thread(end_event, hw_queue) -> None: pm = messaging.PubMaster(['deviceState']) - sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates"], poll=["pandaStates"]) + sm = messaging.SubMaster(["peripheralState", "gpsLocationExternal", "controlsState", "pandaStates"], poll="pandaStates") count = 0 - onroad_conditions: Dict[str, bool] = { + onroad_conditions: dict[str, bool] = { "ignition": False, } - startup_conditions: Dict[str, bool] = {} - startup_conditions_prev: Dict[str, bool] = {} + startup_conditions: dict[str, bool] = {} + startup_conditions_prev: dict[str, bool] = {} - off_ts: Optional[float] = None - started_ts: Optional[float] = None + off_ts: float | None = None + started_ts: float | None = None started_seen = False - startup_blocked_ts: Optional[float] = None + startup_blocked_ts: float | None = None thermal_status = ThermalStatus.yellow last_hw_state = HardwareState( @@ -233,7 +231,7 @@ def thermald_thread(end_event, hw_queue) -> None: if TICI: fan_controller = TiciFanController() - elif (time.monotonic() - sm.rcv_time['pandaStates']) > DISCONNECT_TIMEOUT: + elif (time.monotonic() - sm.recv_time['pandaStates']) > DISCONNECT_TIMEOUT: if onroad_conditions["ignition"]: onroad_conditions["ignition"] = False cloudlog.error("panda timed out onroad") @@ -245,8 +243,10 @@ def thermald_thread(end_event, hw_queue) -> None: msg.deviceState.freeSpacePercent = get_available_percent(default=100.0) msg.deviceState.memoryUsagePercent = int(round(psutil.virtual_memory().percent)) - msg.deviceState.cpuUsagePercent = [int(round(n)) for n in psutil.cpu_percent(percpu=True)] msg.deviceState.gpuUsagePercent = int(round(HARDWARE.get_gpu_usage_percent())) + online_cpu_usage = [int(round(n)) for n in psutil.cpu_percent(percpu=True)] + offline_cpu_usage = [0., ] * (len(msg.deviceState.cpuTempC) - len(online_cpu_usage)) + msg.deviceState.cpuUsagePercent = online_cpu_usage + offline_cpu_usage msg.deviceState.networkType = last_hw_state.network_type msg.deviceState.networkMetered = last_hw_state.network_metered @@ -410,7 +410,6 @@ def thermald_thread(end_event, hw_queue) -> None: for i, temp in enumerate(msg.deviceState.gpuTempC): statlog.gauge(f"gpu{i}_temperature", temp) statlog.gauge("memory_temperature", msg.deviceState.memoryTempC) - statlog.gauge("ambient_temperature", msg.deviceState.ambientTempC) for i, temp in enumerate(msg.deviceState.pmicTempC): statlog.gauge(f"pmic{i}_temperature", temp) for i, temp in enumerate(last_hw_state.nvme_temps): diff --git a/selfdrive/tombstoned.py b/selfdrive/tombstoned.py index 5a81e93b28..ba3582d130 100755 --- a/selfdrive/tombstoned.py +++ b/selfdrive/tombstoned.py @@ -124,7 +124,7 @@ def report_tombstone_apport(fn): clean_path = path.replace('/', '_') date = datetime.datetime.now().strftime("%Y-%m-%d--%H-%M-%S") - new_fn = f"{date}_{get_commit(default='nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] + new_fn = f"{date}_{(get_commit() or 'nocommit')[:8]}_{safe_fn(clean_path)}"[:MAX_TOMBSTONE_FN_LEN] crashlog_dir = os.path.join(Paths.log_root(), "crash") os.makedirs(crashlog_dir, exist_ok=True) diff --git a/selfdrive/ui/.gitignore b/selfdrive/ui/.gitignore index 99f9097905..f9724816da 100644 --- a/selfdrive/ui/.gitignore +++ b/selfdrive/ui/.gitignore @@ -3,11 +3,13 @@ moc_* translations/main_test_en.* +_text +_spinner + ui +mui watch3 installer/installers/* -qt/text -qt/spinner qt/setup/setup qt/setup/reset qt/setup/wifi diff --git a/selfdrive/ui/SConscript b/selfdrive/ui/SConscript index a3cba124fe..ea5734fe6e 100644 --- a/selfdrive/ui/SConscript +++ b/selfdrive/ui/SConscript @@ -11,10 +11,6 @@ if arch == 'larch64': maps = arch in ['larch64', 'aarch64', 'x86_64'] -if maps and arch != 'larch64': - rpath = [Dir(f"#third_party/mapbox-gl-native-qt/{arch}").srcnode().abspath] - qt_env["RPATH"] += rpath - if arch == "Darwin": del base_libs[base_libs.index('OpenCL')] qt_env['FRAMEWORKS'] += ['OpenCL'] @@ -31,7 +27,7 @@ widgets_src = ["ui.cc", "qt/widgets/input.cc", "qt/widgets/wifi.cc", qt_env['CPPDEFINES'] = [] if maps: - base_libs += ['qmapboxgl'] + base_libs += ['QMapLibre'] widgets_src += ["qt/maps/map_helpers.cc", "qt/maps/map_settings.cc", "qt/maps/map.cc", "qt/maps/map_panel.cc", "qt/maps/map_eta.cc", "qt/maps/map_instructions.cc"] qt_env['CPPDEFINES'] += ["ENABLE_MAPS"] @@ -76,8 +72,8 @@ asset_obj = qt_env.Object("assets", assets) qt_env.SharedLibrary("qt/python_helpers", ["qt/qt_window.cc"], LIBS=qt_libs) # spinner and text window -qt_env.Program("qt/text", ["qt/text.cc"], LIBS=qt_libs) -qt_env.Program("qt/spinner", ["qt/spinner.cc"], LIBS=qt_libs) +qt_env.Program("_text", ["qt/text.cc"], LIBS=qt_libs) +qt_env.Program("_spinner", ["qt/spinner.cc"], LIBS=qt_libs) # build main UI qt_env.Program("ui", qt_src + [asset_obj], LIBS=qt_libs) @@ -96,6 +92,9 @@ if GetOption('extras') and arch != "Darwin": # build updater UI qt_env.Program("qt/setup/updater", ["qt/setup/updater.cc", asset_obj], LIBS=qt_libs) + # build mui + qt_env.Program("mui", ["mui.cc"], LIBS=qt_libs) + # build installers senv = qt_env.Clone() senv['LINKFLAGS'].append('-Wl,-strip-debug') diff --git a/selfdrive/ui/mui.cc b/selfdrive/ui/mui.cc new file mode 100644 index 0000000000..98029ee311 --- /dev/null +++ b/selfdrive/ui/mui.cc @@ -0,0 +1,50 @@ +#include +#include +#include + +#include "cereal/messaging/messaging.h" +#include "selfdrive/ui/ui.h" +#include "selfdrive/ui/qt/qt_window.h" + +int main(int argc, char *argv[]) { + QApplication a(argc, argv); + QWidget w; + setMainWindow(&w); + + w.setStyleSheet("background-color: black;"); + + // our beautiful UI + QVBoxLayout *layout = new QVBoxLayout(&w); + QLabel *label = new QLabel("〇"); + layout->addWidget(label, 0, Qt::AlignCenter); + + QTimer timer; + QObject::connect(&timer, &QTimer::timeout, [=]() { + static SubMaster sm({"deviceState", "controlsState"}); + + bool onroad_prev = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + sm.update(0); + + bool onroad = sm.allAliveAndValid({"deviceState"}) && + sm["deviceState"].getDeviceState().getStarted(); + + if (onroad) { + label->setText("〇"); + auto cs = sm["controlsState"].getControlsState(); + UIStatus status = cs.getEnabled() ? STATUS_ENGAGED : STATUS_DISENGAGED; + label->setStyleSheet(QString("color: %1; font-size: 250px;").arg(bg_colors[status].name())); + } else { + label->setText("offroad"); + label->setStyleSheet("color: grey; font-size: 40px;"); + } + + if ((onroad != onroad_prev) || sm.frame < 2) { + Hardware::set_brightness(50); + Hardware::set_display_power(onroad); + } + }); + timer.start(50); + + return a.exec(); +} diff --git a/selfdrive/ui/qt/maps/map.cc b/selfdrive/ui/qt/maps/map.cc index db56fcdcfc..a5644bae4f 100644 --- a/selfdrive/ui/qt/maps/map.cc +++ b/selfdrive/ui/qt/maps/map.cc @@ -18,7 +18,7 @@ const float MAX_PITCH = 50; const float MIN_PITCH = 0; const float MAP_SCALE = 2; -MapWindow::MapWindow(const QMapboxGLSettings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { +MapWindow::MapWindow(const QMapLibre::Settings &settings) : m_settings(settings), velocity_filter(0, 10, 0.05, false) { QObject::connect(uiState(), &UIState::uiUpdate, this, &MapWindow::updateState); map_overlay = new QWidget (this); @@ -57,10 +57,10 @@ void MapWindow::initLayers() { if (!m_map->layerExists("modelPathLayer")) { qDebug() << "Initializing modelPathLayer"; QVariantMap modelPath; - modelPath["id"] = "modelPathLayer"; + //modelPath["id"] = "modelPathLayer"; modelPath["type"] = "line"; modelPath["source"] = "modelPathSource"; - m_map->addLayer(modelPath); + m_map->addLayer("modelPathLayer", modelPath); m_map->setPaintProperty("modelPathLayer", "line-color", QColor("red")); m_map->setPaintProperty("modelPathLayer", "line-width", 5.0); m_map->setLayoutProperty("modelPathLayer", "line-cap", "round"); @@ -68,10 +68,9 @@ void MapWindow::initLayers() { if (!m_map->layerExists("navLayer")) { qDebug() << "Initializing navLayer"; QVariantMap nav; - nav["id"] = "navLayer"; nav["type"] = "line"; nav["source"] = "navSource"; - m_map->addLayer(nav, "road-intersection"); + m_map->addLayer("navLayer", nav, "road-intersection"); QVariantMap transition; transition["duration"] = 400; // ms @@ -84,10 +83,9 @@ void MapWindow::initLayers() { qDebug() << "Initializing pinLayer"; m_map->addImage("default_marker", QImage("../assets/navigation/default_marker.svg")); QVariantMap pin; - pin["id"] = "pinLayer"; pin["type"] = "symbol"; pin["source"] = "pinSource"; - m_map->addLayer(pin); + m_map->addLayer("pinLayer", pin); m_map->setLayoutProperty("pinLayer", "icon-pitch-alignment", "viewport"); m_map->setLayoutProperty("pinLayer", "icon-image", "default_marker"); m_map->setLayoutProperty("pinLayer", "icon-ignore-placement", true); @@ -100,10 +98,9 @@ void MapWindow::initLayers() { m_map->addImage("label-arrow", QImage("../assets/images/triangle.svg")); QVariantMap carPos; - carPos["id"] = "carPosLayer"; carPos["type"] = "symbol"; carPos["source"] = "carPosSource"; - m_map->addLayer(carPos); + m_map->addLayer("carPosLayer", carPos); m_map->setLayoutProperty("carPosLayer", "icon-pitch-alignment", "map"); m_map->setLayoutProperty("carPosLayer", "icon-image", "label-arrow"); m_map->setLayoutProperty("carPosLayer", "icon-size", 0.5); @@ -149,7 +146,7 @@ void MapWindow::updateState(const UIState &s) { locationd_valid = (locationd_pos.getValid() && locationd_orientation.getValid() && locationd_velocity.getValid() && pos_accurate_enough); if (locationd_valid) { - last_position = QMapbox::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); + last_position = QMapLibre::Coordinate(locationd_pos.getValue()[0], locationd_pos.getValue()[1]); last_bearing = RAD2DEG(locationd_orientation.getValue()[2]); velocity_filter.update(std::max(10.0, locationd_velocity.getValue()[0])); } @@ -186,10 +183,10 @@ void MapWindow::updateState(const UIState &s) { if (locationd_valid) { // Update current location marker auto point = coordinate_to_collection(*last_position); - QMapbox::Feature feature1(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature1(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap carPosSource; carPosSource["type"] = "geojson"; - carPosSource["data"] = QVariant::fromValue(feature1); + carPosSource["data"] = QVariant::fromValue(feature1); m_map->updateSource("carPosSource", carPosSource); // Map bearing isn't updated when interacting, keep location marker up to date @@ -230,10 +227,10 @@ void MapWindow::updateState(const UIState &s) { qWarning() << "Updating navLayer with new route"; auto route = sm["navRoute"].getNavRoute(); auto route_points = capnp_coordinate_list_to_collection(route.getCoordinates()); - QMapbox::Feature feature(QMapbox::Feature::LineStringType, route_points, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::LineStringType, route_points, {}, {}); QVariantMap navSource; navSource["type"] = "geojson"; - navSource["data"] = QVariant::fromValue(feature); + navSource["data"] = QVariant::fromValue(feature); m_map->updateSource("navSource", navSource); m_map->setLayoutProperty("navLayer", "visibility", "visible"); @@ -256,24 +253,24 @@ void MapWindow::resizeGL(int w, int h) { } void MapWindow::initializeGL() { - m_map.reset(new QMapboxGL(this, m_settings, size(), 1)); + m_map.reset(new QMapLibre::Map(this, m_settings, size(), 1)); if (last_position) { m_map->setCoordinateZoom(*last_position, MAX_ZOOM); } else { - m_map->setCoordinateZoom(QMapbox::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); + m_map->setCoordinateZoom(QMapLibre::Coordinate(64.31990695292795, -149.79038934046247), MIN_ZOOM); } m_map->setMargins({0, 350, 0, 50}); m_map->setPitch(MIN_PITCH); m_map->setStyleUrl("mapbox://styles/commaai/clkqztk0f00ou01qyhsa5bzpj"); - QObject::connect(m_map.data(), &QMapboxGL::mapChanged, [=](QMapboxGL::MapChange change) { + QObject::connect(m_map.data(), &QMapLibre::Map::mapChanged, [=](QMapLibre::Map::MapChange change) { // set global animation duration to 0 ms so visibility changes are instant - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingStyle) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingStyle) { m_map->setTransitionOptions(0, 0); } - if (change == QMapboxGL::MapChange::MapChangeDidFinishLoadingMap) { + if (change == QMapLibre::Map::MapChange::MapChangeDidFinishLoadingMap) { loaded_once = true; } }); @@ -381,10 +378,10 @@ void MapWindow::updateDestinationMarker() { auto nav_dest = coordinate_from_param("NavDestination"); if (nav_dest.has_value()) { auto point = coordinate_to_collection(*nav_dest); - QMapbox::Feature feature(QMapbox::Feature::PointType, point, {}, {}); + QMapLibre::Feature feature(QMapLibre::Feature::PointType, point, {}, {}); QVariantMap pinSource; pinSource["type"] = "geojson"; - pinSource["data"] = QVariant::fromValue(feature); + pinSource["data"] = QVariant::fromValue(feature); m_map->updateSource("pinSource", pinSource); m_map->setPaintProperty("pinLayer", "visibility", "visible"); } else { diff --git a/selfdrive/ui/qt/maps/map.h b/selfdrive/ui/qt/maps/map.h index 5fe79f8b15..81ba50037a 100644 --- a/selfdrive/ui/qt/maps/map.h +++ b/selfdrive/ui/qt/maps/map.h @@ -6,7 +6,8 @@ #include #include #include -#include +#include +#include #include #include #include @@ -27,7 +28,7 @@ class MapWindow : public QOpenGLWidget { Q_OBJECT public: - MapWindow(const QMapboxGLSettings &); + MapWindow(const QMapLibre::Settings &); ~MapWindow(); private: @@ -35,8 +36,8 @@ private: void paintGL() final; void resizeGL(int w, int h) override; - QMapboxGLSettings m_settings; - QScopedPointer m_map; + QMapLibre::Settings m_settings; + QScopedPointer m_map; void initLayers(); @@ -56,8 +57,8 @@ private: int interaction_counter = 0; // Position - std::optional last_valid_nav_dest; - std::optional last_position; + std::optional last_valid_nav_dest; + std::optional last_position; std::optional last_bearing; FirstOrderFilter velocity_filter; bool locationd_valid = false; diff --git a/selfdrive/ui/qt/maps/map_helpers.cc b/selfdrive/ui/qt/maps/map_helpers.cc index 022355e3ce..3907ff7b05 100644 --- a/selfdrive/ui/qt/maps/map_helpers.cc +++ b/selfdrive/ui/qt/maps/map_helpers.cc @@ -16,24 +16,25 @@ QString get_mapbox_token() { return MAPBOX_TOKEN.isEmpty() ? CommaApi::create_jwt({}, 4 * 7 * 24 * 3600) : MAPBOX_TOKEN; } -QMapboxGLSettings get_mapbox_settings() { - QMapboxGLSettings settings; +QMapLibre::Settings get_mapbox_settings() { + QMapLibre::Settings settings; + settings.setProviderTemplate(QMapLibre::Settings::ProviderTemplate::MapboxProvider); if (!Hardware::PC()) { settings.setCacheDatabasePath(MAPS_CACHE_PATH); settings.setCacheDatabaseMaximumSize(100 * 1024 * 1024); } settings.setApiBaseUrl(MAPS_HOST); - settings.setAccessToken(get_mapbox_token()); + settings.setApiKey(get_mapbox_token()); return settings; } -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in) { +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in) { return QGeoCoordinate(in.first, in.second); } -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, const cereal::XYZTData::Reader &line){ @@ -42,7 +43,7 @@ QMapbox::CoordinatesCollections model_to_collection( Eigen::Vector3d orient(calibratedOrientationECEF.getValue()[0], calibratedOrientationECEF.getValue()[1], calibratedOrientationECEF.getValue()[2]); Eigen::Matrix3d ecef_from_local = euler2rot(orient); - QMapbox::Coordinates coordinates; + QMapLibre::Coordinates coordinates; auto x = line.getX(); auto y = line.getY(); auto z = line.getZ(); @@ -52,28 +53,28 @@ QMapbox::CoordinatesCollections model_to_collection( coordinates.push_back({point_geodetic.lat, point_geodetic.lon}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_to_collection(const QMapbox::Coordinate &c) { - QMapbox::Coordinates coordinates{c}; - return {QMapbox::CoordinatesCollection{coordinates}}; +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c) { + QMapLibre::Coordinates coordinates{c}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader& coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto const &c : coordinate_list) { coordinates.push_back({c.getLatitude(), c.getLongitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } -QMapbox::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { - QMapbox::Coordinates coordinates; +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list) { + QMapLibre::Coordinates coordinates; for (auto &c : coordinate_list) { coordinates.push_back({c.latitude(), c.longitude()}); } - return {QMapbox::CoordinatesCollection{coordinates}}; + return {QMapLibre::CoordinatesCollection{coordinates}}; } QList polyline_to_coordinate_list(const QString &polylineString) { @@ -118,7 +119,7 @@ QList polyline_to_coordinate_list(const QString &polylineString) return path; } -std::optional coordinate_from_param(const std::string ¶m) { +std::optional coordinate_from_param(const std::string ¶m) { QString json_str = QString::fromStdString(Params().get(param)); if (json_str.isEmpty()) return {}; @@ -127,7 +128,7 @@ std::optional coordinate_from_param(const std::string ¶ QJsonObject json = doc.object(); if (json["latitude"].isDouble() && json["longitude"].isDouble()) { - QMapbox::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); + QMapLibre::Coordinate coord(json["latitude"].toDouble(), json["longitude"].toDouble()); return coord; } else { return {}; diff --git a/selfdrive/ui/qt/maps/map_helpers.h b/selfdrive/ui/qt/maps/map_helpers.h index 4d6e9b5382..0f4be674f0 100644 --- a/selfdrive/ui/qt/maps/map_helpers.h +++ b/selfdrive/ui/qt/maps/map_helpers.h @@ -3,8 +3,9 @@ #include #include #include +#include +#include #include -#include #include #include "common/util.h" @@ -17,15 +18,15 @@ const QString MAPS_HOST = util::getenv("MAPS_HOST", MAPBOX_TOKEN.isEmpty() ? "ht const QString MAPS_CACHE_PATH = "/data/mbgl-cache-navd.db"; QString get_mapbox_token(); -QMapboxGLSettings get_mapbox_settings(); -QGeoCoordinate to_QGeoCoordinate(const QMapbox::Coordinate &in); -QMapbox::CoordinatesCollections model_to_collection( +QMapLibre::Settings get_mapbox_settings(); +QGeoCoordinate to_QGeoCoordinate(const QMapLibre::Coordinate &in); +QMapLibre::CoordinatesCollections model_to_collection( const cereal::LiveLocationKalman::Measurement::Reader &calibratedOrientationECEF, const cereal::LiveLocationKalman::Measurement::Reader &positionECEF, 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); +QMapLibre::CoordinatesCollections coordinate_to_collection(const QMapLibre::Coordinate &c); +QMapLibre::CoordinatesCollections capnp_coordinate_list_to_collection(const capnp::List::Reader &coordinate_list); +QMapLibre::CoordinatesCollections coordinate_list_to_collection(const QList &coordinate_list); QList polyline_to_coordinate_list(const QString &polylineString); -std::optional coordinate_from_param(const std::string ¶m); +std::optional coordinate_from_param(const std::string ¶m); std::pair map_format_distance(float d, bool is_metric); diff --git a/selfdrive/ui/qt/maps/map_panel.cc b/selfdrive/ui/qt/maps/map_panel.cc index 0a2286ff6f..c4cc20e21d 100644 --- a/selfdrive/ui/qt/maps/map_panel.cc +++ b/selfdrive/ui/qt/maps/map_panel.cc @@ -8,7 +8,7 @@ #include "selfdrive/ui/qt/util.h" #include "selfdrive/ui/ui.h" -MapPanel::MapPanel(const QMapboxGLSettings &mapboxSettings, QWidget *parent) : QFrame(parent) { +MapPanel::MapPanel(const QMapLibre::Settings &mapboxSettings, QWidget *parent) : QFrame(parent) { content_stack = new QStackedLayout(this); content_stack->setContentsMargins(0, 0, 0, 0); diff --git a/selfdrive/ui/qt/maps/map_panel.h b/selfdrive/ui/qt/maps/map_panel.h index 43a2cc7c89..190bb63446 100644 --- a/selfdrive/ui/qt/maps/map_panel.h +++ b/selfdrive/ui/qt/maps/map_panel.h @@ -1,14 +1,14 @@ #pragma once #include -#include +#include #include class MapPanel : public QFrame { Q_OBJECT public: - explicit MapPanel(const QMapboxGLSettings &settings, QWidget *parent = nullptr); + explicit MapPanel(const QMapLibre::Settings &settings, QWidget *parent = nullptr); signals: void mapPanelRequested(); diff --git a/selfdrive/ui/qt/offroad/onboarding.cc b/selfdrive/ui/qt/offroad/onboarding.cc index d4fcee55ce..b1219055fd 100644 --- a/selfdrive/ui/qt/offroad/onboarding.cc +++ b/selfdrive/ui/qt/offroad/onboarding.cc @@ -199,7 +199,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { TermsPage* terms = new TermsPage(this); addWidget(terms); connect(terms, &TermsPage::acceptedTerms, [=]() { - Params().put("HasAcceptedTerms", current_terms_version); + params.put("HasAcceptedTerms", current_terms_version); accepted_terms = true; updateActiveScreen(); }); @@ -209,7 +209,7 @@ OnboardingWindow::OnboardingWindow(QWidget *parent) : QStackedWidget(parent) { addWidget(tr); connect(tr, &TrainingGuide::completedTraining, [=]() { training_done = true; - Params().put("CompletedTrainingVersion", current_training_version); + params.put("CompletedTrainingVersion", current_training_version); updateActiveScreen(); }); diff --git a/selfdrive/ui/qt/onroad.cc b/selfdrive/ui/qt/onroad.cc index 4df0ed81cb..01750eaf2f 100644 --- a/selfdrive/ui/qt/onroad.cc +++ b/selfdrive/ui/qt/onroad.cc @@ -289,7 +289,7 @@ void AnnotatedCameraWidget::updateState(const UIState &s) { const auto nav_instruction = sm["navInstruction"].getNavInstruction(); // Handle older routes where vCruiseCluster is not set - float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); + float v_cruise = cs.getVCruiseCluster() == 0.0 ? cs.getVCruise() : cs.getVCruiseCluster(); setSpeed = cs_alive ? v_cruise : SET_SPEED_NA; is_cruise_set = setSpeed > 0 && (int)setSpeed != SET_SPEED_NA; if (is_cruise_set && !s.scene.is_metric) { diff --git a/selfdrive/ui/qt/util.cc b/selfdrive/ui/qt/util.cc index 16d31ce304..bc3c494fa7 100644 --- a/selfdrive/ui/qt/util.cc +++ b/selfdrive/ui/qt/util.cc @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -114,6 +115,11 @@ void initApp(int argc, char *argv[], bool disable_hidpi) { qputenv("QT_DBL_CLICK_DIST", QByteArray::number(150)); + // ensure the current dir matches the exectuable's directory + QApplication tmp(argc, argv); + QString appDir = QCoreApplication::applicationDirPath(); + QDir::setCurrent(appDir); + setQtSurfaceFormat(); } diff --git a/selfdrive/ui/soundd.py b/selfdrive/ui/soundd.py index 01148ec199..0550a7db9e 100644 --- a/selfdrive/ui/soundd.py +++ b/selfdrive/ui/soundd.py @@ -3,7 +3,6 @@ import numpy as np import time import wave -from typing import Dict, Optional, Tuple from cereal import car, messaging from openpilot.common.basedir import BASEDIR @@ -27,7 +26,7 @@ DB_SCALE = 30 # AMBIENT_DB + DB_SCALE is where MAX_VOLUME is applied AudibleAlert = car.CarControl.HUDControl.AudibleAlert -sound_list: Dict[int, Tuple[str, Optional[int], float]] = { +sound_list: dict[int, tuple[str, int | None, float]] = { # AudibleAlert, file name, play count (none for infinite) AudibleAlert.engage: ("engage.wav", 1, MAX_VOLUME), AudibleAlert.disengage: ("disengage.wav", 1, MAX_VOLUME), @@ -42,7 +41,7 @@ sound_list: Dict[int, Tuple[str, Optional[int], float]] = { } def check_controls_timeout_alert(sm): - controls_missing = time.monotonic() - sm.rcv_time['controlsState'] + controls_missing = time.monotonic() - sm.recv_time['controlsState'] if controls_missing > CONTROLS_TIMEOUT: if sm['controlsState'].enabled and (controls_missing - CONTROLS_TIMEOUT) < 10: @@ -64,7 +63,7 @@ class Soundd: self.spl_filter_weighted = FirstOrderFilter(0, 2.5, FILTER_DT, initialized=False) def load_sounds(self): - self.loaded_sounds: Dict[int, np.ndarray] = {} + self.loaded_sounds: dict[int, np.ndarray] = {} # Load all sounds for sound in sound_list: diff --git a/selfdrive/ui/spinner b/selfdrive/ui/spinner index 35feab3f7e..965c8f581a 100755 --- a/selfdrive/ui/spinner +++ b/selfdrive/ui/spinner @@ -1,7 +1,7 @@ #!/bin/sh -if [ -f /TICI ] && [ ! -f qt/spinner ]; then - cp qt/spinner_larch64 qt/spinner +if [ -f /TICI ] && [ ! -f _spinner ]; then + cp qt/spinner_larch64 _spinner fi -exec ./qt/spinner "$1" +exec ./_spinner "$1" diff --git a/selfdrive/ui/tests/test_translations.py b/selfdrive/ui/tests/test_translations.py index 9ba9054ea1..8e50695e70 100755 --- a/selfdrive/ui/tests/test_translations.py +++ b/selfdrive/ui/tests/test_translations.py @@ -12,7 +12,7 @@ from parameterized import parameterized_class from openpilot.selfdrive.ui.update_translations import TRANSLATIONS_DIR, LANGUAGES_FILE, update_translations -with open(LANGUAGES_FILE, "r") as f: +with open(LANGUAGES_FILE) as f: translation_files = json.load(f) UNFINISHED_TRANSLATION_TAG = " dict[str, pathlib.Path]: +def get_language_files(languages: list[str] = None) -> dict[str, pathlib.Path]: files = {} with open(TRANSLATIONS_LANGUAGES) as fp: diff --git a/selfdrive/ui/translations/create_badges.py b/selfdrive/ui/translations/create_badges.py index f6c8aae448..f56f894111 100755 --- a/selfdrive/ui/translations/create_badges.py +++ b/selfdrive/ui/translations/create_badges.py @@ -13,13 +13,13 @@ BADGE_HEIGHT = 20 + 8 SHIELDS_URL = "https://img.shields.io/badge" if __name__ == "__main__": - with open(LANGUAGES_FILE, "r") as f: + with open(LANGUAGES_FILE) as f: translation_files = json.load(f) 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: + with open(os.path.join(TRANSLATIONS_DIR, f"{file}.ts")) as tr_f: tr_file = tr_f.read() total_translations = 0 diff --git a/selfdrive/ui/update_translations.py b/selfdrive/ui/update_translations.py index e988a13b7f..0fe0f05ac4 100755 --- a/selfdrive/ui/update_translations.py +++ b/selfdrive/ui/update_translations.py @@ -28,7 +28,7 @@ def update_translations(vanish: bool = False, translation_files: None | list[str generate_translations_include() if translation_files is None: - with open(LANGUAGES_FILE, "r") as f: + with open(LANGUAGES_FILE) as f: translation_files = json.load(f).values() for file in translation_files: diff --git a/selfdrive/updated.py b/selfdrive/updated.py index eb2759223b..b6b395f254 100755 --- a/selfdrive/updated.py +++ b/selfdrive/updated.py @@ -11,7 +11,6 @@ import time import threading from collections import defaultdict from pathlib import Path -from typing import List, Union, Optional from markdown_it import MarkdownIt from openpilot.common.basedir import BASEDIR @@ -64,7 +63,7 @@ def write_time_to_param(params, param) -> None: t = datetime.datetime.utcnow() params.put(param, t.isoformat().encode('utf8')) -def read_time_from_param(params, param) -> Optional[datetime.datetime]: +def read_time_from_param(params, param) -> datetime.datetime | None: t = params.get(param, encoding='utf8') try: return datetime.datetime.fromisoformat(t) @@ -72,7 +71,7 @@ def read_time_from_param(params, param) -> Optional[datetime.datetime]: pass return None -def run(cmd: List[str], cwd: Optional[str] = None) -> str: +def run(cmd: list[str], cwd: str = None) -> str: return subprocess.check_output(cmd, cwd=cwd, stderr=subprocess.STDOUT, encoding='utf8') @@ -234,7 +233,7 @@ def handle_agnos_update() -> None: class Updater: def __init__(self): self.params = Params() - self.branches = defaultdict(lambda: '') + self.branches = defaultdict(str) self._has_internet: bool = False @property @@ -243,7 +242,7 @@ class Updater: @property def target_branch(self) -> str: - b: Union[str, None] = self.params.get("UpdaterTargetBranch", encoding='utf-8') + b: str | None = self.params.get("UpdaterTargetBranch", encoding='utf-8') if b is None: b = self.get_branch(BASEDIR) return b @@ -272,7 +271,7 @@ class Updater: def get_commit_hash(self, path: str = OVERLAY_MERGED) -> str: return run(["git", "rev-parse", "HEAD"], path).rstrip() - def set_params(self, update_success: bool, failed_count: int, exception: Optional[str]) -> None: + def set_params(self, update_success: bool, failed_count: int, exception: str | None) -> None: self.params.put("UpdateFailedCount", str(failed_count)) self.params.put("UpdaterTargetBranch", self.target_branch) @@ -413,96 +412,96 @@ def main() -> None: cloudlog.warning("updates are disabled by the DisableUpdates param") exit(0) - ov_lock_fd = open(LOCK_FILE, 'w') - try: - fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except OSError as e: - raise RuntimeError("couldn't get overlay lock; is another instance running?") from e + with open(LOCK_FILE, 'w') as ov_lock_fd: + try: + fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) + except OSError as e: + raise RuntimeError("couldn't get overlay lock; is another instance running?") from e - # Set low io priority - proc = psutil.Process() - if psutil.LINUX: - proc.ionice(psutil.IOPRIO_CLASS_BE, value=7) + # Set low io priority + proc = psutil.Process() + if psutil.LINUX: + proc.ionice(psutil.IOPRIO_CLASS_BE, value=7) - # Check if we just performed an update - if Path(os.path.join(STAGING_ROOT, "old_openpilot")).is_dir(): - cloudlog.event("update installed") + # Check if we just performed an update + if Path(os.path.join(STAGING_ROOT, "old_openpilot")).is_dir(): + cloudlog.event("update installed") - if not params.get("InstallDate"): - t = datetime.datetime.utcnow().isoformat() - params.put("InstallDate", t.encode('utf8')) + if not params.get("InstallDate"): + t = datetime.datetime.utcnow().isoformat() + params.put("InstallDate", t.encode('utf8')) - updater = Updater() - update_failed_count = 0 # TODO: Load from param? - wait_helper = WaitTimeHelper() + updater = Updater() + update_failed_count = 0 # TODO: Load from param? + wait_helper = WaitTimeHelper() - # invalidate old finalized update - set_consistent_flag(False) + # invalidate old finalized update + set_consistent_flag(False) - # set initial state - params.put("UpdaterState", "idle") + # set initial state + params.put("UpdaterState", "idle") - # Run the update loop - first_run = True - while True: - wait_helper.ready_event.clear() + # Run the update loop + first_run = True + while True: + wait_helper.ready_event.clear() - # Attempt an update - exception = None - try: - # TODO: reuse overlay from previous updated instance if it looks clean - init_overlay() - - # ensure we have some params written soon after startup - updater.set_params(False, update_failed_count, exception) - - if not system_time_valid() or first_run: - first_run = False - wait_helper.sleep(60) - continue - - update_failed_count += 1 - - # check for update - params.put("UpdaterState", "checking...") - updater.check_for_update() - - # download update - last_fetch = read_time_from_param(params, "UpdaterLastFetchTime") - timed_out = last_fetch is None or (datetime.datetime.utcnow() - last_fetch > datetime.timedelta(days=3)) - user_requested_fetch = wait_helper.user_request == UserRequest.FETCH - if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch: - cloudlog.info("skipping fetch, connection metered") - elif wait_helper.user_request == UserRequest.CHECK: - cloudlog.info("skipping fetch, only checking") - else: - updater.fetch_update() - write_time_to_param(params, "UpdaterLastFetchTime") - update_failed_count = 0 - except subprocess.CalledProcessError as e: - cloudlog.event( - "update process failed", - cmd=e.cmd, - output=e.output, - returncode=e.returncode - ) - exception = f"command failed: {e.cmd}\n{e.output}" - OVERLAY_INIT.unlink(missing_ok=True) - except Exception as e: - cloudlog.exception("uncaught updated exception, shouldn't happen") - exception = str(e) - OVERLAY_INIT.unlink(missing_ok=True) + # Attempt an update + exception = None + try: + # TODO: reuse overlay from previous updated instance if it looks clean + init_overlay() + + # ensure we have some params written soon after startup + updater.set_params(False, update_failed_count, exception) + + if not system_time_valid() or first_run: + first_run = False + wait_helper.sleep(60) + continue + + update_failed_count += 1 + + # check for update + params.put("UpdaterState", "checking...") + updater.check_for_update() + + # download update + last_fetch = read_time_from_param(params, "UpdaterLastFetchTime") + timed_out = last_fetch is None or (datetime.datetime.utcnow() - last_fetch > datetime.timedelta(days=3)) + user_requested_fetch = wait_helper.user_request == UserRequest.FETCH + if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch: + cloudlog.info("skipping fetch, connection metered") + elif wait_helper.user_request == UserRequest.CHECK: + cloudlog.info("skipping fetch, only checking") + else: + updater.fetch_update() + write_time_to_param(params, "UpdaterLastFetchTime") + update_failed_count = 0 + except subprocess.CalledProcessError as e: + cloudlog.event( + "update process failed", + cmd=e.cmd, + output=e.output, + returncode=e.returncode + ) + exception = f"command failed: {e.cmd}\n{e.output}" + OVERLAY_INIT.unlink(missing_ok=True) + except Exception as e: + cloudlog.exception("uncaught updated exception, shouldn't happen") + exception = str(e) + OVERLAY_INIT.unlink(missing_ok=True) - try: - params.put("UpdaterState", "idle") - update_successful = (update_failed_count == 0) - updater.set_params(update_successful, update_failed_count, exception) - except Exception: - cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") + try: + params.put("UpdaterState", "idle") + update_successful = (update_failed_count == 0) + updater.set_params(update_successful, update_failed_count, exception) + except Exception: + cloudlog.exception("uncaught updated exception while setting params, shouldn't happen") - # infrequent attempts if we successfully updated recently - wait_helper.user_request = UserRequest.NONE - wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60) + # infrequent attempts if we successfully updated recently + wait_helper.user_request = UserRequest.NONE + wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60) if __name__ == "__main__": diff --git a/system/camerad/cameras/camera_qcom2.cc b/system/camerad/cameras/camera_qcom2.cc index e17203a7dd..3191b821ac 100644 --- a/system/camerad/cameras/camera_qcom2.cc +++ b/system/camerad/cameras/camera_qcom2.cc @@ -606,6 +606,7 @@ void CameraState::camera_open(MultiCameraState *multi_cam_state_, int camera_num LOGD("start csiphy: %d", ret); ret = device_control(multi_cam_state->isp_fd, CAM_START_DEV, session_handle, isp_dev_handle); LOGD("start isp: %d", ret); + assert(ret == 0); // TODO: this is unneeded, should we be doing the start i2c in a different way? //ret = device_control(sensor_fd, CAM_START_DEV, session_handle, sensor_dev_handle); diff --git a/system/camerad/sensors/os04c10_registers.h b/system/camerad/sensors/os04c10_registers.h index c92389d1e1..ad91a02950 100644 --- a/system/camerad/sensors/os04c10_registers.h +++ b/system/camerad/sensors/os04c10_registers.h @@ -200,7 +200,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x370c, 0x01}, {0x370f, 0x04}, {0x3714, 0x24}, - {0x3716, 0x24}, + {0x3716, 0x04}, {0x3719, 0x11}, {0x371a, 0x1e}, {0x3720, 0x00}, @@ -271,7 +271,7 @@ const struct i2c_random_wr_payload init_array_os04c10[] = { {0x3815, 0x01}, {0x3816, 0x01}, {0x3817, 0x01}, - {0x3820, 0x88}, + {0x3820, 0xB0}, {0x3821, 0x00}, {0x3880, 0x25}, {0x3882, 0x20}, diff --git a/system/hardware/base.py b/system/hardware/base.py index 9c7a618337..6cdb4a4d64 100644 --- a/system/hardware/base.py +++ b/system/hardware/base.py @@ -1,16 +1,15 @@ from abc import abstractmethod, ABC from collections import namedtuple -from typing import Dict from cereal import log -ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'ambient', 'pmic']) +ThermalConfig = namedtuple('ThermalConfig', ['cpu', 'gpu', 'mem', 'bat', 'pmic']) NetworkType = log.DeviceState.NetworkType class HardwareBase(ABC): @staticmethod - def get_cmdline() -> Dict[str, str]: + def get_cmdline() -> dict[str, str]: with open('/proc/cmdline') as f: cmdline = f.read() return {kv[0]: kv[1] for kv in [s.split('=') for s in cmdline.split(' ')] if len(kv) == 2} diff --git a/system/hardware/pc/hardware.py b/system/hardware/pc/hardware.py index 4c2c104f94..719e272aea 100644 --- a/system/hardware/pc/hardware.py +++ b/system/hardware/pc/hardware.py @@ -57,7 +57,7 @@ class Pc(HardwareBase): print("SHUTDOWN!") def get_thermal_config(self): - return ThermalConfig(cpu=((None,), 1), gpu=((None,), 1), mem=(None, 1), bat=(None, 1), ambient=(None, 1), pmic=((None,), 1)) + return ThermalConfig(cpu=((None,), 1), gpu=((None,), 1), mem=(None, 1), bat=(None, 1), pmic=((None,), 1)) def set_screen_brightness(self, percentage): pass diff --git a/system/hardware/tici/agnos.json b/system/hardware/tici/agnos.json index a87f3d278d..e69842cfec 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-1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f.img.xz", - "hash": "1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f", - "hash_raw": "1cc21f31a7c09772fd759e6f2a614974bf4f2fc320c91a799ffadd11abc1f85f", + "url": "https://commadist.azureedge.net/agnosupdate/boot-f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe.img.xz", + "hash": "f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe", + "hash_raw": "f0de74e139b8b99224738d4e72a5b1831758f20b09ff6bb28f3aaaae1c4c1ebe", "size": 15636480, "sparse": false, "full_check": true, @@ -61,17 +61,17 @@ }, { "name": "system", - "url": "https://commadist.azureedge.net/agnosupdate/system-38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082.img.xz", - "hash": "5dc1718e21c49e4fa910fbb3b2321381f497b38335a0cf3ca923157d589abe89", - "hash_raw": "38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082", + "url": "https://commadist.azureedge.net/agnosupdate/system-0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2.img.xz", + "hash": "4858385ba6284bcaa179ab77ac4263486e4d8670df921e4ac400464dc1dde59c", + "hash_raw": "0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2", "size": 10737418240, "sparse": true, "full_check": false, "has_ab": true, "alt": { - "hash": "1809e36d8e376e0a0c8348e3f684aba4100fe0382042c051efd0e946af1ce696", - "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-38402b90b65729f8a4feb729c8a862cdf306659a85f27431d3ff7e52d4027082.img.xz", - "size": 4077270244 + "hash": "42658a6fff660d9b6abb9cb9fbb3481071259c9a9598718af6b1edff2b556009", + "url": "https://commadist.azureedge.net/agnosupdate/system-skip-chunks-0f69173d5f3058f7197c139442a6556be59e52f15402a263215a329ba5ec41e2.img.xz", + "size": 4548292756 } } ] \ No newline at end of file diff --git a/system/hardware/tici/agnos.py b/system/hardware/tici/agnos.py index ef7d9adb79..502295be07 100755 --- a/system/hardware/tici/agnos.py +++ b/system/hardware/tici/agnos.py @@ -6,7 +6,7 @@ import os import struct import subprocess import time -from typing import Dict, Generator, List, Tuple, Union +from collections.abc import Generator import requests @@ -20,7 +20,7 @@ class StreamingDecompressor: def __init__(self, url: str) -> None: self.buf = b"" - self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60) # type: ignore + self.req = requests.get(url, stream=True, headers={'Accept-Encoding': None}, timeout=60) self.it = self.req.iter_content(chunk_size=1024 * 1024) self.decompressor = lzma.LZMADecompressor(format=lzma.FORMAT_AUTO) self.eof = False @@ -117,7 +117,7 @@ def get_raw_hash(path: str, partition_size: int) -> str: return raw_hash.hexdigest().lower() -def verify_partition(target_slot_number: int, partition: Dict[str, Union[str, int]], force_full_check: bool = False) -> bool: +def verify_partition(target_slot_number: int, partition: dict[str, str | int], force_full_check: bool = False) -> bool: full_check = partition['full_check'] or force_full_check path = get_partition_path(target_slot_number, partition) @@ -184,7 +184,7 @@ def extract_casync_image(target_slot_number: int, partition: dict, cloudlog): target = casync.parse_caibx(partition['casync_caibx']) - sources: List[Tuple[str, casync.ChunkReader, casync.ChunkDict]] = [] + sources: list[tuple[str, casync.ChunkReader, casync.ChunkDict]] = [] # First source is the current partition. try: diff --git a/system/hardware/tici/amplifier.py b/system/hardware/tici/amplifier.py index e003f131cc..af82067467 100755 --- a/system/hardware/tici/amplifier.py +++ b/system/hardware/tici/amplifier.py @@ -2,7 +2,6 @@ import time from smbus2 import SMBus from collections import namedtuple -from typing import List # https://datasheets.maximintegrated.com/en/ds/MAX98089.pdf @@ -110,7 +109,7 @@ class Amplifier: def _get_shutdown_config(self, amp_disabled: bool) -> AmpConfig: return AmpConfig("Global shutdown", 0b0 if amp_disabled else 0b1, 0x51, 7, 0b10000000) - def _set_configs(self, configs: List[AmpConfig]) -> None: + def _set_configs(self, configs: list[AmpConfig]) -> None: with SMBus(self.AMP_I2C_BUS) as bus: for config in configs: if self.debug: @@ -123,7 +122,7 @@ class Amplifier: if self.debug: print(f" Changed {hex(config.register)}: {hex(old_value)} -> {hex(new_value)}") - def set_configs(self, configs: List[AmpConfig]) -> bool: + def set_configs(self, configs: list[AmpConfig]) -> bool: # retry in case panda is using the amp tries = 15 for i in range(15): diff --git a/system/hardware/tici/casync.py b/system/hardware/tici/casync.py index 993336616d..986228c1cd 100755 --- a/system/hardware/tici/casync.py +++ b/system/hardware/tici/casync.py @@ -7,7 +7,7 @@ import sys import time from abc import ABC, abstractmethod from collections import defaultdict, namedtuple -from typing import Callable, Dict, List, Optional, Tuple +from collections.abc import Callable import requests from Crypto.Hash import SHA512 @@ -28,7 +28,7 @@ CHUNK_DOWNLOAD_RETRIES = 3 CAIBX_DOWNLOAD_TIMEOUT = 120 Chunk = namedtuple('Chunk', ['sha', 'offset', 'length']) -ChunkDict = Dict[bytes, Chunk] +ChunkDict = dict[bytes, Chunk] class ChunkReader(ABC): @@ -83,7 +83,7 @@ class RemoteChunkReader(ChunkReader): return decompressor.decompress(contents) -def parse_caibx(caibx_path: str) -> List[Chunk]: +def parse_caibx(caibx_path: str) -> list[Chunk]: """Parses the chunks from a caibx file. Can handle both local and remote files. Returns a list of chunks with hash, offset and length""" caibx: io.BufferedIOBase @@ -132,7 +132,7 @@ def parse_caibx(caibx_path: str) -> List[Chunk]: return chunks -def build_chunk_dict(chunks: List[Chunk]) -> ChunkDict: +def build_chunk_dict(chunks: list[Chunk]) -> ChunkDict: """Turn a list of chunks into a dict for faster lookups based on hash. Keep first chunk since it's more likely to be already downloaded.""" r = {} @@ -142,11 +142,11 @@ def build_chunk_dict(chunks: List[Chunk]) -> ChunkDict: return r -def extract(target: List[Chunk], - sources: List[Tuple[str, ChunkReader, ChunkDict]], +def extract(target: list[Chunk], + sources: list[tuple[str, ChunkReader, ChunkDict]], out_path: str, - progress: Optional[Callable[[int], None]] = None): - stats: Dict[str, int] = defaultdict(int) + progress: Callable[[int], None] = None): + stats: dict[str, int] = defaultdict(int) mode = 'rb+' if os.path.exists(out_path) else 'wb' with open(out_path, mode) as out: @@ -181,7 +181,7 @@ def extract(target: List[Chunk], return stats -def print_stats(stats: Dict[str, int]): +def print_stats(stats: dict[str, int]): total_bytes = sum(stats.values()) print(f"Total size: {total_bytes / 1024 / 1024:.2f} MB") for name, total in stats.items(): diff --git a/system/hardware/tici/hardware.h b/system/hardware/tici/hardware.h index f6ea86b002..e553a665a8 100644 --- a/system/hardware/tici/hardware.h +++ b/system/hardware/tici/hardware.h @@ -72,6 +72,7 @@ public: std::map ret = { {"/BUILD", util::read_file("/BUILD")}, {"lsblk", util::check_output("lsblk -o NAME,SIZE,STATE,VENDOR,MODEL,REV,SERIAL")}, + {"SOM ID", util::read_file("/sys/devices/platform/vendor/vendor:gpio-som-id/som_id")}, }; std::string bs = util::check_output("abctl --boot_slot"); diff --git a/system/hardware/tici/hardware.py b/system/hardware/tici/hardware.py index 3431718e22..5bb1032ba9 100644 --- a/system/hardware/tici/hardware.py +++ b/system/hardware/tici/hardware.py @@ -349,7 +349,6 @@ class Tici(HardwareBase): gpu=(("gpu0-usr", "gpu1-usr"), 1000), mem=("ddr-usr", 1000), bat=(None, 1), - ambient=("xo-therm-adc", 1000), pmic=(("pm8998_tz", "pm8005_tz"), 1000)) def set_screen_brightness(self, percentage): @@ -457,24 +456,37 @@ class Tici(HardwareBase): def configure_modem(self): sim_id = self.get_sim_info().get('sim_id', '') - # configure modem as data-centric - cmds = [ - 'AT+QNVW=5280,0,"0102000000000000"', - 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00', - 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01', - ] modem = self.get_modem() + try: + manufacturer = str(modem.Get(MM_MODEM, 'Manufacturer', dbus_interface=DBUS_PROPS, timeout=TIMEOUT)) + except Exception: + manufacturer = None + + cmds = [] + if manufacturer == 'Cavli Inc.': + cmds += [ + # use sim slot + 'AT^SIMSWAP=1', + + # configure ECM mode + 'AT$QCPCFG=usbNet,1' + ] + else: + cmds += [ + # configure modem as data-centric + 'AT+QNVW=5280,0,"0102000000000000"', + 'AT+QNVFW="/nv/item_files/ims/IMS_enable",00', + 'AT+QNVFW="/nv/item_files/modem/mmode/ue_usage_setting",01', + ] + + # clear out old blue prime initial APN + os.system('mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn="') for cmd in cmds: try: modem.Command(cmd, math.ceil(TIMEOUT), dbus_interface=MM_MODEM, timeout=TIMEOUT) except Exception: pass - # blue prime - blue_prime = sim_id.startswith('8901410') - initial_apn = "Broadband" if blue_prime else "" - os.system(f'mmcli -m any --3gpp-set-initial-eps-bearer-settings="apn={initial_apn}"') - # eSIM prime if sim_id.startswith('8985235'): dest = "/etc/NetworkManager/system-connections/esim.nmconnection" diff --git a/system/hardware/tici/power_monitor.py b/system/hardware/tici/power_monitor.py index ef3055ac47..296290dae8 100755 --- a/system/hardware/tici/power_monitor.py +++ b/system/hardware/tici/power_monitor.py @@ -3,7 +3,6 @@ import sys import time import datetime import numpy as np -from typing import List from collections import deque from openpilot.common.realtime import Ratekeeper @@ -14,7 +13,7 @@ def read_power(): with open("/sys/bus/i2c/devices/0-0040/hwmon/hwmon1/power1_input") as f: return int(f.read()) / 1e6 -def sample_power(seconds=5) -> List[float]: +def sample_power(seconds=5) -> list[float]: rate = 123 rk = Ratekeeper(rate, print_delay_threshold=None) diff --git a/system/hardware/tici/precise_power_measure.py b/system/hardware/tici/precise_power_measure.py index e186ba4aea..52fe0850ab 100755 --- a/system/hardware/tici/precise_power_measure.py +++ b/system/hardware/tici/precise_power_measure.py @@ -6,4 +6,4 @@ if __name__ == '__main__': print("measuring for 5 seconds") for _ in range(3): pwrs = sample_power() - print("mean %.2f std %.2f" % (np.mean(pwrs), np.std(pwrs))) + print(f"mean {np.mean(pwrs):.2f} std {np.std(pwrs):.2f}") diff --git a/system/hardware/tici/tests/compare_casync_manifest.py b/system/hardware/tici/tests/compare_casync_manifest.py index 835985b0b5..7de66d91d0 100755 --- a/system/hardware/tici/tests/compare_casync_manifest.py +++ b/system/hardware/tici/tests/compare_casync_manifest.py @@ -3,7 +3,6 @@ import argparse import collections import multiprocessing import os -from typing import Dict, List import requests from tqdm import tqdm @@ -42,7 +41,7 @@ if __name__ == "__main__": szs = list(tqdm(pool.imap(get_chunk_download_size, to), total=len(to))) chunk_sizes = {t.sha: sz for (t, sz) in zip(to, szs, strict=True)} - sources: Dict[str, List[int]] = { + sources: dict[str, list[int]] = { 'seed': [], 'remote_uncompressed': [], 'remote_compressed': [], diff --git a/system/hardware/tici/tests/test_hardware.py b/system/hardware/tici/tests/test_hardware.py index 4abc86107b..0c436595ee 100755 --- a/system/hardware/tici/tests/test_hardware.py +++ b/system/hardware/tici/tests/test_hardware.py @@ -12,11 +12,6 @@ HARDWARE = Tici() @pytest.mark.tici class TestHardware(unittest.TestCase): - @classmethod - def setUpClass(cls): - HARDWARE.initialize_hardware() - HARDWARE.set_power_save(False) - def test_power_save_time(self): ts = [] for _ in range(5): @@ -30,4 +25,4 @@ class TestHardware(unittest.TestCase): if __name__ == "__main__": - unittest.main() + pytest.main() diff --git a/system/hardware/tici/tests/test_power_draw.py b/system/hardware/tici/tests/test_power_draw.py index eed2ce231b..352fcdf18a 100755 --- a/system/hardware/tici/tests/test_power_draw.py +++ b/system/hardware/tici/tests/test_power_draw.py @@ -1,57 +1,50 @@ #!/usr/bin/env python3 +from collections import defaultdict, deque +import sys import pytest import unittest import time -import threading import numpy as np from dataclasses import dataclass from tabulate import tabulate -from typing import List import cereal.messaging as messaging from cereal.services import SERVICE_LIST +from openpilot.common.mock import mock_messages from openpilot.selfdrive.car.car_helpers import write_car_param -from openpilot.system.hardware import HARDWARE from openpilot.system.hardware.tici.power_monitor import get_power from openpilot.selfdrive.manager.process_config import managed_processes from openpilot.selfdrive.manager.manager import manager_cleanup -from openpilot.selfdrive.navd.tests.test_map_renderer import gen_llk -SAMPLE_TIME = 8 # seconds to sample power +SAMPLE_TIME = 8 # seconds to sample power +MAX_WARMUP_TIME = 30 # seconds to wait for SAMPLE_TIME consecutive valid samples @dataclass class Proc: - name: str + procs: list[str] power: float - msgs: List[str] + msgs: list[str] rtol: float = 0.05 atol: float = 0.12 - warmup: float = 6. + + @property + def name(self): + return '+'.join(self.procs) + PROCS = [ - Proc('camerad', 2.1, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), - Proc('modeld', 1.12, atol=0.2, msgs=['modelV2']), - Proc('dmonitoringmodeld', 0.4, msgs=['driverStateV2']), - Proc('encoderd', 0.23, msgs=[]), - Proc('mapsd', 0.05, msgs=['mapRenderState']), - Proc('navmodeld', 0.05, msgs=['navModel']), + Proc(['camerad'], 2.1, msgs=['roadCameraState', 'wideRoadCameraState', 'driverCameraState']), + Proc(['modeld'], 1.12, atol=0.2, msgs=['modelV2']), + Proc(['dmonitoringmodeld'], 0.4, msgs=['driverStateV2']), + Proc(['encoderd'], 0.23, msgs=[]), + Proc(['mapsd', 'navmodeld'], 0.05, msgs=['mapRenderState', 'navModel']), ] -def send_llk_msg(done): - # Send liveLocationKalman at 20Hz - pm = messaging.PubMaster(['liveLocationKalman']) - while not done.is_set(): - msg = gen_llk() - pm.send('liveLocationKalman', msg) - time.sleep(1/20.) - @pytest.mark.tici class TestPowerDraw(unittest.TestCase): def setUp(self): - HARDWARE.initialize_hardware() - HARDWARE.set_power_save(False) write_car_param() # wait a bit for power save to disable @@ -60,44 +53,83 @@ class TestPowerDraw(unittest.TestCase): def tearDown(self): manager_cleanup() + def get_expected_messages(self, proc): + return int(sum(SAMPLE_TIME * SERVICE_LIST[msg].frequency for msg in proc.msgs)) + + def valid_msg_count(self, proc, msg_counts): + msgs_received = sum(msg_counts[msg] for msg in proc.msgs) + msgs_expected = self.get_expected_messages(proc) + return np.core.numeric.isclose(msgs_expected, msgs_received, rtol=.02, atol=2) + + def valid_power_draw(self, proc, used): + return np.core.numeric.isclose(used, proc.power, rtol=proc.rtol, atol=proc.atol) + + def tabulate_msg_counts(self, msgs_and_power): + msg_counts = defaultdict(int) + for _, counts in msgs_and_power: + for msg, count in counts.items(): + msg_counts[msg] += count + return msg_counts + + def get_power_with_warmup_for_target(self, proc, prev): + socks = {msg: messaging.sub_sock(msg) for msg in proc.msgs} + for sock in socks.values(): + messaging.drain_sock_raw(sock) + + msgs_and_power = deque([], maxlen=SAMPLE_TIME) + + start_time = time.monotonic() + + while (time.monotonic() - start_time) < MAX_WARMUP_TIME: + power = get_power(1) + iteration_msg_counts = {} + for msg,sock in socks.items(): + iteration_msg_counts[msg] = len(messaging.drain_sock_raw(sock)) + msgs_and_power.append((power, iteration_msg_counts)) + + if len(msgs_and_power) < SAMPLE_TIME: + continue + + msg_counts = self.tabulate_msg_counts(msgs_and_power) + now = np.mean([m[0] for m in msgs_and_power]) + + if self.valid_msg_count(proc, msg_counts) and self.valid_power_draw(proc, now - prev): + break + + return now, msg_counts, time.monotonic() - start_time - SAMPLE_TIME + + @mock_messages(['liveLocationKalman']) def test_camera_procs(self): baseline = get_power() - done = threading.Event() - thread = threading.Thread(target=send_llk_msg, args=(done,), daemon=True) - thread.start() prev = baseline used = {} + warmup_time = {} msg_counts = {} + for proc in PROCS: - socks = {msg: messaging.sub_sock(msg) for msg in proc.msgs} - managed_processes[proc.name].start() - time.sleep(proc.warmup) - for sock in socks.values(): - messaging.drain_sock_raw(sock) + for p in proc.procs: + managed_processes[p].start() + now, local_msg_counts, warmup_time[proc.name] = self.get_power_with_warmup_for_target(proc, prev) + msg_counts.update(local_msg_counts) - now = get_power(SAMPLE_TIME) used[proc.name] = now - prev prev = now - for msg,sock in socks.items(): - msg_counts[msg] = len(messaging.drain_sock_raw(sock)) - done.set() manager_cleanup() - tab = [['process', 'expected (W)', 'measured (W)', '# msgs expected', '# msgs received']] + tab = [['process', 'expected (W)', 'measured (W)', '# msgs expected', '# msgs received', "warmup time (s)"]] for proc in PROCS: cur = used[proc.name] expected = proc.power msgs_received = sum(msg_counts[msg] for msg in proc.msgs) - msgs_expected = int(sum(SAMPLE_TIME * SERVICE_LIST[msg].frequency for msg in proc.msgs)) - tab.append([proc.name, round(expected, 2), round(cur, 2), msgs_expected, msgs_received]) + tab.append([proc.name, round(expected, 2), round(cur, 2), self.get_expected_messages(proc), msgs_received, round(warmup_time[proc.name], 2)]) with self.subTest(proc=proc.name): - np.testing.assert_allclose(msgs_expected, msgs_received, rtol=.02, atol=2) - np.testing.assert_allclose(cur, expected, rtol=proc.rtol, atol=proc.atol) + self.assertTrue(self.valid_msg_count(proc, msg_counts), f"expected {self.get_expected_messages(proc)} msgs, got {msgs_received} msgs") + self.assertTrue(self.valid_power_draw(proc, cur), f"expected {expected:.2f}W, got {cur:.2f}W") print(tabulate(tab)) print(f"Baseline {baseline:.2f}W\n") if __name__ == "__main__": - unittest.main() + pytest.main(sys.argv) diff --git a/system/loggerd/deleter.py b/system/loggerd/deleter.py index 868340150a..2f0b96c90e 100755 --- a/system/loggerd/deleter.py +++ b/system/loggerd/deleter.py @@ -2,7 +2,6 @@ import os import shutil import threading -from typing import List from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog from openpilot.system.loggerd.config import get_available_bytes, get_available_percent @@ -23,7 +22,7 @@ def has_preserve_xattr(d: str) -> bool: return getxattr(os.path.join(Paths.log_root(), d), PRESERVE_ATTR_NAME) == PRESERVE_ATTR_VALUE -def get_preserved_segments(dirs_by_creation: List[str]) -> List[str]: +def get_preserved_segments(dirs_by_creation: list[str]) -> list[str]: preserved = [] for n, d in enumerate(filter(has_preserve_xattr, reversed(dirs_by_creation))): if n == PRESERVE_COUNT: diff --git a/system/loggerd/encoder/encoder.cc b/system/loggerd/encoder/encoder.cc index 0aba4b8b49..c4bd91bcf7 100644 --- a/system/loggerd/encoder/encoder.cc +++ b/system/loggerd/encoder/encoder.cc @@ -2,6 +2,10 @@ VideoEncoder::VideoEncoder(const EncoderInfo &encoder_info, int in_width, int in_height) : encoder_info(encoder_info), in_width(in_width), in_height(in_height) { + + out_width = encoder_info.frame_width > 0 ? encoder_info.frame_width : in_width; + out_height = encoder_info.frame_height > 0 ? encoder_info.frame_height : in_height; + pm.reset(new PubMaster({encoder_info.publish_name})); } @@ -25,6 +29,8 @@ void VideoEncoder::publisher_publish(VideoEncoder *e, int segment_num, uint32_t edata.setFlags(flags); edata.setLen(dat.size()); edat.setData(dat); + edat.setWidth(out_width); + edat.setHeight(out_height); if (flags & V4L2_BUF_FLAG_KEYFRAME) edat.setHeader(header); uint32_t bytes_size = capnp::computeSerializedSizeInWords(msg) * sizeof(capnp::word); diff --git a/system/loggerd/encoder/encoder.h b/system/loggerd/encoder/encoder.h index 9c23928cc6..7c203f9193 100644 --- a/system/loggerd/encoder/encoder.h +++ b/system/loggerd/encoder/encoder.h @@ -22,10 +22,11 @@ public: virtual void encoder_open(const char* path) = 0; virtual void encoder_close() = 0; - static void publisher_publish(VideoEncoder *e, int segment_num, uint32_t idx, VisionIpcBufExtra &extra, unsigned int flags, kj::ArrayPtr header, kj::ArrayPtr dat); + void publisher_publish(VideoEncoder *e, int segment_num, uint32_t idx, VisionIpcBufExtra &extra, unsigned int flags, kj::ArrayPtr header, kj::ArrayPtr dat); protected: int in_width, in_height; + int out_width, out_height; const EncoderInfo encoder_info; private: diff --git a/system/loggerd/encoder/ffmpeg_encoder.cc b/system/loggerd/encoder/ffmpeg_encoder.cc index f44f2fbed7..9d992f088d 100644 --- a/system/loggerd/encoder/ffmpeg_encoder.cc +++ b/system/loggerd/encoder/ffmpeg_encoder.cc @@ -29,16 +29,16 @@ FfmpegEncoder::FfmpegEncoder(const EncoderInfo &encoder_info, int in_width, int frame = av_frame_alloc(); assert(frame); frame->format = AV_PIX_FMT_YUV420P; - frame->width = encoder_info.frame_width; - frame->height = encoder_info.frame_height; - frame->linesize[0] = encoder_info.frame_width; - frame->linesize[1] = encoder_info.frame_width/2; - frame->linesize[2] = encoder_info.frame_width/2; + frame->width = out_width; + frame->height = out_height; + frame->linesize[0] = out_width; + frame->linesize[1] = out_width/2; + frame->linesize[2] = out_width/2; convert_buf.resize(in_width * in_height * 3 / 2); - if (in_width != encoder_info.frame_width || in_height != encoder_info.frame_height) { - downscale_buf.resize(encoder_info.frame_width * encoder_info.frame_height * 3 / 2); + if (in_width != out_width || in_height != out_height) { + downscale_buf.resize(out_width * out_height * 3 / 2); } } diff --git a/system/loggerd/encoder/v4l_encoder.cc b/system/loggerd/encoder/v4l_encoder.cc index 571f5979e2..2bd2863126 100644 --- a/system/loggerd/encoder/v4l_encoder.cc +++ b/system/loggerd/encoder/v4l_encoder.cc @@ -164,8 +164,8 @@ V4LEncoder::V4LEncoder(const EncoderInfo &encoder_info, int in_width, int in_hei .fmt = { .pix_mp = { // downscales are free with v4l - .width = (unsigned int)encoder_info.frame_width, - .height = (unsigned int)encoder_info.frame_height, + .width = (unsigned int)(out_width), + .height = (unsigned int)(out_height), .pixelformat = (encoder_info.encode_type == cereal::EncodeIndex::Type::FULL_H_E_V_C) ? V4L2_PIX_FMT_HEVC : V4L2_PIX_FMT_H264, .field = V4L2_FIELD_ANY, .colorspace = V4L2_COLORSPACE_DEFAULT, diff --git a/system/loggerd/logger.cc b/system/loggerd/logger.cc index bb829df6ed..7a829a2f1f 100644 --- a/system/loggerd/logger.cc +++ b/system/loggerd/logger.cc @@ -40,10 +40,11 @@ kj::Array logger_build_init_data() { init.setOsVersion(util::read_file("/VERSION")); // log params - auto params = Params(); + auto params = Params(util::getenv("PARAMS_COPY_PATH", "")); std::map params_map = params.readAll(); init.setGitCommit(params_map["GitCommit"]); + init.setGitCommitDate(params_map["GitCommitDate"]); init.setGitBranch(params_map["GitBranch"]); init.setGitRemote(params_map["GitRemote"]); init.setPassive(false); diff --git a/system/loggerd/loggerd.cc b/system/loggerd/loggerd.cc index 27dfa187c4..3c0ffc1667 100644 --- a/system/loggerd/loggerd.cc +++ b/system/loggerd/loggerd.cc @@ -116,7 +116,7 @@ int handle_encoder_msg(LoggerdState *s, Message *msg, std::string &name, struct assert(encoder_info.filename != NULL); re.writer.reset(new VideoWriter(s->logger.segmentPath().c_str(), encoder_info.filename, idx.getType() != cereal::EncodeIndex::Type::FULL_H_E_V_C, - encoder_info.frame_width, encoder_info.frame_height, encoder_info.fps, idx.getType())); + edata.getWidth(), edata.getHeight(), encoder_info.fps, idx.getType())); // write the header auto header = edata.getHeader(); re.writer->write((uint8_t *)header.begin(), header.size(), idx.getTimestampEof()/1000, true, false); diff --git a/system/loggerd/loggerd.h b/system/loggerd/loggerd.h index cfc06c28d3..ea288f4861 100644 --- a/system/loggerd/loggerd.h +++ b/system/loggerd/loggerd.h @@ -35,8 +35,8 @@ public: const char *publish_name; const char *filename = NULL; bool record = true; - int frame_width = 1928; - int frame_height = 1208; + int frame_width = -1; + int frame_height = -1; int fps = MAIN_FPS; int bitrate = MAIN_BITRATE; cereal::EncodeIndex::Type encode_type = Hardware::PC() ? cereal::EncodeIndex::Type::BIG_BOX_LOSSLESS diff --git a/system/loggerd/tests/loggerd_tests_common.py b/system/loggerd/tests/loggerd_tests_common.py index 3aa9e40531..42eec2a0f4 100644 --- a/system/loggerd/tests/loggerd_tests_common.py +++ b/system/loggerd/tests/loggerd_tests_common.py @@ -2,7 +2,6 @@ import os import random import unittest from pathlib import Path -from typing import Optional import openpilot.system.loggerd.deleter as deleter @@ -12,7 +11,7 @@ from openpilot.system.hardware.hw import Paths from openpilot.system.loggerd.xattr_cache import setxattr -def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: Optional[bytes] = None) -> None: +def create_random_file(file_path: Path, size_mb: float, lock: bool = False, upload_xattr: bytes = None) -> None: file_path.parent.mkdir(parents=True, exist_ok=True) if lock: @@ -82,7 +81,7 @@ class UploaderTestCase(unittest.TestCase): self.params.put("DongleId", "0000000000000000") def make_file_with_data(self, f_dir: str, fn: str, size_mb: float = .1, lock: bool = False, - upload_xattr: Optional[bytes] = None, preserve_xattr: Optional[bytes] = None) -> Path: + upload_xattr: bytes = None, preserve_xattr: bytes = None) -> Path: file_path = Path(Paths.log_root()) / f_dir / fn create_random_file(file_path, size_mb, lock, upload_xattr) diff --git a/system/loggerd/tests/test_deleter.py b/system/loggerd/tests/test_deleter.py index e4112b7b4e..37d25507e0 100755 --- a/system/loggerd/tests/test_deleter.py +++ b/system/loggerd/tests/test_deleter.py @@ -4,7 +4,7 @@ import threading import unittest from collections import namedtuple from pathlib import Path -from typing import Sequence +from collections.abc import Sequence import openpilot.system.loggerd.deleter as deleter from openpilot.common.timeout import Timeout, TimeoutException diff --git a/system/loggerd/tests/test_loggerd.py b/system/loggerd/tests/test_loggerd.py index 0cd8548809..c80dc19fce 100755 --- a/system/loggerd/tests/test_loggerd.py +++ b/system/loggerd/tests/test_loggerd.py @@ -8,7 +8,6 @@ import subprocess import time from collections import defaultdict from pathlib import Path -from typing import Dict, List from flaky import flaky import cereal.messaging as messaging @@ -76,7 +75,7 @@ class TestLoggerd: end_type = SentinelType.endOfRoute if route else SentinelType.endOfSegment assert msgs[-1].sentinel.type == end_type - def _publish_random_messages(self, services: List[str]) -> Dict[str, list]: + def _publish_random_messages(self, services: list[str]) -> dict[str, list]: pm = messaging.PubMaster(services) managed_processes["loggerd"].start() @@ -107,6 +106,7 @@ class TestLoggerd: # param, initData field, value ("DongleId", "dongleId", dongle), ("GitCommit", "gitCommit", "commit"), + ("GitCommitDate", "gitCommitDate", "date"), ("GitBranch", "gitBranch", "branch"), ("GitRemote", "gitRemote", "remote"), ] diff --git a/system/loggerd/tests/test_uploader.py b/system/loggerd/tests/test_uploader.py index b674de5438..73917a30cf 100755 --- a/system/loggerd/tests/test_uploader.py +++ b/system/loggerd/tests/test_uploader.py @@ -6,7 +6,6 @@ import unittest import logging import json from pathlib import Path -from typing import List, Optional from openpilot.system.hardware.hw import Paths from openpilot.common.swaglog import cloudlog @@ -53,7 +52,7 @@ class TestUploader(UploaderTestCase): self.end_event.set() self.up_thread.join() - def gen_files(self, lock=False, xattr: Optional[bytes] = None, boot=True) -> List[Path]: + def gen_files(self, lock=False, xattr: bytes = None, boot=True) -> list[Path]: f_paths = [] for t in ["qlog", "rlog", "dcamera.hevc", "fcamera.hevc"]: f_paths.append(self.make_file_with_data(self.seg_dir, t, 1, lock=lock, upload_xattr=xattr)) @@ -62,7 +61,7 @@ class TestUploader(UploaderTestCase): f_paths.append(self.make_file_with_data("boot", f"{self.seg_dir}", 1, lock=lock, upload_xattr=xattr)) return f_paths - def gen_order(self, seg1: List[int], seg2: List[int], boot=True) -> List[str]: + def gen_order(self, seg1: list[int], seg2: list[int], boot=True) -> list[str]: keys = [] if boot: keys += [f"boot/{self.seg_format.format(i)}.bz2" for i in seg1] diff --git a/system/loggerd/uploader.py b/system/loggerd/uploader.py index 105e830a4c..33ee8c1850 100755 --- a/system/loggerd/uploader.py +++ b/system/loggerd/uploader.py @@ -9,7 +9,8 @@ import threading import time import traceback import datetime -from typing import BinaryIO, Iterator, List, Optional, Tuple +from typing import BinaryIO +from collections.abc import Iterator from cereal import log import cereal.messaging as messaging @@ -42,10 +43,10 @@ class FakeResponse: self.request = FakeRequest() -def get_directory_sort(d: str) -> List[str]: +def get_directory_sort(d: str) -> list[str]: return [s.rjust(10, '0') for s in d.rsplit('--', 1)] -def listdir_by_creation(d: str) -> List[str]: +def listdir_by_creation(d: str) -> list[str]: if not os.path.isdir(d): return [] @@ -82,7 +83,7 @@ class Uploader: self.immediate_folders = ["crash/", "boot/"] self.immediate_priority = {"qlog": 0, "qlog.bz2": 0, "qcamera.ts": 1} - def list_upload_files(self, metered: bool) -> Iterator[Tuple[str, str, str]]: + def list_upload_files(self, metered: bool) -> Iterator[tuple[str, str, str]]: r = self.params.get("AthenadRecentlyViewedRoutes", encoding="utf8") requested_routes = [] if r is None else r.split(",") @@ -121,7 +122,7 @@ class Uploader: yield name, key, fn - def next_file_to_upload(self, metered: bool) -> Optional[Tuple[str, str, str]]: + def next_file_to_upload(self, metered: bool) -> tuple[str, str, str] | None: upload_files = list(self.list_upload_files(metered)) for name, key, fn in upload_files: @@ -207,10 +208,10 @@ class Uploader: return success - def step(self, network_type: int, metered: bool) -> bool: + def step(self, network_type: int, metered: bool) -> bool | None: d = self.next_file_to_upload(metered) if d is None: - return True + return None name, key, fn = d @@ -221,7 +222,7 @@ class Uploader: return self.upload(name, key, fn, network_type, metered) -def main(exit_event: Optional[threading.Event] = None) -> None: +def main(exit_event: threading.Event = None) -> None: if exit_event is None: exit_event = threading.Event() @@ -253,12 +254,15 @@ def main(exit_event: Optional[threading.Event] = None) -> None: continue success = uploader.step(sm['deviceState'].networkType.raw, sm['deviceState'].networkMetered) - if success: + if success is None: + backoff = 60 if offroad else 5 + elif success: backoff = 0.1 - elif allow_sleep: + else: cloudlog.info("upload backoff %r", backoff) backoff = min(backoff*2, 120) - time.sleep(backoff + random.uniform(0, backoff)) + if allow_sleep: + time.sleep(backoff + random.uniform(0, backoff)) if __name__ == "__main__": diff --git a/system/loggerd/xattr_cache.py b/system/loggerd/xattr_cache.py index 5feeff34d2..d3220118ac 100644 --- a/system/loggerd/xattr_cache.py +++ b/system/loggerd/xattr_cache.py @@ -1,10 +1,9 @@ import os import errno -from typing import Dict, Optional, Tuple -_cached_attributes: Dict[Tuple, Optional[bytes]] = {} +_cached_attributes: dict[tuple, bytes | None] = {} -def getxattr(path: str, attr_name: str) -> Optional[bytes]: +def getxattr(path: str, attr_name: str) -> bytes | None: key = (path, attr_name) if key not in _cached_attributes: try: diff --git a/system/qcomgpsd/nmeaport.py b/system/qcomgpsd/nmeaport.py index 231096fc5d..caff7af646 100644 --- a/system/qcomgpsd/nmeaport.py +++ b/system/qcomgpsd/nmeaport.py @@ -93,7 +93,7 @@ def nmea_checksum_ok(s): def process_nmea_port_messages(device:str="/dev/ttyUSB1") -> NoReturn: while True: try: - with open(device, "r") as nmeaport: + with open(device) as nmeaport: for line in nmeaport: line = line.strip() if DEBUG: diff --git a/system/qcomgpsd/qcomgpsd.py b/system/qcomgpsd/qcomgpsd.py index 3f72350234..e8c407a627 100755 --- a/system/qcomgpsd/qcomgpsd.py +++ b/system/qcomgpsd/qcomgpsd.py @@ -10,7 +10,7 @@ import shutil import subprocess import datetime from multiprocessing import Process, Event -from typing import NoReturn, Optional +from typing import NoReturn from struct import unpack_from, calcsize, pack from cereal import log @@ -90,15 +90,11 @@ def try_setup_logs(diag, logs): return setup_logs(diag, logs) @retry(attempts=3, delay=1.0) -def at_cmd(cmd: str) -> Optional[str]: +def at_cmd(cmd: str) -> str | None: return subprocess.check_output(f"mmcli -m any --timeout 30 --command='{cmd}'", shell=True, encoding='utf8') 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 + return "QGPS: 1" in at_cmd("AT+QGPS?") def download_assistance(): try: @@ -346,7 +342,7 @@ def main() -> NoReturn: gps.bearingDeg = report["q_FltHeadingRad"] * 180/math.pi # TODO needs update if there is another leap second, after june 2024? - dt_timestamp = (datetime.datetime(1980, 1, 6, 0, 0, 0, 0, datetime.timezone.utc) + + dt_timestamp = (datetime.datetime(1980, 1, 6, 0, 0, 0, 0, datetime.UTC) + datetime.timedelta(weeks=report['w_GpsWeekNumber']) + datetime.timedelta(seconds=(1e-3*report['q_GpsFixTimeMs'] - 18))) gps.unixTimestampMillis = dt_timestamp.timestamp()*1e3 diff --git a/system/qcomgpsd/tests/test_qcomgpsd.py b/system/qcomgpsd/tests/test_qcomgpsd.py index 8291f2cc32..6c93f7dd93 100755 --- a/system/qcomgpsd/tests/test_qcomgpsd.py +++ b/system/qcomgpsd/tests/test_qcomgpsd.py @@ -68,13 +68,14 @@ class TestRawgpsd(unittest.TestCase): def test_turns_off_gnss(self): for s in (0.1, 1, 5): - managed_processes['qcomgpsd'].start() - time.sleep(s) - managed_processes['qcomgpsd'].stop() + with self.subTest(runtime=s): + managed_processes['qcomgpsd'].start() + time.sleep(s) + managed_processes['qcomgpsd'].stop() - ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') - loc_status = json.loads(ls) - assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} + ls = subprocess.check_output("mmcli -m any --location-status --output-json", shell=True, encoding='utf-8') + loc_status = json.loads(ls) + assert set(loc_status['modem']['location']['enabled']) <= {'3gpp-lac-ci'} def check_assistance(self, should_be_loaded): diff --git a/system/sensord/pigeond.py b/system/sensord/pigeond.py index 78b3b07498..21b3a86f97 100755 --- a/system/sensord/pigeond.py +++ b/system/sensord/pigeond.py @@ -7,7 +7,6 @@ import struct import requests import urllib.parse from datetime import datetime -from typing import List, Optional, Tuple from cereal import messaging from openpilot.common.params import Params @@ -41,7 +40,7 @@ def add_ubx_checksum(msg: bytes) -> bytes: B = (B + A) % 256 return msg + bytes([A, B]) -def get_assistnow_messages(token: bytes) -> List[bytes]: +def get_assistnow_messages(token: bytes) -> list[bytes]: # make request # TODO: implement adding the last known location r = requests.get("https://online-live2.services.u-blox.com/GetOnlineData.ashx", params=urllib.parse.urlencode({ @@ -238,7 +237,7 @@ def initialize_pigeon(pigeon: TTYPigeon) -> bool: return False return True -def deinitialize_and_exit(pigeon: Optional[TTYPigeon]): +def deinitialize_and_exit(pigeon: TTYPigeon | None): cloudlog.warning("Storing almanac in ublox flash") if pigeon is not None: @@ -259,7 +258,7 @@ def deinitialize_and_exit(pigeon: Optional[TTYPigeon]): set_power(False) sys.exit(0) -def create_pigeon() -> Tuple[TTYPigeon, messaging.PubMaster]: +def create_pigeon() -> tuple[TTYPigeon, messaging.PubMaster]: pigeon = None # register exit handler diff --git a/system/sensord/sensors_qcom2.cc b/system/sensord/sensors_qcom2.cc index 36d9b4a13e..9cbc24864d 100644 --- a/system/sensord/sensors_qcom2.cc +++ b/system/sensord/sensors_qcom2.cc @@ -134,7 +134,13 @@ int sensor_loop(I2CBus *i2c_bus_imu) { // increase interrupt quality by pinning interrupt and process to core 1 setpriority(PRIO_PROCESS, 0, -18); util::set_core_affinity({1}); - std::system("sudo su -c 'echo 1 > /proc/irq/336/smp_affinity_list'"); + + // TODO: get the IRQ number from gpiochip + std::string irq_path = "/proc/irq/336/smp_affinity_list"; + if (!util::file_exists(irq_path)) { + irq_path = "/proc/irq/335/smp_affinity_list"; + } + std::system(util::string_format("sudo su -c 'echo 1 > %s'", irq_path.c_str()).c_str()); // thread for reading events via interrupts threads.emplace_back(&interrupt_loop, std::ref(sensors_init)); diff --git a/system/sensord/tests/test_pigeond.py b/system/sensord/tests/test_pigeond.py index f2ab43bbb7..742e20bb90 100755 --- a/system/sensord/tests/test_pigeond.py +++ b/system/sensord/tests/test_pigeond.py @@ -40,7 +40,7 @@ class TestPigeond(unittest.TestCase): sm.update(1 * 1000) if sm.updated['ubloxRaw']: break - assert sm.rcv_frame['ubloxRaw'] > 0, "pigeond didn't start outputting messages in time" + assert sm.recv_frame['ubloxRaw'] > 0, "pigeond didn't start outputting messages in time" et = time.monotonic() - start_time assert et < 5, f"pigeond took {et:.1f}s to start" diff --git a/system/timed.py b/system/timed.py index 21fb47b680..39acb2ba12 100755 --- a/system/timed.py +++ b/system/timed.py @@ -65,10 +65,15 @@ def main() -> NoReturn: cloudlog.debug("Restoring timezone from param") set_timezone(tz) + pm = messaging.PubMaster(['clocks']) sm = messaging.SubMaster(['liveLocationKalman']) while True: sm.update(1000) + msg = messaging.new_message('clocks', valid=True) + msg.clocks.wallTimeNanos = time.time_ns() + pm.send('clocks', msg) + llk = sm['liveLocationKalman'] if not llk.gpsOK or (time.monotonic() - sm.logMonoTime['liveLocationKalman']/1e9) > 0.2: continue diff --git a/system/ubloxd/tests/ubloxd.py b/system/ubloxd/tests/ubloxd.py index 4ee99dc28a..c17387114f 100755 --- a/system/ubloxd/tests/ubloxd.py +++ b/system/ubloxd/tests/ubloxd.py @@ -82,7 +82,7 @@ def configure_ublox(dev): if __name__ == "__main__": class Device: def write(self, s): - d = '"{}"s'.format(''.join('\\x{:02X}'.format(b) for b in s)) + d = '"{}"s'.format(''.join(f'\\x{b:02X}' for b in s)) print(f" if (!send_with_ack({d})) continue;") dev = ublox.UBlox(Device(), baudrate=baudrate) diff --git a/system/version.py b/system/version.py index 6bcae5f3fa..4319ef2140 100755 --- a/system/version.py +++ b/system/version.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 import os import subprocess -from typing import List, Optional +from typing import TypeVar +from collections.abc import Callable from functools import lru_cache from openpilot.common.basedir import BASEDIR @@ -13,16 +14,16 @@ TESTED_BRANCHES = RELEASE_BRANCHES + ['devel', 'devel-staging'] training_version: bytes = b"0.2.0" terms_version: bytes = b"2" - -def cache(user_function, /): +_RT = TypeVar("_RT") +def cache(user_function: Callable[..., _RT], /) -> Callable[..., _RT]: return lru_cache(maxsize=None)(user_function) -def run_cmd(cmd: List[str]) -> str: +def run_cmd(cmd: list[str]) -> str: return subprocess.check_output(cmd, encoding='utf8').strip() -def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[str]: +def run_cmd_default(cmd: list[str], default: str = "") -> str: try: return run_cmd(cmd) except subprocess.CalledProcessError: @@ -30,41 +31,42 @@ def run_cmd_default(cmd: List[str], default: Optional[str] = None) -> Optional[s @cache -def get_commit(branch: str = "HEAD", default: Optional[str] = None) -> Optional[str]: - return run_cmd_default(["git", "rev-parse", branch], default=default) +def get_commit(branch: str = "HEAD") -> str: + return run_cmd_default(["git", "rev-parse", branch]) @cache -def get_short_branch(default: Optional[str] = None) -> Optional[str]: - return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"], default=default) +def get_commit_date(commit: str = "HEAD") -> str: + return run_cmd_default(["git", "show", "--no-patch", "--format='%ct %ci'", commit]) @cache -def get_branch(default: Optional[str] = None) -> Optional[str]: - return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"], default=default) +def get_short_branch() -> str: + return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "HEAD"]) @cache -def get_origin(default: Optional[str] = None) -> Optional[str]: +def get_branch() -> str: + return run_cmd_default(["git", "rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"]) + + +@cache +def get_origin() -> str: try: local_branch = run_cmd(["git", "name-rev", "--name-only", "HEAD"]) tracking_remote = run_cmd(["git", "config", "branch." + local_branch + ".remote"]) return run_cmd(["git", "config", "remote." + tracking_remote + ".url"]) except subprocess.CalledProcessError: # Not on a branch, fallback - return run_cmd_default(["git", "config", "--get", "remote.origin.url"], default=default) + return run_cmd_default(["git", "config", "--get", "remote.origin.url"]) @cache -def get_normalized_origin(default: Optional[str] = None) -> Optional[str]: - origin: Optional[str] = get_origin() - - if origin is None: - return default - - return origin.replace("git@", "", 1) \ - .replace(".git", "", 1) \ - .replace("https://", "", 1) \ - .replace(":", "/", 1) +def get_normalized_origin() -> str: + return get_origin() \ + .replace("git@", "", 1) \ + .replace(".git", "", 1) \ + .replace("https://", "", 1) \ + .replace(":", "/", 1) @cache @@ -75,7 +77,7 @@ def get_version() -> str: @cache def get_short_version() -> str: - return get_version().split('-')[0] # type: ignore + return get_version().split('-')[0] @cache def is_prebuilt() -> bool: @@ -86,12 +88,7 @@ def is_prebuilt() -> bool: def is_comma_remote() -> bool: # note to fork maintainers, this is used for release metrics. please do not # touch this to get rid of the orange startup alert. there's better ways to do that - origin: Optional[str] = get_origin() - if origin is None: - return False - - return origin.startswith(('git@github.com:commaai', 'https://github.com/commaai')) - + return get_normalized_origin() == "github.com/commaai/openpilot" @cache def is_tested_branch() -> bool: @@ -105,7 +102,7 @@ def is_release_branch() -> bool: def is_dirty() -> bool: origin = get_origin() branch = get_branch() - if (origin is None) or (branch is None): + if not origin or not branch: return True dirty = False @@ -141,3 +138,4 @@ if __name__ == "__main__": print(f"Branch: {get_branch()}") print(f"Short branch: {get_short_branch()}") print(f"Prebuilt: {is_prebuilt()}") + print(f"Commit date: {get_commit_date()}") diff --git a/system/webrtc/device/audio.py b/system/webrtc/device/audio.py index 3c78be6752..4b22033e03 100644 --- a/system/webrtc/device/audio.py +++ b/system/webrtc/device/audio.py @@ -1,6 +1,5 @@ import asyncio import io -from typing import Optional, List, Tuple import aiortc import av @@ -17,7 +16,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): pyaudio.paFloat32: 'flt', } - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: Optional[int] = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 16000, channels: int = 1, packet_time: float = 0.020, device_index: int = None): super().__init__() self.p = pyaudio.PyAudio() @@ -49,7 +48,7 @@ class AudioInputStreamTrack(aiortc.mediastreams.AudioStreamTrack): class AudioOutputSpeaker: - def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: Optional[int] = None): + def __init__(self, audio_format: int = pyaudio.paInt16, rate: int = 48000, channels: int = 2, packet_time: float = 0.2, device_index: int = None): chunk_size = int(packet_time * rate) self.p = pyaudio.PyAudio() @@ -62,7 +61,7 @@ class AudioOutputSpeaker: output=True, output_device_index=device_index, stream_callback=self.__pyaudio_callback) - self.tracks_and_tasks: List[Tuple[aiortc.MediaStreamTrack, Optional[asyncio.Task]]] = [] + self.tracks_and_tasks: list[tuple[aiortc.MediaStreamTrack, asyncio.Task | None]] = [] def __pyaudio_callback(self, in_data, frame_count, time_info, status): if self.buffer.getbuffer().nbytes < frame_count * self.channels * 2: diff --git a/system/webrtc/device/video.py b/system/webrtc/device/video.py index 1ecb6dbd74..1bca909294 100644 --- a/system/webrtc/device/video.py +++ b/system/webrtc/device/video.py @@ -1,11 +1,9 @@ import asyncio -from typing import Optional import av from teleoprtc.tracks import TiciVideoStreamTrack from cereal import messaging -from openpilot.tools.lib.framereader import FrameReader from openpilot.common.realtime import DT_MDL, DT_DMON @@ -41,29 +39,5 @@ class LiveStreamVideoStreamTrack(TiciVideoStreamTrack): return packet - def codec_preference(self) -> Optional[str]: + def codec_preference(self) -> str | None: return "H264" - - -class FrameReaderVideoStreamTrack(TiciVideoStreamTrack): - def __init__(self, input_file: str, dt: float = DT_MDL, camera_type: str = "driver"): - super().__init__(camera_type, dt) - - frame_reader = FrameReader(input_file) - self._frames = [frame_reader.get(i, pix_fmt="rgb24") for i in range(frame_reader.frame_count)] - self._frame_count = len(self.frames) - self._frame_index = 0 - self._pts = 0 - - async def recv(self): - self.log_debug("track sending frame %s", self._pts) - img = self._frames[self._frame_index] - - new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") - new_frame.pts = self._pts - new_frame.time_base = self._time_base - - self._frame_index = (self._frame_index + 1) % self._frame_count - self._pts = await self.next_pts(self._pts) - - return new_frame diff --git a/system/webrtc/schema.py b/system/webrtc/schema.py index f659b34293..d80986ebf2 100644 --- a/system/webrtc/schema.py +++ b/system/webrtc/schema.py @@ -1,8 +1,8 @@ import capnp -from typing import Union, List, Dict, Any +from typing import Any -def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, Any]]: +def generate_type(type_walker, schema_walker) -> str | list[Any] | dict[str, Any]: data_type = next(type_walker) if data_type.which() == 'struct': return generate_struct(next(schema_walker)) @@ -15,11 +15,11 @@ def generate_type(type_walker, schema_walker) -> Union[str, List[Any], Dict[str, return str(data_type.which()) -def generate_struct(schema: capnp.lib.capnp._StructSchema) -> Dict[str, Any]: +def generate_struct(schema: capnp.lib.capnp._StructSchema) -> dict[str, Any]: return {field: generate_field(schema.fields[field]) for field in schema.fields if not field.endswith("DEPRECATED")} -def generate_field(field: capnp.lib.capnp._StructSchemaField) -> Union[str, List[Any], Dict[str, Any]]: +def generate_field(field: capnp.lib.capnp._StructSchemaField) -> str | list[Any] | dict[str, Any]: def schema_walker(field): yield field.schema diff --git a/system/webrtc/tests/test_webrtcd.py b/system/webrtc/tests/test_webrtcd.py index b48bf6bc19..c31b63d02b 100755 --- a/system/webrtc/tests/test_webrtcd.py +++ b/system/webrtc/tests/test_webrtcd.py @@ -18,13 +18,13 @@ class TestWebrtcdProc(unittest.IsolatedAsyncioTestCase): try: async with asyncio.timeout(timeout): await awaitable - except asyncio.TimeoutError: + except TimeoutError: self.fail("Timeout while waiting for awaitable to complete") async def test_webrtcd(self): mock_request = MagicMock() async def connect(offer): - body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': [], 'bridge_services_out': []} + body = {'sdp': offer.sdp, 'cameras': offer.video, 'bridge_services_in': [], 'bridge_services_out': ['carState']} mock_request.json.side_effect = AsyncMock(return_value=body) response = await get_stream(mock_request) response_json = json.loads(response.text) diff --git a/system/webrtc/webrtcd.py b/system/webrtc/webrtcd.py index 12f9328532..6c1370eae9 100755 --- a/system/webrtc/webrtcd.py +++ b/system/webrtc/webrtcd.py @@ -6,34 +6,27 @@ import json import uuid import logging from dataclasses import dataclass, field -from typing import Any, List, Optional, Union +from typing import Any, TYPE_CHECKING # aiortc and its dependencies have lots of internal warnings :( import warnings warnings.filterwarnings("ignore", category=DeprecationWarning) -import aiortc -from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack -from aiortc.contrib.media import MediaBlackhole -from aiortc.exceptions import InvalidStateError -from aiohttp import web import capnp -from teleoprtc import WebRTCAnswerBuilder -from teleoprtc.info import parse_info_from_offer +from aiohttp import web +if TYPE_CHECKING: + from aiortc.rtcdatachannel import RTCDataChannel -from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack -from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker from openpilot.system.webrtc.schema import generate_field - from cereal import messaging, log class CerealOutgoingMessageProxy: def __init__(self, sm: messaging.SubMaster): self.sm = sm - self.channels: List[aiortc.RTCDataChannel] = [] + self.channels: list['RTCDataChannel'] = [] - def add_channel(self, channel: aiortc.RTCDataChannel): + def add_channel(self, channel: 'RTCDataChannel'): self.channels.append(channel) def to_json(self, msg_content: Any): @@ -96,6 +89,8 @@ class CerealProxyRunner: self.task = None async def run(self): + from aiortc.exceptions import InvalidStateError + while True: try: self.proxy.update() @@ -108,7 +103,14 @@ class CerealProxyRunner: class StreamSession: - def __init__(self, sdp: str, cameras: List[str], incoming_services: List[str], outgoing_services: List[str], debug_mode: bool = False): + def __init__(self, sdp: str, cameras: list[str], incoming_services: list[str], outgoing_services: list[str], debug_mode: bool = False): + from aiortc.mediastreams import VideoStreamTrack, AudioStreamTrack + from aiortc.contrib.media import MediaBlackhole + from openpilot.system.webrtc.device.video import LiveStreamVideoStreamTrack + from openpilot.system.webrtc.device.audio import AudioInputStreamTrack, AudioOutputSpeaker + from teleoprtc import WebRTCAnswerBuilder + from teleoprtc.info import parse_info_from_offer + config = parse_info_from_offer(sdp) builder = WebRTCAnswerBuilder(sdp) @@ -130,8 +132,8 @@ class StreamSession: self.incoming_bridge = CerealIncomingMessageProxy(messaging.PubMaster(incoming_services)) self.outgoing_bridge_runner = CerealProxyRunner(self.outgoing_bridge) - self.audio_output: Optional[Union[AudioOutputSpeaker, MediaBlackhole]] = None - self.run_task: Optional[asyncio.Task] = None + self.audio_output: AudioOutputSpeaker | MediaBlackhole | None = None + self.run_task: asyncio.Task | None = None self.logger = logging.getLogger("webrtcd") self.logger.info("New stream session (%s), cameras %s, audio in %s out %s, incoming services %s, outgoing services %s", self.identifier, cameras, config.incoming_audio_track, config.expected_audio_track, incoming_services, outgoing_services) @@ -187,12 +189,12 @@ class StreamSession: @dataclass class StreamRequestBody: sdp: str - cameras: List[str] - bridge_services_in: List[str] = field(default_factory=list) - bridge_services_out: List[str] = field(default_factory=list) + cameras: list[str] + bridge_services_in: list[str] = field(default_factory=list) + bridge_services_out: list[str] = field(default_factory=list) -async def get_stream(request: web.Request): +async def get_stream(request: 'web.Request'): stream_dict, debug_mode = request.app['streams'], request.app['debug'] raw_body = await request.json() body = StreamRequestBody(**raw_body) @@ -206,7 +208,7 @@ async def get_stream(request: web.Request): return web.json_response({"sdp": answer.sdp, "type": answer.type}) -async def get_schema(request: web.Request): +async def get_schema(request: 'web.Request'): services = request.query["services"].split(",") services = [s for s in services if s] assert all(s in log.Event.schema.fields and not s.endswith("DEPRECATED") for s in services), "Invalid service name" @@ -214,7 +216,7 @@ async def get_schema(request: web.Request): return web.json_response(schema_dict) -async def on_shutdown(app: web.Application): +async def on_shutdown(app: 'web.Application'): for session in app['streams'].values(): session.stop() del app['streams'] diff --git a/third_party/mapbox-gl-native-qt/.gitattributes b/third_party/mapbox-gl-native-qt/.gitattributes deleted file mode 100644 index 323c737ba1..0000000000 --- a/third_party/mapbox-gl-native-qt/.gitattributes +++ /dev/null @@ -1,2 +0,0 @@ -x86_64 filter=lfs diff=lfs merge=lfs -text -larch64 filter=lfs diff=lfs merge=lfs -text diff --git a/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so b/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so deleted file mode 100755 index 508463c141..0000000000 --- a/third_party/mapbox-gl-native-qt/aarch64/libqmapboxgl.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7fb33cb09b184a689eaa7f78c839d12a5bdd5b6f576d55758ec7e1246187a53f -size 9300680 diff --git a/third_party/mapbox-gl-native-qt/build.sh b/third_party/mapbox-gl-native-qt/build.sh deleted file mode 100755 index f2936fad3d..0000000000 --- a/third_party/mapbox-gl-native-qt/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env sh -cd /tmp -git clone --recursive https://github.com/commaai/mapbox-gl-native.git -cd mapbox-gl-native -mkdir build && cd build -cmake -DMBGL_WITH_QT=ON .. -make -j$(nproc) mbgl-qt diff --git a/third_party/mapbox-gl-native-qt/include/QMapbox b/third_party/mapbox-gl-native-qt/include/QMapbox deleted file mode 100644 index a8479c09aa..0000000000 --- a/third_party/mapbox-gl-native-qt/include/QMapbox +++ /dev/null @@ -1 +0,0 @@ -#include "qmapbox.hpp" diff --git a/third_party/mapbox-gl-native-qt/include/QMapboxGL b/third_party/mapbox-gl-native-qt/include/QMapboxGL deleted file mode 100644 index 15b55a9abe..0000000000 --- a/third_party/mapbox-gl-native-qt/include/QMapboxGL +++ /dev/null @@ -1 +0,0 @@ -#include "qmapboxgl.hpp" diff --git a/third_party/mapbox-gl-native-qt/include/qmapbox.hpp b/third_party/mapbox-gl-native-qt/include/qmapbox.hpp deleted file mode 100644 index 3acc9d55e0..0000000000 --- a/third_party/mapbox-gl-native-qt/include/qmapbox.hpp +++ /dev/null @@ -1,147 +0,0 @@ -#ifndef QMAPBOX_H -#define QMAPBOX_H - -#include -#include -#include -#include -#include - -// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style - -#if !defined(QT_MAPBOXGL_STATIC) -# if defined(QT_BUILD_MAPBOXGL_LIB) -# define Q_MAPBOXGL_EXPORT Q_DECL_EXPORT -# else -# define Q_MAPBOXGL_EXPORT Q_DECL_IMPORT -# endif -#else -# define Q_MAPBOXGL_EXPORT -#endif - -namespace QMapbox { - -typedef QPair Coordinate; -typedef QPair CoordinateZoom; -typedef QPair ProjectedMeters; - -typedef QVector Coordinates; -typedef QVector CoordinatesCollection; - -typedef QVector CoordinatesCollections; - -struct Q_MAPBOXGL_EXPORT Feature { - enum Type { - PointType = 1, - LineStringType, - PolygonType - }; - - /*! Class constructor. */ - Feature(Type type_ = PointType, const CoordinatesCollections& geometry_ = CoordinatesCollections(), - const QVariantMap& properties_ = QVariantMap(), const QVariant& id_ = QVariant()) - : type(type_), geometry(geometry_), properties(properties_), id(id_) {} - - Type type; - CoordinatesCollections geometry; - QVariantMap properties; - QVariant id; -}; - -struct Q_MAPBOXGL_EXPORT ShapeAnnotationGeometry { - enum Type { - LineStringType = 1, - PolygonType, - MultiLineStringType, - MultiPolygonType - }; - - /*! Class constructor. */ - ShapeAnnotationGeometry(Type type_ = LineStringType, const CoordinatesCollections& geometry_ = CoordinatesCollections()) - : type(type_), geometry(geometry_) {} - - Type type; - CoordinatesCollections geometry; -}; - -struct Q_MAPBOXGL_EXPORT SymbolAnnotation { - Coordinate geometry; - QString icon; -}; - -struct Q_MAPBOXGL_EXPORT LineAnnotation { - /*! Class constructor. */ - LineAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, - float width_ = 1.0f, const QColor& color_ = Qt::black) - : geometry(geometry_), opacity(opacity_), width(width_), color(color_) {} - - ShapeAnnotationGeometry geometry; - float opacity; - float width; - QColor color; -}; - -struct Q_MAPBOXGL_EXPORT FillAnnotation { - /*! Class constructor. */ - FillAnnotation(const ShapeAnnotationGeometry& geometry_ = ShapeAnnotationGeometry(), float opacity_ = 1.0f, - const QColor& color_ = Qt::black, const QVariant& outlineColor_ = QVariant()) - : geometry(geometry_), opacity(opacity_), color(color_), outlineColor(outlineColor_) {} - - ShapeAnnotationGeometry geometry; - float opacity; - QColor color; - QVariant outlineColor; -}; - -typedef QVariant Annotation; -typedef quint32 AnnotationID; -typedef QVector AnnotationIDs; - -enum NetworkMode { - Online, // Default - Offline, -}; - -Q_MAPBOXGL_EXPORT QVector >& defaultStyles(); - -Q_MAPBOXGL_EXPORT NetworkMode networkMode(); -Q_MAPBOXGL_EXPORT void setNetworkMode(NetworkMode); - -// This struct is a 1:1 copy of mbgl::CustomLayerRenderParameters. -struct Q_MAPBOXGL_EXPORT CustomLayerRenderParameters { - double width; - double height; - double latitude; - double longitude; - double zoom; - double bearing; - double pitch; - double fieldOfView; -}; - -class Q_MAPBOXGL_EXPORT CustomLayerHostInterface { -public: - virtual ~CustomLayerHostInterface() = default; - virtual void initialize() = 0; - virtual void render(const CustomLayerRenderParameters&) = 0; - virtual void deinitialize() = 0; -}; - -Q_MAPBOXGL_EXPORT double metersPerPixelAtLatitude(double latitude, double zoom); -Q_MAPBOXGL_EXPORT ProjectedMeters projectedMetersForCoordinate(const Coordinate &); -Q_MAPBOXGL_EXPORT Coordinate coordinateForProjectedMeters(const ProjectedMeters &); - -} // namespace QMapbox - -Q_DECLARE_METATYPE(QMapbox::Coordinate); -Q_DECLARE_METATYPE(QMapbox::Coordinates); -Q_DECLARE_METATYPE(QMapbox::CoordinatesCollection); -Q_DECLARE_METATYPE(QMapbox::CoordinatesCollections); -Q_DECLARE_METATYPE(QMapbox::Feature); - -Q_DECLARE_METATYPE(QMapbox::SymbolAnnotation); -Q_DECLARE_METATYPE(QMapbox::ShapeAnnotationGeometry); -Q_DECLARE_METATYPE(QMapbox::LineAnnotation); -Q_DECLARE_METATYPE(QMapbox::FillAnnotation); - -#endif // QMAPBOX_H diff --git a/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp b/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp deleted file mode 100644 index 337991aa1c..0000000000 --- a/third_party/mapbox-gl-native-qt/include/qmapboxgl.hpp +++ /dev/null @@ -1,277 +0,0 @@ -#ifndef QMAPBOXGL_H -#define QMAPBOXGL_H - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -class QMapboxGLPrivate; - -// This header follows the Qt coding style: https://wiki.qt.io/Qt_Coding_Style - -class Q_MAPBOXGL_EXPORT QMapboxGLSettings -{ -public: - QMapboxGLSettings(); - - enum GLContextMode { - UniqueGLContext = 0, - SharedGLContext - }; - - enum MapMode { - Continuous = 0, - Static - }; - - enum ConstrainMode { - NoConstrain = 0, - ConstrainHeightOnly, - ConstrainWidthAndHeight - }; - - enum ViewportMode { - DefaultViewport = 0, - FlippedYViewport - }; - - GLContextMode contextMode() const; - void setContextMode(GLContextMode); - - MapMode mapMode() const; - void setMapMode(MapMode); - - ConstrainMode constrainMode() const; - void setConstrainMode(ConstrainMode); - - ViewportMode viewportMode() const; - void setViewportMode(ViewportMode); - - unsigned cacheDatabaseMaximumSize() const; - void setCacheDatabaseMaximumSize(unsigned); - - QString cacheDatabasePath() const; - void setCacheDatabasePath(const QString &); - - QString assetPath() const; - void setAssetPath(const QString &); - - QString accessToken() const; - void setAccessToken(const QString &); - - QString apiBaseUrl() const; - void setApiBaseUrl(const QString &); - - QString localFontFamily() const; - void setLocalFontFamily(const QString &); - - std::function resourceTransform() const; - void setResourceTransform(const std::function &); - -private: - GLContextMode m_contextMode; - MapMode m_mapMode; - ConstrainMode m_constrainMode; - ViewportMode m_viewportMode; - - unsigned m_cacheMaximumSize; - QString m_cacheDatabasePath; - QString m_assetPath; - QString m_accessToken; - QString m_apiBaseUrl; - QString m_localFontFamily; - std::function m_resourceTransform; -}; - -struct Q_MAPBOXGL_EXPORT QMapboxGLCameraOptions { - QVariant center; // Coordinate - QVariant anchor; // QPointF - QVariant zoom; // double - QVariant bearing; // double - QVariant pitch; // double -}; - -class Q_MAPBOXGL_EXPORT QMapboxGL : public QObject -{ - Q_OBJECT - Q_PROPERTY(double latitude READ latitude WRITE setLatitude) - Q_PROPERTY(double longitude READ longitude WRITE setLongitude) - Q_PROPERTY(double zoom READ zoom WRITE setZoom) - Q_PROPERTY(double bearing READ bearing WRITE setBearing) - Q_PROPERTY(double pitch READ pitch WRITE setPitch) - Q_PROPERTY(QString styleJson READ styleJson WRITE setStyleJson) - Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl) - Q_PROPERTY(double scale READ scale WRITE setScale) - Q_PROPERTY(QMapbox::Coordinate coordinate READ coordinate WRITE setCoordinate) - Q_PROPERTY(QMargins margins READ margins WRITE setMargins) - -public: - enum MapChange { - MapChangeRegionWillChange = 0, - MapChangeRegionWillChangeAnimated, - MapChangeRegionIsChanging, - MapChangeRegionDidChange, - MapChangeRegionDidChangeAnimated, - MapChangeWillStartLoadingMap, - MapChangeDidFinishLoadingMap, - MapChangeDidFailLoadingMap, - MapChangeWillStartRenderingFrame, - MapChangeDidFinishRenderingFrame, - MapChangeDidFinishRenderingFrameFullyRendered, - MapChangeWillStartRenderingMap, - MapChangeDidFinishRenderingMap, - MapChangeDidFinishRenderingMapFullyRendered, - MapChangeDidFinishLoadingStyle, - MapChangeSourceDidChange - }; - - enum MapLoadingFailure { - StyleParseFailure, - StyleLoadFailure, - NotFoundFailure, - UnknownFailure - }; - - // Determines the orientation of the map. - enum NorthOrientation { - NorthUpwards, // Default - NorthRightwards, - NorthDownwards, - NorthLeftwards, - }; - - QMapboxGL(QObject* parent = 0, - const QMapboxGLSettings& = QMapboxGLSettings(), - const QSize& size = QSize(), - qreal pixelRatio = 1); - virtual ~QMapboxGL(); - - QString styleJson() const; - QString styleUrl() const; - - void setStyleJson(const QString &); - void setStyleUrl(const QString &); - - double latitude() const; - void setLatitude(double latitude); - - double longitude() const; - void setLongitude(double longitude); - - double scale() const; - void setScale(double scale, const QPointF ¢er = QPointF()); - - double zoom() const; - void setZoom(double zoom); - - double minimumZoom() const; - double maximumZoom() const; - - double bearing() const; - void setBearing(double degrees); - void setBearing(double degrees, const QPointF ¢er); - - double pitch() const; - void setPitch(double pitch); - void pitchBy(double pitch); - - NorthOrientation northOrientation() const; - void setNorthOrientation(NorthOrientation); - - QMapbox::Coordinate coordinate() const; - void setCoordinate(const QMapbox::Coordinate &); - void setCoordinateZoom(const QMapbox::Coordinate &, double zoom); - - void jumpTo(const QMapboxGLCameraOptions&); - - void setGestureInProgress(bool inProgress); - - void setTransitionOptions(qint64 duration, qint64 delay = 0); - - void addAnnotationIcon(const QString &name, const QImage &sprite); - - QMapbox::AnnotationID addAnnotation(const QMapbox::Annotation &); - void updateAnnotation(QMapbox::AnnotationID, const QMapbox::Annotation &); - void removeAnnotation(QMapbox::AnnotationID); - - bool setLayoutProperty(const QString &layer, const QString &property, const QVariant &value); - bool setPaintProperty(const QString &layer, const QString &property, const QVariant &value); - - bool isFullyLoaded() const; - - void moveBy(const QPointF &offset); - void scaleBy(double scale, const QPointF ¢er = QPointF()); - void rotateBy(const QPointF &first, const QPointF &second); - - void resize(const QSize &size); - - double metersPerPixelAtLatitude(double latitude, double zoom) const; - QMapbox::ProjectedMeters projectedMetersForCoordinate(const QMapbox::Coordinate &) const; - QMapbox::Coordinate coordinateForProjectedMeters(const QMapbox::ProjectedMeters &) const; - QPointF pixelForCoordinate(const QMapbox::Coordinate &) const; - QMapbox::Coordinate coordinateForPixel(const QPointF &) const; - - QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne) const; - QMapbox::CoordinateZoom coordinateZoomForBounds(const QMapbox::Coordinate &sw, QMapbox::Coordinate &ne, double bearing, double pitch); - - void setMargins(const QMargins &margins); - QMargins margins() const; - - void addSource(const QString &sourceID, const QVariantMap& params); - bool sourceExists(const QString &sourceID); - void updateSource(const QString &sourceID, const QVariantMap& params); - void removeSource(const QString &sourceID); - - void addImage(const QString &name, const QImage &sprite); - void removeImage(const QString &name); - - void addCustomLayer(const QString &id, - QScopedPointer& host, - const QString& before = QString()); - void addLayer(const QVariantMap ¶ms, const QString& before = QString()); - bool layerExists(const QString &id); - void removeLayer(const QString &id); - - QVector layerIds() const; - - void setFilter(const QString &layer, const QVariant &filter); - QVariant getFilter(const QString &layer) const; - // When rendering on a different thread, - // should be called on the render thread. - void createRenderer(); - void destroyRenderer(); - void setFramebufferObject(quint32 fbo, const QSize &size); - -public slots: - void render(); - void connectionEstablished(); - - // Commit changes, load all the resources - // and renders the map when completed. - void startStaticRender(); - -signals: - void needsRendering(); - void mapChanged(QMapboxGL::MapChange); - void mapLoadingFailed(QMapboxGL::MapLoadingFailure, const QString &reason); - void copyrightsChanged(const QString ©rightsHtml); - - void staticRenderFinished(const QString &error); - -private: - Q_DISABLE_COPY(QMapboxGL) - - QMapboxGLPrivate *d_ptr; -}; - -Q_DECLARE_METATYPE(QMapboxGL::MapChange); -Q_DECLARE_METATYPE(QMapboxGL::MapLoadingFailure); - -#endif // QMAPBOXGL_H diff --git a/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so b/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so deleted file mode 100755 index a61c80c63d..0000000000 --- a/third_party/mapbox-gl-native-qt/x86_64/libqmapboxgl.so +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ee37b571a5a50d07f2fd1a3150aa2842f10576e96e01278bbc060815549d57e9 -size 10219704 diff --git a/third_party/maplibre-native-qt/.gitignore b/third_party/maplibre-native-qt/.gitignore new file mode 100644 index 0000000000..9adc6681c0 --- /dev/null +++ b/third_party/maplibre-native-qt/.gitignore @@ -0,0 +1 @@ +/maplibre/ diff --git a/third_party/maplibre-native-qt/aarch64 b/third_party/maplibre-native-qt/aarch64 new file mode 120000 index 0000000000..062c65e8d9 --- /dev/null +++ b/third_party/maplibre-native-qt/aarch64 @@ -0,0 +1 @@ +larch64/ \ No newline at end of file diff --git a/third_party/maplibre-native-qt/build.sh b/third_party/maplibre-native-qt/build.sh new file mode 100755 index 0000000000..c64f0fc649 --- /dev/null +++ b/third_party/maplibre-native-qt/build.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +set -e + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null && pwd)" + +ARCHNAME="x86_64" +MAPLIBRE_FLAGS="-DMLN_QT_WITH_LOCATION=OFF" +if [ -f /AGNOS ]; then + ARCHNAME="larch64" + #MAPLIBRE_FLAGS="$MAPLIBRE_FLAGS -DCMAKE_SYSTEM_NAME=Android -DANDROID_ABI=arm64-v8a" +fi + +cd $DIR +if [ ! -d maplibre ]; then + git clone git@github.com:maplibre/maplibre-native-qt.git $DIR/maplibre +fi + +cd maplibre +git fetch --all +git checkout 3726266e127c1f94ad64837c9dbe03d238255816 +git submodule update --depth=1 --recursive --init + +# build +mkdir -p build +cd build +set -x +cmake $MAPLIBRE_FLAGS $DIR/maplibre +make -j$(nproc) || make -j2 || make -j1 + +INSTALL_DIR="$DIR/$ARCHNAME" +rm -rf $INSTALL_DIR +mkdir -p $INSTALL_DIR + +rm -rf $INSTALL_DIR/lib $DIR/include +mkdir -p $INSTALL_DIR/lib $INSTALL_DIR/include $DIR/include +cp -r $DIR/maplibre/build/src/core/*.so* $INSTALL_DIR/lib +cp -r $DIR/maplibre/build/src/core/include/* $INSTALL_DIR/include +cp -r $DIR/maplibre/src/**/*.hpp $DIR/include diff --git a/third_party/maplibre-native-qt/include/conversion_p.hpp b/third_party/maplibre-native-qt/include/conversion_p.hpp new file mode 100644 index 0000000000..38b03d498e --- /dev/null +++ b/third_party/maplibre-native-qt/include/conversion_p.hpp @@ -0,0 +1,241 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2018 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "geojson_p.hpp" +#include "types.hpp" + +#include +#include + +#include +#include + +#include + +namespace mbgl::style::conversion { + +std::string convertColor(const QColor &color); + +template <> +class ConversionTraits { +public: + static bool isUndefined(const QVariant &value) { return value.isNull() || !value.isValid(); } + + static bool isArray(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::QVariantList)); +#else + return value.canConvert(QVariant::List); +#endif + } + + static std::size_t arrayLength(const QVariant &value) { return value.toList().size(); } + + static QVariant arrayMember(const QVariant &value, std::size_t i) { return value.toList()[static_cast(i)]; } + + static bool isObject(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + return QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::QVariantMap)) || + value.typeId() == QMetaType::QByteArray +#else + return value.canConvert(QVariant::Map) || value.type() == QVariant::ByteArray +#endif + || QString(value.typeName()) == QStringLiteral("QMapLibre::Feature") || + value.userType() == qMetaTypeId>() || + value.userType() == qMetaTypeId>() || + value.userType() == qMetaTypeId>(); + } + + static std::optional objectMember(const QVariant &value, const char *key) { + auto map = value.toMap(); + auto iter = map.constFind(key); + + if (iter != map.constEnd()) { + return iter.value(); + } + + return {}; + } + + template + static std::optional eachMember(const QVariant &value, Fn &&fn) { + auto map = value.toMap(); + auto iter = map.constBegin(); + + while (iter != map.constEnd()) { + std::optional result = fn(iter.key().toStdString(), QVariant(iter.value())); + if (result) { + return result; + } + + ++iter; + } + + return {}; + } + + static std::optional toBool(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Bool) { +#else + if (value.type() == QVariant::Bool) { +#endif + return value.toBool(); + } + + return {}; + } + + static std::optional toNumber(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Int || value.typeId() == QMetaType::Double || + value.typeId() == QMetaType::Long || value.typeId() == QMetaType::LongLong || + value.typeId() == QMetaType::ULong || value.typeId() == QMetaType::ULongLong) { +#else + if (value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || + value.type() == QVariant::ULongLong) { +#endif + return value.toFloat(); + } + + return {}; + } + + static std::optional toDouble(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Int || value.typeId() == QMetaType::Double || + value.typeId() == QMetaType::Long || value.typeId() == QMetaType::LongLong || + value.typeId() == QMetaType::ULong || value.typeId() == QMetaType::ULongLong) { +#else + if (value.type() == QVariant::Int || value.type() == QVariant::Double || value.type() == QVariant::LongLong || + value.type() == QVariant::ULongLong) { +#endif + return value.toDouble(); + } + + return {}; + } + + static std::optional toString(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::QString) { + return value.toString().toStdString(); + } + + if (value.typeId() == QMetaType::QColor) { + return convertColor(value.value()); + } +#else + if (value.type() == QVariant::String) { + return value.toString().toStdString(); + } + + if (value.type() == QVariant::Color) { + return convertColor(value.value()); + } +#endif + return {}; + } + + static std::optional toValue(const QVariant &value) { +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() == QMetaType::Bool) { + return {value.toBool()}; + } + + if (value.typeId() == QMetaType::QString) { + return {value.toString().toStdString()}; + } + + if (value.typeId() == QMetaType::QColor) { + return {convertColor(value.value())}; + } + + if (value.typeId() == QMetaType::Int) { + return {static_cast(value.toInt())}; + } + + if (QMetaType::canConvert(value.metaType(), QMetaType(QMetaType::Double))) { + return {value.toDouble()}; + } +#else + if (value.type() == QVariant::Bool) { + return {value.toBool()}; + } + + if (value.type() == QVariant::String) { + return {value.toString().toStdString()}; + } + + if (value.type() == QVariant::Color) { + return {convertColor(value.value())}; + } + + if (value.type() == QVariant::Int) { + return {static_cast(value.toInt())}; + } + + if (value.canConvert(QVariant::Double)) { + return {value.toDouble()}; + } +#endif + return {}; + } + + static std::optional toGeoJSON(const QVariant &value, Error &error) { + if (value.typeName() == QStringLiteral("QMapLibre::Feature")) { + return GeoJSON{QMapLibre::GeoJSON::asFeature(value.value())}; + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + + if (value.userType() == qMetaTypeId>()) { + return featureCollectionToGeoJSON(value.value>()); + } + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + if (value.typeId() != QMetaType::QByteArray) { +#else + if (value.type() != QVariant::ByteArray) { +#endif + error = {"JSON data must be in QByteArray"}; + return {}; + } + + const QByteArray data = value.toByteArray(); + return parseGeoJSON(std::string(data.constData(), data.size()), error); + } + +private: + template + static GeoJSON featureCollectionToGeoJSON(const T &features) { + mapbox::feature::feature_collection collection; + collection.reserve(static_cast(features.size())); + for (const auto &feature : features) { + collection.push_back(QMapLibre::GeoJSON::asFeature(feature)); + } + return GeoJSON{std::move(collection)}; + } +}; + +template +std::optional convert(const QVariant &value, Error &error, Args &&...args) { + return convert(Convertible(value), error, std::forward(args)...); +} + +inline std::string convertColor(const QColor &color) { + return QString::asprintf("rgba(%d,%d,%d,%lf)", color.red(), color.green(), color.blue(), color.alphaF()) + .toStdString(); +} + +} // namespace mbgl::style::conversion diff --git a/third_party/maplibre-native-qt/include/export_core.hpp b/third_party/maplibre-native-qt/include/export_core.hpp new file mode 100644 index 0000000000..bd5ad495db --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_core.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_CORE_EXPORT_H +#define QMAPLIBRE_CORE_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_CORE_LIB) +#define Q_MAPLIBRE_CORE_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_CORE_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_CORE_EXPORT +#endif + +#endif // QMAPLIBRE_CORE_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/export_location.hpp b/third_party/maplibre-native-qt/include/export_location.hpp new file mode 100644 index 0000000000..a986346884 --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_location.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_LOCATION_EXPORT_H +#define QMAPLIBRE_LOCATION_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_LOCATION_LIB) +#define Q_MAPLIBRE_LOCATION_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_LOCATION_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_LOCATION_EXPORT +#endif + +#endif // QMAPLIBRE_LOCATION_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/export_widgets.hpp b/third_party/maplibre-native-qt/include/export_widgets.hpp new file mode 100644 index 0000000000..11bc288190 --- /dev/null +++ b/third_party/maplibre-native-qt/include/export_widgets.hpp @@ -0,0 +1,20 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_WIDGETS_EXPORT_H +#define QMAPLIBRE_WIDGETS_EXPORT_H + +#include + +#if !defined(QT_MAPLIBRE_STATIC) +#if defined(QT_BUILD_MAPLIBRE_WIDGETS_LIB) +#define Q_MAPLIBRE_WIDGETS_EXPORT Q_DECL_EXPORT +#else +#define Q_MAPLIBRE_WIDGETS_EXPORT Q_DECL_IMPORT +#endif +#else +#define Q_MAPLIBRE_WIDGETS_EXPORT +#endif + +#endif // QMAPLIBRE_WIDGETS_EXPORT_H diff --git a/third_party/maplibre-native-qt/include/geojson_p.hpp b/third_party/maplibre-native-qt/include/geojson_p.hpp new file mode 100644 index 0000000000..8387f70c4b --- /dev/null +++ b/third_party/maplibre-native-qt/include/geojson_p.hpp @@ -0,0 +1,30 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "types.hpp" + +#include +#include +#include + +#include + +#include + +namespace QMapLibre::GeoJSON { + +mbgl::Point asPoint(const Coordinate &coordinate); +mbgl::MultiPoint asMultiPoint(const Coordinates &multiPoint); +mbgl::LineString asLineString(const Coordinates &lineString); +mbgl::MultiLineString asMultiLineString(const CoordinatesCollection &multiLineString); +mbgl::Polygon asPolygon(const CoordinatesCollection &polygon); +mbgl::MultiPolygon asMultiPolygon(const CoordinatesCollections &multiPolygon); +mbgl::Value asPropertyValue(const QVariant &value); +mbgl::FeatureIdentifier asFeatureIdentifier(const QVariant &id); +mbgl::GeoJSONFeature asFeature(const Feature &feature); + +} // namespace QMapLibre::GeoJSON diff --git a/third_party/maplibre-native-qt/include/gl_widget.hpp b/third_party/maplibre-native-qt/include/gl_widget.hpp new file mode 100644 index 0000000000..b2630daea7 --- /dev/null +++ b/third_party/maplibre-native-qt/include/gl_widget.hpp @@ -0,0 +1,56 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_GL_WIDGET_H +#define QMAPLIBRE_GL_WIDGET_H + +#include + +#include +#include + +#include + +#include + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +QT_END_NAMESPACE + +namespace QMapLibre { + +class GLWidgetPrivate; + +class Q_MAPLIBRE_WIDGETS_EXPORT GLWidget : public QOpenGLWidget { + Q_OBJECT + +public: + explicit GLWidget(const Settings &); + ~GLWidget() override; + + Map *map(); + +protected: + // QWidget implementation. + void mousePressEvent(QMouseEvent *event) override; + void mouseMoveEvent(QMouseEvent *event) override; + void wheelEvent(QWheelEvent *event) override; + + // Q{,Open}GLWidget implementation. + void initializeGL() override; + void paintGL() override; + +private: + Q_DISABLE_COPY(GLWidget) + + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +#endif // QMAPLIBRE_GL_WIDGET_H diff --git a/third_party/maplibre-native-qt/include/gl_widget_p.hpp b/third_party/maplibre-native-qt/include/gl_widget_p.hpp new file mode 100644 index 0000000000..c97781fd29 --- /dev/null +++ b/third_party/maplibre-native-qt/include/gl_widget_p.hpp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include +#include + +#include + +QT_BEGIN_NAMESPACE + +class QKeyEvent; +class QMouseEvent; +class QWheelEvent; + +QT_END_NAMESPACE + +namespace QMapLibre { + +class GLWidgetPrivate : public QObject { + Q_OBJECT + +public: + explicit GLWidgetPrivate(QObject *parent, Settings settings); + ~GLWidgetPrivate() override; + + void handleMousePressEvent(QMouseEvent *event); + void handleMouseMoveEvent(QMouseEvent *event); + void handleWheelEvent(QWheelEvent *event) const; + + std::unique_ptr m_map{}; + Settings m_settings; + +private: + Q_DISABLE_COPY(GLWidgetPrivate); + + QPointF m_lastPos; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map.hpp b/third_party/maplibre-native-qt/include/map.hpp new file mode 100644 index 0000000000..cd56996185 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map.hpp @@ -0,0 +1,205 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_MAP_H +#define QMAPLIBRE_MAP_H + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate; + +class Q_MAPLIBRE_CORE_EXPORT Map : public QObject { + Q_OBJECT + Q_PROPERTY(double latitude READ latitude WRITE setLatitude) + Q_PROPERTY(double longitude READ longitude WRITE setLongitude) + Q_PROPERTY(double zoom READ zoom WRITE setZoom) + Q_PROPERTY(double bearing READ bearing WRITE setBearing) + Q_PROPERTY(double pitch READ pitch WRITE setPitch) + Q_PROPERTY(QString styleJson READ styleJson WRITE setStyleJson) + Q_PROPERTY(QString styleUrl READ styleUrl WRITE setStyleUrl) + Q_PROPERTY(double scale READ scale WRITE setScale) + Q_PROPERTY(QMapLibre::Coordinate coordinate READ coordinate WRITE setCoordinate) + Q_PROPERTY(QMargins margins READ margins WRITE setMargins) + +public: + enum MapChange { + MapChangeRegionWillChange = 0, + MapChangeRegionWillChangeAnimated, + MapChangeRegionIsChanging, + MapChangeRegionDidChange, + MapChangeRegionDidChangeAnimated, + MapChangeWillStartLoadingMap, + MapChangeDidFinishLoadingMap, + MapChangeDidFailLoadingMap, + MapChangeWillStartRenderingFrame, + MapChangeDidFinishRenderingFrame, + MapChangeDidFinishRenderingFrameFullyRendered, + MapChangeWillStartRenderingMap, + MapChangeDidFinishRenderingMap, + MapChangeDidFinishRenderingMapFullyRendered, + MapChangeDidFinishLoadingStyle, + MapChangeSourceDidChange + }; + + enum MapLoadingFailure { + StyleParseFailure, + StyleLoadFailure, + NotFoundFailure, + UnknownFailure + }; + + // Determines the orientation of the map. + enum NorthOrientation { + NorthUpwards, // Default + NorthRightwards, + NorthDownwards, + NorthLeftwards, + }; + + explicit Map(QObject *parent = nullptr, + const Settings &settings = Settings(), + const QSize &size = QSize(), + qreal pixelRatio = 1); + ~Map() override; + + [[nodiscard]] QString styleJson() const; + [[nodiscard]] QString styleUrl() const; + + void setStyleJson(const QString &); + void setStyleUrl(const QString &); + + [[nodiscard]] double latitude() const; + void setLatitude(double latitude); + + [[nodiscard]] double longitude() const; + void setLongitude(double longitude); + + [[nodiscard]] double scale() const; + void setScale(double scale, const QPointF ¢er = QPointF()); + + [[nodiscard]] double zoom() const; + void setZoom(double zoom); + + [[nodiscard]] double minimumZoom() const; + [[nodiscard]] double maximumZoom() const; + + [[nodiscard]] double bearing() const; + void setBearing(double degrees); + void setBearing(double degrees, const QPointF ¢er); + + [[nodiscard]] double pitch() const; + void setPitch(double pitch); + void pitchBy(double pitch); + + [[nodiscard]] NorthOrientation northOrientation() const; + void setNorthOrientation(NorthOrientation); + + [[nodiscard]] Coordinate coordinate() const; + void setCoordinate(const Coordinate &coordinate); + void setCoordinateZoom(const Coordinate &coordinate, double zoom); + + void jumpTo(const CameraOptions &); + + void setGestureInProgress(bool inProgress); + + void setTransitionOptions(qint64 duration, qint64 delay = 0); + + void addAnnotationIcon(const QString &name, const QImage &sprite); + + AnnotationID addAnnotation(const Annotation &annotation); + void updateAnnotation(AnnotationID id, const Annotation &annotation); + void removeAnnotation(AnnotationID id); + + bool setLayoutProperty(const QString &layerId, const QString &propertyName, const QVariant &value); + bool setPaintProperty(const QString &layerId, const QString &propertyName, const QVariant &value); + + [[nodiscard]] bool isFullyLoaded() const; + + void moveBy(const QPointF &offset); + void scaleBy(double scale, const QPointF ¢er = QPointF()); + void rotateBy(const QPointF &first, const QPointF &second); + + void resize(const QSize &size); + + [[nodiscard]] QPointF pixelForCoordinate(const Coordinate &coordinate) const; + [[nodiscard]] Coordinate coordinateForPixel(const QPointF &pixel) const; + + [[nodiscard]] CoordinateZoom coordinateZoomForBounds(const Coordinate &sw, const Coordinate &ne) const; + [[nodiscard]] CoordinateZoom coordinateZoomForBounds(const Coordinate &sw, + const Coordinate &ne, + double bearing, + double pitch); + + void setMargins(const QMargins &margins); + [[nodiscard]] QMargins margins() const; + + void addSource(const QString &id, const QVariantMap ¶ms); + bool sourceExists(const QString &id); + void updateSource(const QString &id, const QVariantMap ¶ms); + void removeSource(const QString &id); + + void addImage(const QString &id, const QImage &sprite); + void removeImage(const QString &id); + + void addCustomLayer(const QString &id, + std::unique_ptr host, + const QString &before = QString()); + void addLayer(const QString &id, const QVariantMap ¶ms, const QString &before = QString()); + bool layerExists(const QString &id); + void removeLayer(const QString &id); + + [[nodiscard]] QVector layerIds() const; + + void setFilter(const QString &layerId, const QVariant &filter); + [[nodiscard]] QVariant getFilter(const QString &layerId) const; + // When rendering on a different thread, + // should be called on the render thread. + void createRenderer(); + void destroyRenderer(); + void setFramebufferObject(quint32 fbo, const QSize &size); + +public slots: + void render(); + void setConnectionEstablished(); + + // Commit changes, load all the resources + // and renders the map when completed. + void startStaticRender(); + +signals: + void needsRendering(); + void mapChanged(Map::MapChange); + void mapLoadingFailed(Map::MapLoadingFailure, const QString &reason); + void copyrightsChanged(const QString ©rightsHtml); + + void staticRenderFinished(const QString &error); + +private: + Q_DISABLE_COPY(Map) + + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +Q_DECLARE_METATYPE(QMapLibre::Map::MapChange); +Q_DECLARE_METATYPE(QMapLibre::Map::MapLoadingFailure); + +#endif // QMAPLIBRE_MAP_H diff --git a/third_party/maplibre-native-qt/include/map_observer_p.hpp b/third_party/maplibre-native-qt/include/map_observer_p.hpp new file mode 100644 index 0000000000..e68c72b17b --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_observer_p.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "map.hpp" + +#include +#include + +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate; + +class MapObserver : public QObject, public mbgl::MapObserver { + Q_OBJECT + +public: + explicit MapObserver(MapPrivate *ptr); + ~MapObserver() override; + + // mbgl::MapObserver implementation. + void onCameraWillChange(mbgl::MapObserver::CameraChangeMode mode) final; + void onCameraIsChanging() final; + void onCameraDidChange(mbgl::MapObserver::CameraChangeMode mode) final; + void onWillStartLoadingMap() final; + void onDidFinishLoadingMap() final; + void onDidFailLoadingMap(mbgl::MapLoadError error, const std::string &what) final; + void onWillStartRenderingFrame() final; + void onDidFinishRenderingFrame(mbgl::MapObserver::RenderFrameStatus status) final; + void onWillStartRenderingMap() final; + void onDidFinishRenderingMap(mbgl::MapObserver::RenderMode mode) final; + void onDidFinishLoadingStyle() final; + void onSourceChanged(mbgl::style::Source &source) final; + +signals: + void mapChanged(Map::MapChange); + void mapLoadingFailed(Map::MapLoadingFailure, const QString &reason); + void copyrightsChanged(const QString ©rightsHtml); + +private: + Q_DISABLE_COPY(MapObserver) + + MapPrivate *d_ptrRef; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map_p.hpp b/third_party/maplibre-native-qt/include/map_p.hpp new file mode 100644 index 0000000000..9ca0c7e6f5 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_p.hpp @@ -0,0 +1,79 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "map.hpp" +#include "map_observer_p.hpp" +#include "map_renderer_p.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace QMapLibre { + +class MapPrivate : public QObject, public mbgl::RendererFrontend { + Q_OBJECT + +public: + explicit MapPrivate(Map *map, const Settings &settings, const QSize &size, qreal pixelRatio); + ~MapPrivate() override; + + // mbgl::RendererFrontend implementation. + void reset() final {} + void setObserver(mbgl::RendererObserver &observer) final; + void update(std::shared_ptr parameters) final; + + // These need to be called on the same thread. + void createRenderer(); + void destroyRenderer(); + void render(); + void setFramebufferObject(quint32 fbo, const QSize &size); + + using PropertySetter = std::optional (mbgl::style::Layer::*)( + const std::string &, const mbgl::style::conversion::Convertible &); + [[nodiscard]] bool setProperty(const PropertySetter &setter, + const QString &layerId, + const QString &name, + const QVariant &value) const; + + mbgl::EdgeInsets margins; + std::unique_ptr mapObj{}; + +public slots: + void requestRendering(); + +signals: + void needsRendering(); + +private: + Q_DISABLE_COPY(MapPrivate) + + std::recursive_mutex m_mapRendererMutex; + std::shared_ptr m_rendererObserver{}; + std::shared_ptr m_updateParameters{}; + + std::unique_ptr m_mapObserver{}; + std::unique_ptr m_mapRenderer{}; + std::unique_ptr> m_resourceTransform{}; + + Settings::GLContextMode m_mode; + qreal m_pixelRatio; + + QString m_localFontFamily; + + std::atomic_flag m_renderQueued = ATOMIC_FLAG_INIT; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/map_renderer_p.hpp b/third_party/maplibre-native-qt/include/map_renderer_p.hpp new file mode 100644 index 0000000000..b9a087c392 --- /dev/null +++ b/third_party/maplibre-native-qt/include/map_renderer_p.hpp @@ -0,0 +1,63 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "settings.hpp" + +#include "utils/renderer_backend.hpp" + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace mbgl { +class Renderer; +class UpdateParameters; +} // namespace mbgl + +namespace QMapLibre { + +class RendererBackend; + +class MapRenderer : public QObject { + Q_OBJECT + +public: + MapRenderer(qreal pixelRatio, Settings::GLContextMode, const QString &localFontFamily); + ~MapRenderer() override; + + void render(); + void updateFramebuffer(quint32 fbo, const mbgl::Size &size); + void setObserver(mbgl::RendererObserver *observer); + + // Thread-safe, called by the Frontend + void updateParameters(std::shared_ptr parameters); + +signals: + void needsRendering(); + +private: + MBGL_STORE_THREAD(tid) + + Q_DISABLE_COPY(MapRenderer) + + std::mutex m_updateMutex; + std::shared_ptr m_updateParameters; + + RendererBackend m_backend; + std::unique_ptr m_renderer{}; + + bool m_forceScheduler{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qgeomap.hpp b/third_party/maplibre-native-qt/include/qgeomap.hpp new file mode 100644 index 0000000000..5eb0180503 --- /dev/null +++ b/third_party/maplibre-native-qt/include/qgeomap.hpp @@ -0,0 +1,54 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "export_location.hpp" + +#include +#include + +#include + +namespace QMapLibre { + +class QGeoMapMapLibrePrivate; + +class Q_MAPLIBRE_LOCATION_EXPORT QGeoMapMapLibre : public QGeoMap { + Q_OBJECT + Q_DECLARE_PRIVATE(QGeoMapMapLibre) + +public: + explicit QGeoMapMapLibre(QGeoMappingManagerEngine *engine, QObject *parent = nullptr); + ~QGeoMapMapLibre() override; + + [[nodiscard]] Capabilities capabilities() const override; + + void setSettings(const Settings &settings); + void setMapItemsBefore(const QString &mapItemsBefore); + + void addStyleParameter(StyleParameter *parameter); + void removeStyleParameter(StyleParameter *parameter); + void clearStyleParameters(); + +private Q_SLOTS: + // QMapLibre + void onMapChanged(Map::MapChange); + + // QDeclarativeGeoMapItemBase + void onMapItemPropertyChanged(); + void onMapItemSubPropertyChanged(); + void onMapItemUnsupportedPropertyChanged(); + void onMapItemGeometryChanged(); + + // StyleParameter + void onStyleParameterUpdated(StyleParameter *parameter); + +private: + QSGNode *updateSceneGraph(QSGNode *oldNode, QQuickWindow *window) override; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qgeomap_p.hpp b/third_party/maplibre-native-qt/include/qgeomap_p.hpp new file mode 100644 index 0000000000..ce415d9bcf --- /dev/null +++ b/third_party/maplibre-native-qt/include/qgeomap_p.hpp @@ -0,0 +1,93 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "qgeomap.hpp" + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace QMapLibre { + +class Map; +class StyleChange; + +class QGeoMapMapLibrePrivate : public QGeoMapPrivate { + Q_DECLARE_PUBLIC(QGeoMapMapLibre) + +public: + explicit QGeoMapMapLibrePrivate(QGeoMappingManagerEngine *engine); + ~QGeoMapMapLibrePrivate() override; + + QSGNode *updateSceneGraph(QSGNode *oldNode, QQuickWindow *window); + + QGeoMap::ItemTypes supportedMapItemTypes() const override; + void addMapItem(QDeclarativeGeoMapItemBase *item) override; + void removeMapItem(QDeclarativeGeoMapItemBase *item) override; + + void addStyleParameter(StyleParameter *parameter); + void removeStyleParameter(StyleParameter *parameter); + void clearStyleParameters(); + + /* Data members */ + enum SyncState : int { + NoSync = 0, + ViewportSync = 1 << 0, + CameraDataSync = 1 << 1, + MapTypeSync = 1 << 2, + VisibleAreaSync = 1 << 3 + }; + Q_DECLARE_FLAGS(SyncStates, SyncState); + + Settings m_settings; + QString m_mapItemsBefore; + + QList m_mapParameters; + + QTimer m_refresh; + bool m_shouldRefresh = true; + bool m_warned = false; + bool m_threadedRendering = false; + bool m_styleLoaded = false; + + SyncStates m_syncState = NoSync; + + std::vector> m_styleChanges; + +protected: + void changeViewportSize(const QSize &size) override; + void changeCameraData(const QGeoCameraData &data) override; +#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0) + void changeActiveMapType(const QGeoMapType &mapType) override; +#else + void changeActiveMapType(const QGeoMapType mapType) override; +#endif + + void setVisibleArea(const QRectF &visibleArea) override; + QRectF visibleArea() const override; + +private: + Q_DISABLE_COPY(QGeoMapMapLibrePrivate); + + void syncStyleChanges(Map *map); + void threadedRenderingHack(QQuickWindow *window, Map *map); + + QRectF m_visibleArea; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QGeoMapMapLibrePrivate::SyncStates) + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/qmaplibre.hpp b/third_party/maplibre-native-qt/include/qmaplibre.hpp new file mode 100644 index 0000000000..a8dc445e2b --- /dev/null +++ b/third_party/maplibre-native-qt/include/qmaplibre.hpp @@ -0,0 +1,9 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "export_core.hpp" +#include "map.hpp" +#include "settings.hpp" +#include "types.hpp" +#include "utils.hpp" diff --git a/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp b/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp new file mode 100644 index 0000000000..ebe9a8eea4 --- /dev/null +++ b/third_party/maplibre-native-qt/include/qmaplibrewidgets.hpp @@ -0,0 +1,6 @@ +// Copyright (C) 2023 MapLibre contributors + +// SPDX-License-Identifier: BSD-2-Clause + +#include "export_widgets.hpp" +#include "gl_widget.hpp" diff --git a/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp b/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp new file mode 100644 index 0000000000..67cb4b56ce --- /dev/null +++ b/third_party/maplibre-native-qt/include/qt_mapping_engine.hpp @@ -0,0 +1,31 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include "export_location.hpp" + +#include + +#include +#include + +namespace QMapLibre { + +class Q_MAPLIBRE_LOCATION_EXPORT QtMappingEngine : public QGeoMappingManagerEngine { + Q_OBJECT + +public: + QtMappingEngine(const QVariantMap ¶meters, QGeoServiceProvider::Error *error, QString *errorString); + + QGeoMap *createMap() override; + +private: + Settings m_settings; + QString m_mapItemsBefore; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/settings.hpp b/third_party/maplibre-native-qt/include/settings.hpp new file mode 100644 index 0000000000..d6f88b871b --- /dev/null +++ b/third_party/maplibre-native-qt/include/settings.hpp @@ -0,0 +1,125 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_SETTINGS_H +#define QMAPLIBRE_SETTINGS_H + +#include +#include + +#include +#include + +#include +#include + +// TODO: this will be wrapped at some point +namespace mbgl { +class TileServerOptions; +} // namespace mbgl + +namespace QMapLibre { + +class SettingsPrivate; + +class Q_MAPLIBRE_CORE_EXPORT Settings { +public: + enum GLContextMode : bool { + UniqueGLContext, + SharedGLContext + }; + + enum MapMode { + Continuous = 0, + Static + }; + + enum ConstrainMode { + NoConstrain = 0, + ConstrainHeightOnly, + ConstrainWidthAndHeight + }; + + enum ViewportMode { + DefaultViewport = 0, + FlippedYViewport + }; + + enum ProviderTemplate { + NoProvider = 0, + MapLibreProvider, + MapTilerProvider, + MapboxProvider + }; + + using ResourceTransformFunction = std::function; + + explicit Settings(ProviderTemplate provider = NoProvider); + ~Settings(); + Settings(const Settings &s); + Settings(Settings &&s) noexcept; + Settings &operator=(const Settings &s); + Settings &operator=(Settings &&s) noexcept; + + [[nodiscard]] GLContextMode contextMode() const; + void setContextMode(GLContextMode); + + [[nodiscard]] MapMode mapMode() const; + void setMapMode(MapMode); + + [[nodiscard]] ConstrainMode constrainMode() const; + void setConstrainMode(ConstrainMode); + + [[nodiscard]] ViewportMode viewportMode() const; + void setViewportMode(ViewportMode); + + [[nodiscard]] unsigned cacheDatabaseMaximumSize() const; + void setCacheDatabaseMaximumSize(unsigned); + + [[nodiscard]] QString cacheDatabasePath() const; + void setCacheDatabasePath(const QString &path); + + [[nodiscard]] QString assetPath() const; + void setAssetPath(const QString &path); + + [[nodiscard]] QString apiKey() const; + void setApiKey(const QString &key); + + [[nodiscard]] QString apiBaseUrl() const; + void setApiBaseUrl(const QString &url); + + [[nodiscard]] QString localFontFamily() const; + void setLocalFontFamily(const QString &family); + + [[nodiscard]] QString clientName() const; + void setClientName(const QString &name); + + [[nodiscard]] QString clientVersion() const; + void setClientVersion(const QString &version); + + [[nodiscard]] ResourceTransformFunction resourceTransform() const; + void setResourceTransform(const ResourceTransformFunction &transform); + + void setProviderTemplate(ProviderTemplate providerTemplate); + void setStyles(const Styles &styles); + + [[nodiscard]] const Styles &styles() const; + [[nodiscard]] Styles providerStyles() const; + + [[nodiscard]] Coordinate defaultCoordinate() const; + void setDefaultCoordinate(const Coordinate &coordinate); + [[nodiscard]] double defaultZoom() const; + void setDefaultZoom(double zoom); + + [[nodiscard]] bool customTileServerOptions() const; + [[nodiscard]] const mbgl::TileServerOptions &tileServerOptions() const; + +private: + std::unique_ptr d_ptr; +}; + +} // namespace QMapLibre + +#endif // QMAPLIBRE_SETTINGS_H diff --git a/third_party/maplibre-native-qt/include/settings_p.hpp b/third_party/maplibre-native-qt/include/settings_p.hpp new file mode 100644 index 0000000000..257bdfd5a9 --- /dev/null +++ b/third_party/maplibre-native-qt/include/settings_p.hpp @@ -0,0 +1,57 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#pragma once + +#include "settings.hpp" +#include "types.hpp" + +#include + +#include +#include + +#include +#include + +namespace mbgl { +class TileServerOptions; +} // namespace mbgl + +namespace QMapLibre { + +class SettingsPrivate { +public: + SettingsPrivate(); + + void setProviderTemplate(Settings::ProviderTemplate providerTemplate); + void setProviderApiBaseUrl(const QString &url); + + Settings::GLContextMode m_contextMode{Settings::SharedGLContext}; + Settings::MapMode m_mapMode{Settings::Continuous}; + Settings::ConstrainMode m_constrainMode{Settings::ConstrainHeightOnly}; + Settings::ViewportMode m_viewportMode{Settings::DefaultViewport}; + Settings::ProviderTemplate m_providerTemplate{Settings::NoProvider}; + + unsigned m_cacheMaximumSize; + QString m_cacheDatabasePath; + QString m_assetPath; + QString m_apiKey; + QString m_localFontFamily; + QString m_clientName; + QString m_clientVersion; + + Coordinate m_defaultCoordinate{}; + double m_defaultZoom{}; + + Styles m_styles; + + std::function m_resourceTransform; + + bool m_customTileServerOptions{}; + mbgl::TileServerOptions m_tileServerOptions{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/style_change_utils_p.hpp b/third_party/maplibre-native-qt/include/style_change_utils_p.hpp new file mode 100644 index 0000000000..991bb4077e --- /dev/null +++ b/third_party/maplibre-native-qt/include/style_change_utils_p.hpp @@ -0,0 +1,34 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include + +#include +#include +#include +#include +#include + +namespace QMapLibre::StyleChangeUtils { + +Feature featureFromMapRectangle(QDeclarativeRectangleMapItem *item); +Feature featureFromMapCircle(QDeclarativeCircleMapItem *item); +Feature featureFromMapPolygon(QDeclarativePolygonMapItem *item); +Feature featureFromMapPolyline(QDeclarativePolylineMapItem *item); +Feature featureFromMapItem(QDeclarativeGeoMapItemBase *item); + +QString featureId(QDeclarativeGeoMapItemBase *item); +std::vector featureLayoutPropertiesFromMapPolyline(QDeclarativePolylineMapItem *item); +std::vector featureLayoutPropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); +std::vector featurePaintPropertiesFromMapRectangle(QDeclarativeRectangleMapItem *item); +std::vector featurePaingPropertiesFromMapCircle(QDeclarativeCircleMapItem *item); +std::vector featurePaintPropertiesFromMapPolygon(QDeclarativePolygonMapItem *item); +std::vector featurePaintPropertiesFromMapPolyline(QDeclarativePolylineMapItem *item); +std::vector featurePaintPropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); +std::vector featurePropertiesFromMapItem(QDeclarativeGeoMapItemBase *item); + +} // namespace QMapLibre::StyleChangeUtils diff --git a/third_party/maplibre-native-qt/include/texture_node.hpp b/third_party/maplibre-native-qt/include/texture_node.hpp new file mode 100644 index 0000000000..96f63b3534 --- /dev/null +++ b/third_party/maplibre-native-qt/include/texture_node.hpp @@ -0,0 +1,42 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2017 The Qt Company Ltd. +// Copyright (C) 2017 Mapbox, Inc. + +// SPDX-License-Identifier: LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#pragma once + +#include +#include +#include +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +#include +#else +#include +#endif + +#include + +namespace QMapLibre { + +class QGeoMapMapLibre; + +class TextureNode : public QSGSimpleTextureNode { +public: + TextureNode(const Settings &setting, const QSize &size, qreal pixelRatio, QGeoMapMapLibre *geoMap); + + [[nodiscard]] Map *map() const; + +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + void resize(const QSize &size, qreal pixelRatio, QQuickWindow *window); +#else + void resize(const QSize &size, qreal pixelRatio); +#endif + void render(QQuickWindow *); + +private: + std::unique_ptr m_map{}; + std::unique_ptr m_fbo{}; +}; + +} // namespace QMapLibre diff --git a/third_party/maplibre-native-qt/include/types.hpp b/third_party/maplibre-native-qt/include/types.hpp new file mode 100644 index 0000000000..696fab1a88 --- /dev/null +++ b/third_party/maplibre-native-qt/include/types.hpp @@ -0,0 +1,206 @@ +// Copyright (C) 2023 MapLibre contributors +// Copyright (C) 2019 Mapbox, Inc. + +// SPDX-License-Identifier: BSD-2-Clause + +#ifndef QMAPLIBRE_TYPES_H +#define QMAPLIBRE_TYPES_H + +#include + +#include +#include +#include +#include +#include +#include + +namespace QMapLibre { + +using Coordinate = QPair; +using CoordinateZoom = QPair; +using ProjectedMeters = QPair; + +using Coordinates = QVector; +using CoordinatesCollection = QVector; + +using CoordinatesCollections = QVector; + +struct Q_MAPLIBRE_CORE_EXPORT Style { + enum Type { // Taken from Qt to be in sync with QtLocation + NoMap = 0, + StreetMap, + SatelliteMapDay, + SatelliteMapNight, + TerrainMap, + HybridMap, + TransitMap, + GrayStreetMap, + PedestrianMap, + CarNavigationMap, + CycleMap, + CustomMap = 100 + }; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0) + explicit Style(QString url_, QString name_ = QString()) + : url(std::move(url_)), + name(std::move(name_)) {} +#else + explicit Style(QString url_ = QString(), QString name_ = QString()) + : url(std::move(url_)), + name(std::move(name_)) {} +#endif + + QString url; + QString name; + QString description; + bool night{}; + Type type{CustomMap}; +}; + +using Styles = QVector